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 {
|
func prepareEchoResponses(parameters []*proto.RichParameter, presets ...*proto.Preset) *echo.Responses {
|
||||||
return &echo.Responses{
|
return &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionInit: echo.InitComplete,
|
||||||
|
ProvisionPlan: echo.PlanComplete,
|
||||||
|
ProvisionGraph: []*proto.Response{
|
||||||
{
|
{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Graph{
|
||||||
Plan: &proto.PlanComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Parameters: parameters,
|
Parameters: parameters,
|
||||||
Presets: presets,
|
Presets: presets,
|
||||||
},
|
},
|
||||||
@@ -1573,11 +1575,13 @@ func TestCreateValidateRichParameters(t *testing.T) {
|
|||||||
func TestCreateWithGitAuth(t *testing.T) {
|
func TestCreateWithGitAuth(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
echoResponses := &echo.Responses{
|
echoResponses := &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionInit: echo.InitComplete,
|
||||||
|
ProvisionPlan: echo.PlanComplete,
|
||||||
|
ProvisionGraph: []*proto.Response{
|
||||||
{
|
{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Graph{
|
||||||
Plan: &proto.PlanComplete{
|
Graph: &proto.GraphComplete{
|
||||||
ExternalAuthProviders: []*proto.ExternalAuthProviderResource{{Id: "github"}},
|
ExternalAuthProviders: []*proto.ExternalAuthProviderResource{{Id: "github"}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
+3
-3
@@ -306,10 +306,10 @@ func TestRestartWithParameters(t *testing.T) {
|
|||||||
echoResponses := func() *echo.Responses {
|
echoResponses := func() *echo.Responses {
|
||||||
return &echo.Responses{
|
return &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{
|
{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Graph{
|
||||||
Plan: &proto.PlanComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Parameters: []*proto.RichParameter{
|
Parameters: []*proto.RichParameter{
|
||||||
{
|
{
|
||||||
Name: immutableParameterName,
|
Name: immutableParameterName,
|
||||||
|
|||||||
+5
-5
@@ -155,7 +155,7 @@ func TestSSH(t *testing.T) {
|
|||||||
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||||
})
|
})
|
||||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||||
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, 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{
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||||
})
|
})
|
||||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||||
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
||||||
@@ -305,7 +305,7 @@ func TestSSH(t *testing.T) {
|
|||||||
echoResponses := &echo.Responses{
|
echoResponses := &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||||
}
|
}
|
||||||
|
|
||||||
version := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, echoResponses)
|
version := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, echoResponses)
|
||||||
@@ -326,7 +326,7 @@ func TestSSH(t *testing.T) {
|
|||||||
echoResponses2 := &echo.Responses{
|
echoResponses2 := &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken2),
|
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken2),
|
||||||
}
|
}
|
||||||
version = coderdtest.UpdateTemplateVersion(t, ownerClient, owner.OrganizationID, echoResponses2, template.ID)
|
version = coderdtest.UpdateTemplateVersion(t, ownerClient, owner.OrganizationID, echoResponses2, template.ID)
|
||||||
coderdtest.AwaitTemplateVersionJobCompleted(t, ownerClient, version.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{
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||||
})
|
})
|
||||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||||
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
||||||
|
|||||||
+12
-10
@@ -36,10 +36,10 @@ const (
|
|||||||
func mutableParamsResponse() *echo.Responses {
|
func mutableParamsResponse() *echo.Responses {
|
||||||
return &echo.Responses{
|
return &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{
|
{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Graph{
|
||||||
Plan: &proto.PlanComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Parameters: []*proto.RichParameter{
|
Parameters: []*proto.RichParameter{
|
||||||
{
|
{
|
||||||
Name: mutableParameterName,
|
Name: mutableParameterName,
|
||||||
@@ -59,10 +59,10 @@ func mutableParamsResponse() *echo.Responses {
|
|||||||
func immutableParamsResponse() *echo.Responses {
|
func immutableParamsResponse() *echo.Responses {
|
||||||
return &echo.Responses{
|
return &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{
|
{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Graph{
|
||||||
Plan: &proto.PlanComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Parameters: []*proto.RichParameter{
|
Parameters: []*proto.RichParameter{
|
||||||
{
|
{
|
||||||
Name: immutableParameterName,
|
Name: immutableParameterName,
|
||||||
@@ -83,11 +83,13 @@ func TestStart(t *testing.T) {
|
|||||||
|
|
||||||
echoResponses := func() *echo.Responses {
|
echoResponses := func() *echo.Responses {
|
||||||
return &echo.Responses{
|
return &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionInit: echo.InitComplete,
|
||||||
|
ProvisionPlan: echo.PlanComplete,
|
||||||
|
ProvisionGraph: []*proto.Response{
|
||||||
{
|
{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Graph{
|
||||||
Plan: &proto.PlanComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Parameters: []*proto.RichParameter{
|
Parameters: []*proto.RichParameter{
|
||||||
{
|
{
|
||||||
Name: ephemeralParameterName,
|
Name: ephemeralParameterName,
|
||||||
|
|||||||
+4
-12
@@ -285,19 +285,10 @@ func createAITaskTemplate(t *testing.T, client *codersdk.Client, orgID uuid.UUID
|
|||||||
taskAppID := uuid.New()
|
taskAppID := uuid.New()
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, orgID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, orgID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{
|
{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Graph{
|
||||||
Plan: &proto.PlanComplete{
|
Graph: &proto.GraphComplete{
|
||||||
HasAiTasks: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ProvisionApply: []*proto.Response{
|
|
||||||
{
|
|
||||||
Type: &proto.Response_Apply{
|
|
||||||
Apply: &proto.ApplyComplete{
|
|
||||||
Resources: []*proto.Resource{
|
Resources: []*proto.Resource{
|
||||||
{
|
{
|
||||||
Name: "example",
|
Name: "example",
|
||||||
@@ -321,6 +312,7 @@ func createAITaskTemplate(t *testing.T, client *codersdk.Client, orgID uuid.UUID
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
HasAiTasks: true,
|
||||||
AiTasks: []*proto.AITask{
|
AiTasks: []*proto.AITask{
|
||||||
{
|
{
|
||||||
AppId: taskAppID.String(),
|
AppId: taskAppID.String(),
|
||||||
|
|||||||
@@ -282,10 +282,10 @@ func TestTemplatePresets(t *testing.T) {
|
|||||||
func templateWithPresets(presets []*proto.Preset) *echo.Responses {
|
func templateWithPresets(presets []*proto.Preset) *echo.Responses {
|
||||||
return &echo.Responses{
|
return &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{
|
{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Graph{
|
||||||
Plan: &proto.PlanComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Presets: presets,
|
Presets: presets,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1306,31 +1306,10 @@ func createEchoResponsesWithTemplateVariables(templateVariables []*proto.Templat
|
|||||||
func completeWithAgent() *echo.Responses {
|
func completeWithAgent() *echo.Responses {
|
||||||
return &echo.Responses{
|
return &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{
|
{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Graph{
|
||||||
Plan: &proto.PlanComplete{
|
Graph: &proto.GraphComplete{
|
||||||
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{
|
|
||||||
Resources: []*proto.Resource{
|
Resources: []*proto.Resource{
|
||||||
{
|
{
|
||||||
Type: "compute",
|
Type: "compute",
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ func TestTemplateVersionsArchive(t *testing.T) {
|
|||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: echo.ApplyFailed,
|
ProvisionApply: echo.ApplyFailed,
|
||||||
ProvisionPlan: echo.PlanFailed,
|
ProvisionPlan: echo.PlanFailed,
|
||||||
|
ProvisionInit: echo.InitComplete,
|
||||||
}, func(request *codersdk.CreateTemplateVersionRequest) {
|
}, func(request *codersdk.CreateTemplateVersionRequest) {
|
||||||
request.TemplateID = template.ID
|
request.TemplateID = template.ID
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ func TestWorkspaceActivityBump(t *testing.T) {
|
|||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: echo.ProvisionApplyWithAgent(agentToken),
|
ProvisionGraph: echo.ProvisionGraphWithAgent(agentToken),
|
||||||
})
|
})
|
||||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||||
|
|||||||
+19
-27
@@ -61,19 +61,11 @@ func TestTasks(t *testing.T) {
|
|||||||
taskAppID := uuid.New()
|
taskAppID := uuid.New()
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{
|
{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Graph{
|
||||||
Plan: &proto.PlanComplete{
|
Graph: &proto.GraphComplete{
|
||||||
HasAiTasks: true,
|
HasAiTasks: true,
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ProvisionApply: []*proto.Response{
|
|
||||||
{
|
|
||||||
Type: &proto.Response_Apply{
|
|
||||||
Apply: &proto.ApplyComplete{
|
|
||||||
Resources: []*proto.Resource{
|
Resources: []*proto.Resource{
|
||||||
{
|
{
|
||||||
Name: "example",
|
Name: "example",
|
||||||
@@ -951,8 +943,8 @@ func TestTasksCreate(t *testing.T) {
|
|||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: echo.ApplyComplete,
|
ProvisionApply: echo.ApplyComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{Type: &proto.Response_Plan{Plan: &proto.PlanComplete{
|
{Type: &proto.Response_Graph{Graph: &proto.GraphComplete{
|
||||||
HasAiTasks: true,
|
HasAiTasks: true,
|
||||||
}}},
|
}}},
|
||||||
},
|
},
|
||||||
@@ -995,8 +987,8 @@ func TestTasksCreate(t *testing.T) {
|
|||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: echo.ApplyComplete,
|
ProvisionApply: echo.ApplyComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{Type: &proto.Response_Plan{Plan: &proto.PlanComplete{
|
{Type: &proto.Response_Graph{Graph: &proto.GraphComplete{
|
||||||
Parameters: []*proto.RichParameter{{Name: codersdk.AITaskPromptParameterName, Type: "string"}},
|
Parameters: []*proto.RichParameter{{Name: codersdk.AITaskPromptParameterName, Type: "string"}},
|
||||||
HasAiTasks: true,
|
HasAiTasks: true,
|
||||||
}}},
|
}}},
|
||||||
@@ -1097,8 +1089,8 @@ func TestTasksCreate(t *testing.T) {
|
|||||||
version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: echo.ApplyComplete,
|
ProvisionApply: echo.ApplyComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{Type: &proto.Response_Plan{Plan: &proto.PlanComplete{
|
{Type: &proto.Response_Graph{Graph: &proto.GraphComplete{
|
||||||
HasAiTasks: true,
|
HasAiTasks: true,
|
||||||
}}},
|
}}},
|
||||||
},
|
},
|
||||||
@@ -1218,8 +1210,8 @@ func TestTasksCreate(t *testing.T) {
|
|||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: echo.ApplyComplete,
|
ProvisionApply: echo.ApplyComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{Type: &proto.Response_Plan{Plan: &proto.PlanComplete{
|
{Type: &proto.Response_Graph{Graph: &proto.GraphComplete{
|
||||||
HasAiTasks: true,
|
HasAiTasks: true,
|
||||||
}}},
|
}}},
|
||||||
},
|
},
|
||||||
@@ -1275,8 +1267,8 @@ func TestTasksCreate(t *testing.T) {
|
|||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: echo.ApplyComplete,
|
ProvisionApply: echo.ApplyComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{Type: &proto.Response_Plan{Plan: &proto.PlanComplete{
|
{Type: &proto.Response_Graph{Graph: &proto.GraphComplete{
|
||||||
HasAiTasks: true,
|
HasAiTasks: true,
|
||||||
}}},
|
}}},
|
||||||
},
|
},
|
||||||
@@ -1309,8 +1301,8 @@ func TestTasksCreate(t *testing.T) {
|
|||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: echo.ApplyComplete,
|
ProvisionApply: echo.ApplyComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{Type: &proto.Response_Plan{Plan: &proto.PlanComplete{
|
{Type: &proto.Response_Graph{Graph: &proto.GraphComplete{
|
||||||
HasAiTasks: true,
|
HasAiTasks: true,
|
||||||
}}},
|
}}},
|
||||||
},
|
},
|
||||||
@@ -1359,8 +1351,8 @@ func TestTasksCreate(t *testing.T) {
|
|||||||
version1 := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version1 := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: echo.ApplyComplete,
|
ProvisionApply: echo.ApplyComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{Type: &proto.Response_Plan{Plan: &proto.PlanComplete{
|
{Type: &proto.Response_Graph{Graph: &proto.GraphComplete{
|
||||||
HasAiTasks: true,
|
HasAiTasks: true,
|
||||||
}}},
|
}}},
|
||||||
},
|
},
|
||||||
@@ -1371,8 +1363,8 @@ func TestTasksCreate(t *testing.T) {
|
|||||||
version2 := coderdtest.UpdateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version2 := coderdtest.UpdateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: echo.ApplyComplete,
|
ProvisionApply: echo.ApplyComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{Type: &proto.Response_Plan{Plan: &proto.PlanComplete{
|
{Type: &proto.Response_Graph{Graph: &proto.GraphComplete{
|
||||||
HasAiTasks: true,
|
HasAiTasks: true,
|
||||||
}}},
|
}}},
|
||||||
},
|
},
|
||||||
|
|||||||
+3
-30
@@ -476,37 +476,10 @@ func TestAuditLogsFilter(t *testing.T) {
|
|||||||
func completeWithAgentAndApp() *echo.Responses {
|
func completeWithAgentAndApp() *echo.Responses {
|
||||||
return &echo.Responses{
|
return &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{
|
{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Graph{
|
||||||
Plan: &proto.PlanComplete{
|
Graph: &proto.GraphComplete{
|
||||||
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{
|
|
||||||
Resources: []*proto.Resource{
|
Resources: []*proto.Resource{
|
||||||
{
|
{
|
||||||
Type: "compute",
|
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
|
// Since initial version has no parameters, any parameters in the new version will be incompatible
|
||||||
res = &echo.Responses{
|
res = &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: []*proto.Response{{
|
ProvisionGraph: []*proto.Response{{
|
||||||
Type: &proto.Response_Apply{
|
Type: &proto.Response_Graph{
|
||||||
Apply: &proto.ApplyComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Parameters: []*proto.RichParameter{
|
Parameters: []*proto.RichParameter{
|
||||||
{
|
{
|
||||||
Name: "new",
|
Name: "new",
|
||||||
@@ -1105,8 +1105,10 @@ func TestExecutorFailedWorkspace(t *testing.T) {
|
|||||||
user := coderdtest.CreateFirstUser(t, client)
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
|
ProvisionInit: echo.InitComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: echo.ApplyFailed,
|
ProvisionApply: echo.ApplyFailed,
|
||||||
|
ProvisionGraph: echo.GraphComplete,
|
||||||
})
|
})
|
||||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
|
||||||
ctr.FailureTTLMillis = ptr.Ref[int64](failureTTL.Milliseconds())
|
ctr.FailureTTLMillis = ptr.Ref[int64](failureTTL.Milliseconds())
|
||||||
@@ -1644,10 +1646,10 @@ func mustProvisionWorkspaceWithParameters(t *testing.T, client *codersdk.Client,
|
|||||||
user := coderdtest.CreateFirstUser(t, client)
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{
|
{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Graph{
|
||||||
Plan: &proto.PlanComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Parameters: richParameters,
|
Parameters: richParameters,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -1774,17 +1776,10 @@ func TestExecutorTaskWorkspace(t *testing.T) {
|
|||||||
taskAppID := uuid.New()
|
taskAppID := uuid.New()
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, orgID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, orgID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{
|
{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Graph{
|
||||||
Plan: &proto.PlanComplete{HasAiTasks: true},
|
Graph: &proto.GraphComplete{
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ProvisionApply: []*proto.Response{
|
|
||||||
{
|
|
||||||
Type: &proto.Response_Apply{
|
|
||||||
Apply: &proto.ApplyComplete{
|
|
||||||
Resources: []*proto.Resource{
|
Resources: []*proto.Resource{
|
||||||
{
|
{
|
||||||
Agents: []*proto.Agent{
|
Agents: []*proto.Agent{
|
||||||
@@ -1804,6 +1799,7 @@ func TestExecutorTaskWorkspace(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
HasAiTasks: true,
|
||||||
AiTasks: []*proto.AITask{
|
AiTasks: []*proto.AITask{
|
||||||
{
|
{
|
||||||
AppId: taskAppID.String(),
|
AppId: taskAppID.String(),
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ func TestDERPForceWebSockets(t *testing.T) {
|
|||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||||
})
|
})
|
||||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, 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 := echo.WithExtraFiles(extraFiles)
|
||||||
|
files.ProvisionInit = []*proto.Response{{
|
||||||
|
Type: &proto.Response_Init{
|
||||||
|
Init: &proto.InitComplete{
|
||||||
|
ModuleFiles: args.ModulesArchive,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
files.ProvisionPlan = []*proto.Response{{
|
files.ProvisionPlan = []*proto.Response{{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Plan{
|
||||||
Plan: &proto.PlanComplete{
|
Plan: &proto.PlanComplete{
|
||||||
Plan: args.Plan,
|
Plan: args.Plan,
|
||||||
ModuleFiles: args.ModulesArchive,
|
},
|
||||||
Parameters: args.StaticParams,
|
},
|
||||||
|
}}
|
||||||
|
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{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||||
})
|
})
|
||||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, 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{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||||
})
|
})
|
||||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, 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{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||||
})
|
})
|
||||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, 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{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||||
})
|
})
|
||||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, 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{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||||
})
|
})
|
||||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, 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{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: echo.ProvisionApplyWithAgentAndAPIKeyScope(authToken, tt.apiKeyScope),
|
ProvisionGraph: echo.ProvisionGraphWithAgentAndAPIKeyScope(authToken, tt.apiKeyScope),
|
||||||
})
|
})
|
||||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, 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{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||||
})
|
})
|
||||||
project := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
project := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, 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{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: echo.ProvisionApplyWithAgentAndAPIKeyScope(authToken, tt.apiKeyScope),
|
ProvisionGraph: echo.ProvisionGraphWithAgentAndAPIKeyScope(authToken, tt.apiKeyScope),
|
||||||
})
|
})
|
||||||
project := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
project := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, 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{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||||
})
|
})
|
||||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||||
require.Empty(t, template.BuildTimeStats[codersdk.WorkspaceTransitionStart])
|
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{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||||
})
|
})
|
||||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||||
require.Empty(t, template.BuildTimeStats[codersdk.WorkspaceTransitionStart])
|
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{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||||
})
|
})
|
||||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||||
require.Empty(t, template.BuildTimeStats[codersdk.WorkspaceTransitionStart])
|
require.Empty(t, template.BuildTimeStats[codersdk.WorkspaceTransitionStart])
|
||||||
@@ -641,22 +641,16 @@ func TestTemplateInsights_Golden(t *testing.T) {
|
|||||||
// Create the template version and template.
|
// Create the template version and template.
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, firstUser.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, firstUser.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{
|
{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Graph{
|
||||||
Plan: &proto.PlanComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Parameters: parameters,
|
Parameters: parameters,
|
||||||
|
Resources: resources,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ProvisionApply: []*proto.Response{{
|
|
||||||
Type: &proto.Response_Apply{
|
|
||||||
Apply: &proto.ApplyComplete{
|
|
||||||
Resources: resources,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
})
|
})
|
||||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
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{
|
version := coderdtest.CreateTemplateVersion(t, client, firstUser.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: []*proto.Response{{
|
ProvisionGraph: []*proto.Response{{
|
||||||
Type: &proto.Response_Apply{
|
Type: &proto.Response_Graph{
|
||||||
Apply: &proto.ApplyComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Resources: resources,
|
Resources: resources,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -536,9 +536,9 @@ func TestAgents(t *testing.T) {
|
|||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: []*proto.Response{{
|
ProvisionGraph: []*proto.Response{{
|
||||||
Type: &proto.Response_Apply{
|
Type: &proto.Response_Graph{
|
||||||
Apply: &proto.ApplyComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "example",
|
Name: "example",
|
||||||
Type: "aws_instance",
|
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{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||||
})
|
})
|
||||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, 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{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||||
})
|
})
|
||||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||||
require.Equal(t, -1, template.ActiveUserCount)
|
require.Equal(t, -1, template.ActiveUserCount)
|
||||||
|
|||||||
@@ -760,7 +760,7 @@ func TestPatchCancelTemplateVersion(t *testing.T) {
|
|||||||
user := coderdtest.CreateFirstUser(t, client)
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: []*proto.Response{{
|
ProvisionPlan: []*proto.Response{{
|
||||||
Type: &proto.Response_Log{
|
Type: &proto.Response_Log{
|
||||||
Log: &proto.Log{},
|
Log: &proto.Log{},
|
||||||
},
|
},
|
||||||
@@ -793,7 +793,7 @@ func TestPatchCancelTemplateVersion(t *testing.T) {
|
|||||||
user := coderdtest.CreateFirstUser(t, client)
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: []*proto.Response{{
|
ProvisionPlan: []*proto.Response{{
|
||||||
Type: &proto.Response_Log{
|
Type: &proto.Response_Log{
|
||||||
Log: &proto.Log{},
|
Log: &proto.Log{},
|
||||||
},
|
},
|
||||||
@@ -857,9 +857,9 @@ func TestTemplateVersionsExternalAuth(t *testing.T) {
|
|||||||
user := coderdtest.CreateFirstUser(t, client)
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: []*proto.Response{{
|
ProvisionGraph: []*proto.Response{{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Graph{
|
||||||
Plan: &proto.PlanComplete{
|
Graph: &proto.GraphComplete{
|
||||||
ExternalAuthProviders: []*proto.ExternalAuthProviderResource{{Id: "github", Optional: true}},
|
ExternalAuthProviders: []*proto.ExternalAuthProviderResource{{Id: "github", Optional: true}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -912,9 +912,9 @@ func TestTemplateVersionResources(t *testing.T) {
|
|||||||
user := coderdtest.CreateFirstUser(t, client)
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: []*proto.Response{{
|
ProvisionGraph: []*proto.Response{{
|
||||||
Type: &proto.Response_Apply{
|
Type: &proto.Response_Graph{
|
||||||
Apply: &proto.ApplyComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "some",
|
Name: "some",
|
||||||
Type: "example",
|
Type: "example",
|
||||||
@@ -953,7 +953,7 @@ func TestTemplateVersionLogs(t *testing.T) {
|
|||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: []*proto.Response{{
|
ProvisionGraph: []*proto.Response{{
|
||||||
Type: &proto.Response_Log{
|
Type: &proto.Response_Log{
|
||||||
Log: &proto.Log{
|
Log: &proto.Log{
|
||||||
Level: proto.LogLevel_INFO,
|
Level: proto.LogLevel_INFO,
|
||||||
@@ -961,8 +961,8 @@ func TestTemplateVersionLogs(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
Type: &proto.Response_Apply{
|
Type: &proto.Response_Graph{
|
||||||
Apply: &proto.ApplyComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "some",
|
Name: "some",
|
||||||
Type: "example",
|
Type: "example",
|
||||||
@@ -1211,15 +1211,15 @@ func TestTemplateVersionDryRun(t *testing.T) {
|
|||||||
user := coderdtest.CreateFirstUser(t, client)
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{
|
{
|
||||||
Type: &proto.Response_Log{
|
Type: &proto.Response_Log{
|
||||||
Log: &proto.Log{},
|
Log: &proto.Log{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Type: &proto.Response_Apply{
|
Type: &proto.Response_Graph{
|
||||||
Apply: &proto.ApplyComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{resource},
|
Resources: []*proto.Resource{resource},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -2060,10 +2060,10 @@ func TestTemplateVersionParameters_Order(t *testing.T) {
|
|||||||
user := coderdtest.CreateFirstUser(t, client)
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{
|
{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Graph{
|
||||||
Plan: &proto.PlanComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Parameters: []*proto.RichParameter{
|
Parameters: []*proto.RichParameter{
|
||||||
{
|
{
|
||||||
Name: firstParameterName,
|
Name: firstParameterName,
|
||||||
@@ -2133,6 +2133,7 @@ func TestTemplateArchiveVersions(t *testing.T) {
|
|||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanFailed,
|
ProvisionPlan: echo.PlanFailed,
|
||||||
ProvisionApply: echo.ApplyFailed,
|
ProvisionApply: echo.ApplyFailed,
|
||||||
|
ProvisionInit: echo.InitComplete,
|
||||||
}, func(req *codersdk.CreateTemplateVersionRequest) {
|
}, func(req *codersdk.CreateTemplateVersionRequest) {
|
||||||
req.TemplateID = template.ID
|
req.TemplateID = template.ID
|
||||||
})
|
})
|
||||||
@@ -2228,10 +2229,10 @@ func TestTemplateVersionHasExternalAgent(t *testing.T) {
|
|||||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{
|
{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Graph{
|
||||||
Plan: &proto.PlanComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{
|
Resources: []*proto.Resource{
|
||||||
{
|
{
|
||||||
Name: "example",
|
Name: "example",
|
||||||
|
|||||||
@@ -495,7 +495,7 @@ func TestWorkspaceAgentConnectRPC(t *testing.T) {
|
|||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||||
})
|
})
|
||||||
|
|
||||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
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{
|
version = coderdtest.UpdateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: []*proto.Response{{
|
ProvisionGraph: []*proto.Response{{
|
||||||
Type: &proto.Response_Apply{
|
Type: &proto.Response_Graph{
|
||||||
Apply: &proto.ApplyComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "example",
|
Name: "example",
|
||||||
Type: "aws_instance",
|
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{
|
version := coderdtest.CreateTemplateVersion(t, client, orgID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: []*proto.Response{{
|
ProvisionGraph: []*proto.Response{{
|
||||||
Type: &proto.Response_Apply{
|
Type: &proto.Response_Graph{
|
||||||
Apply: &proto.ApplyComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "example",
|
Name: "example",
|
||||||
Type: "aws_instance",
|
Type: "aws_instance",
|
||||||
|
|||||||
@@ -121,9 +121,9 @@ func Test_ResolveRequest(t *testing.T) {
|
|||||||
version := coderdtest.CreateTemplateVersion(t, client, firstUser.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, firstUser.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: []*proto.Response{{
|
ProvisionGraph: []*proto.Response{{
|
||||||
Type: &proto.Response_Apply{
|
Type: &proto.Response_Graph{
|
||||||
Apply: &proto.ApplyComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "example",
|
Name: "example",
|
||||||
Type: "aws_instance",
|
Type: "aws_instance",
|
||||||
|
|||||||
@@ -556,13 +556,16 @@ func TestPatchCancelWorkspaceBuild(t *testing.T) {
|
|||||||
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
||||||
user := coderdtest.CreateFirstUser(t, client)
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
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{{
|
ProvisionApply: []*proto.Response{{
|
||||||
Type: &proto.Response_Log{
|
Type: &proto.Response_Log{
|
||||||
Log: &proto.Log{},
|
Log: &proto.Log{},
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
ProvisionPlan: echo.PlanComplete,
|
|
||||||
})
|
})
|
||||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, 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})
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true, Logger: &logger})
|
||||||
owner := coderdtest.CreateFirstUser(t, client)
|
owner := coderdtest.CreateFirstUser(t, client)
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, &echo.Responses{
|
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{{
|
ProvisionApply: []*proto.Response{{
|
||||||
Type: &proto.Response_Log{
|
Type: &proto.Response_Log{
|
||||||
Log: &proto.Log{},
|
Log: &proto.Log{},
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
ProvisionPlan: echo.PlanComplete,
|
|
||||||
})
|
})
|
||||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||||
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, 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})
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
||||||
user := coderdtest.CreateFirstUser(t, client)
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
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{{
|
ProvisionApply: []*proto.Response{{
|
||||||
Type: &proto.Response_Log{
|
Type: &proto.Response_Log{
|
||||||
Log: &proto.Log{},
|
Log: &proto.Log{},
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
ProvisionPlan: echo.PlanComplete,
|
|
||||||
})
|
})
|
||||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, 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})
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
||||||
user := coderdtest.CreateFirstUser(t, client)
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
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{{
|
ProvisionApply: []*proto.Response{{
|
||||||
Type: &proto.Response_Log{
|
Type: &proto.Response_Log{
|
||||||
Log: &proto.Log{},
|
Log: &proto.Log{},
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
ProvisionPlan: echo.PlanComplete,
|
|
||||||
})
|
})
|
||||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, 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)
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: []*proto.Response{{
|
ProvisionGraph: []*proto.Response{{
|
||||||
Type: &proto.Response_Apply{
|
Type: &proto.Response_Graph{
|
||||||
Apply: &proto.ApplyComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "first_resource",
|
Name: "first_resource",
|
||||||
Type: "example",
|
Type: "example",
|
||||||
@@ -1032,7 +1044,7 @@ func TestWorkspaceBuildLogs(t *testing.T) {
|
|||||||
user := coderdtest.CreateFirstUser(t, client)
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: []*proto.Response{{
|
ProvisionGraph: []*proto.Response{{
|
||||||
Type: &proto.Response_Log{
|
Type: &proto.Response_Log{
|
||||||
Log: &proto.Log{
|
Log: &proto.Log{
|
||||||
Level: proto.LogLevel_INFO,
|
Level: proto.LogLevel_INFO,
|
||||||
@@ -1040,8 +1052,8 @@ func TestWorkspaceBuildLogs(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
Type: &proto.Response_Apply{
|
Type: &proto.Response_Graph{
|
||||||
Apply: &proto.ApplyComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "some",
|
Name: "some",
|
||||||
Type: "example",
|
Type: "example",
|
||||||
@@ -1208,9 +1220,9 @@ func TestWorkspaceDeleteSuspendedUser(t *testing.T) {
|
|||||||
version := coderdtest.CreateTemplateVersion(t, client, first.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, first.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: echo.ApplyComplete,
|
ProvisionApply: echo.ApplyComplete,
|
||||||
ProvisionPlan: []*proto.Response{{
|
ProvisionGraph: []*proto.Response{{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Graph{
|
||||||
Plan: &proto.PlanComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Error: "",
|
Error: "",
|
||||||
Resources: nil,
|
Resources: nil,
|
||||||
Parameters: nil,
|
Parameters: nil,
|
||||||
@@ -1488,10 +1500,18 @@ func TestPostWorkspaceBuild(t *testing.T) {
|
|||||||
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
||||||
user := coderdtest.CreateFirstUser(t, client)
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
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)
|
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)
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@@ -1642,9 +1662,9 @@ func TestPostWorkspaceBuild(t *testing.T) {
|
|||||||
user := coderdtest.CreateFirstUser(t, client)
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: []*proto.Response{{
|
ProvisionGraph: []*proto.Response{{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Graph{
|
||||||
Plan: &proto.PlanComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Presets: []*proto.Preset{
|
Presets: []*proto.Preset{
|
||||||
{
|
{
|
||||||
Name: "autodetected",
|
Name: "autodetected",
|
||||||
|
|||||||
@@ -26,9 +26,9 @@ func TestPostWorkspaceAuthAzureInstanceIdentity(t *testing.T) {
|
|||||||
user := coderdtest.CreateFirstUser(t, client)
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: []*proto.Response{{
|
ProvisionGraph: []*proto.Response{{
|
||||||
Type: &proto.Response_Apply{
|
Type: &proto.Response_Graph{
|
||||||
Apply: &proto.ApplyComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "somename",
|
Name: "somename",
|
||||||
Type: "someinstance",
|
Type: "someinstance",
|
||||||
@@ -70,9 +70,9 @@ func TestPostWorkspaceAuthAWSInstanceIdentity(t *testing.T) {
|
|||||||
user := coderdtest.CreateFirstUser(t, client)
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: []*proto.Response{{
|
ProvisionGraph: []*proto.Response{{
|
||||||
Type: &proto.Response_Apply{
|
Type: &proto.Response_Graph{
|
||||||
Apply: &proto.ApplyComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "somename",
|
Name: "somename",
|
||||||
Type: "someinstance",
|
Type: "someinstance",
|
||||||
@@ -151,9 +151,9 @@ func TestPostWorkspaceAuthGoogleInstanceIdentity(t *testing.T) {
|
|||||||
user := coderdtest.CreateFirstUser(t, client)
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: []*proto.Response{{
|
ProvisionGraph: []*proto.Response{{
|
||||||
Type: &proto.Response_Apply{
|
Type: &proto.Response_Graph{
|
||||||
Apply: &proto.ApplyComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "somename",
|
Name: "somename",
|
||||||
Type: "someinstance",
|
Type: "someinstance",
|
||||||
|
|||||||
+67
-65
@@ -213,9 +213,9 @@ func TestWorkspace(t *testing.T) {
|
|||||||
user := coderdtest.CreateFirstUser(t, client)
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: []*proto.Response{{
|
ProvisionGraph: []*proto.Response{{
|
||||||
Type: &proto.Response_Apply{
|
Type: &proto.Response_Graph{
|
||||||
Apply: &proto.ApplyComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "some",
|
Name: "some",
|
||||||
Type: "example",
|
Type: "example",
|
||||||
@@ -254,9 +254,9 @@ func TestWorkspace(t *testing.T) {
|
|||||||
user := coderdtest.CreateFirstUser(t, client)
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: []*proto.Response{{
|
ProvisionGraph: []*proto.Response{{
|
||||||
Type: &proto.Response_Apply{
|
Type: &proto.Response_Graph{
|
||||||
Apply: &proto.ApplyComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "some",
|
Name: "some",
|
||||||
Type: "example",
|
Type: "example",
|
||||||
@@ -299,9 +299,9 @@ func TestWorkspace(t *testing.T) {
|
|||||||
user := coderdtest.CreateFirstUser(t, client)
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: []*proto.Response{{
|
ProvisionGraph: []*proto.Response{{
|
||||||
Type: &proto.Response_Apply{
|
Type: &proto.Response_Graph{
|
||||||
Apply: &proto.ApplyComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "some",
|
Name: "some",
|
||||||
Type: "example",
|
Type: "example",
|
||||||
@@ -357,9 +357,9 @@ func TestWorkspace(t *testing.T) {
|
|||||||
user := coderdtest.CreateFirstUser(t, client)
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: []*proto.Response{{
|
ProvisionGraph: []*proto.Response{{
|
||||||
Type: &proto.Response_Apply{
|
Type: &proto.Response_Graph{
|
||||||
Apply: &proto.ApplyComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "some",
|
Name: "some",
|
||||||
Type: "example",
|
Type: "example",
|
||||||
@@ -735,9 +735,9 @@ func TestWorkspace(t *testing.T) {
|
|||||||
authz := coderdtest.AssertRBAC(t, api, client)
|
authz := coderdtest.AssertRBAC(t, api, client)
|
||||||
|
|
||||||
// Create a plan response with the specified presets and parameters
|
// Create a plan response with the specified presets and parameters
|
||||||
planResponse := &proto.Response{
|
graphResponse := &proto.Response{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Graph{
|
||||||
Plan: &proto.PlanComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Presets: tc.presets,
|
Presets: tc.presets,
|
||||||
Parameters: tc.templateVersionParameters,
|
Parameters: tc.templateVersionParameters,
|
||||||
},
|
},
|
||||||
@@ -746,7 +746,7 @@ func TestWorkspace(t *testing.T) {
|
|||||||
|
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: []*proto.Response{planResponse},
|
ProvisionGraph: []*proto.Response{graphResponse},
|
||||||
ProvisionApply: echo.ApplyComplete,
|
ProvisionApply: echo.ApplyComplete,
|
||||||
})
|
})
|
||||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||||
@@ -2269,7 +2269,7 @@ func TestWorkspaceFilterManual(t *testing.T) {
|
|||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||||
})
|
})
|
||||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, 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{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||||
})
|
})
|
||||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, 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{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: []*proto.Response{{
|
ProvisionGraph: []*proto.Response{{
|
||||||
Type: &proto.Response_Apply{
|
Type: &proto.Response_Graph{
|
||||||
Apply: &proto.ApplyComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "example",
|
Name: "example",
|
||||||
Type: "aws_instance",
|
Type: "aws_instance",
|
||||||
@@ -2420,7 +2420,7 @@ func TestWorkspaceFilterManual(t *testing.T) {
|
|||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||||
})
|
})
|
||||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||||
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||||
@@ -2525,10 +2525,10 @@ func TestWorkspaceFilterManual(t *testing.T) {
|
|||||||
makeParameters := func(extra ...*proto.RichParameter) *echo.Responses {
|
makeParameters := func(extra ...*proto.RichParameter) *echo.Responses {
|
||||||
return &echo.Responses{
|
return &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{
|
{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Graph{
|
||||||
Plan: &proto.PlanComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Parameters: append([]*proto.RichParameter{
|
Parameters: append([]*proto.RichParameter{
|
||||||
{Name: paramOneName, Description: "", Mutable: true, Type: "string"},
|
{Name: paramOneName, Description: "", Mutable: true, Type: "string"},
|
||||||
{Name: paramTwoName, DisplayName: "", 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{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: []*proto.Response{{
|
ProvisionGraph: []*proto.Response{{
|
||||||
Type: &proto.Response_Apply{
|
Type: &proto.Response_Graph{
|
||||||
Apply: &proto.ApplyComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "example",
|
Name: "example",
|
||||||
Type: "aws_instance",
|
Type: "aws_instance",
|
||||||
@@ -3467,8 +3467,10 @@ func TestWorkspaceWatcher(t *testing.T) {
|
|||||||
|
|
||||||
// Add a new version that will fail.
|
// Add a new version that will fail.
|
||||||
badVersion := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
badVersion := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
|
ProvisionInit: echo.InitComplete,
|
||||||
|
ProvisionGraph: echo.GraphComplete,
|
||||||
ProvisionApply: []*proto.Response{{
|
ProvisionApply: []*proto.Response{{
|
||||||
Type: &proto.Response_Apply{
|
Type: &proto.Response_Apply{
|
||||||
Apply: &proto.ApplyComplete{
|
Apply: &proto.ApplyComplete{
|
||||||
@@ -3536,9 +3538,9 @@ func TestWorkspaceResource(t *testing.T) {
|
|||||||
user := coderdtest.CreateFirstUser(t, client)
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: []*proto.Response{{
|
ProvisionGraph: []*proto.Response{{
|
||||||
Type: &proto.Response_Apply{
|
Type: &proto.Response_Graph{
|
||||||
Apply: &proto.ApplyComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "beta",
|
Name: "beta",
|
||||||
Type: "example",
|
Type: "example",
|
||||||
@@ -3604,9 +3606,9 @@ func TestWorkspaceResource(t *testing.T) {
|
|||||||
}
|
}
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: []*proto.Response{{
|
ProvisionGraph: []*proto.Response{{
|
||||||
Type: &proto.Response_Apply{
|
Type: &proto.Response_Graph{
|
||||||
Apply: &proto.ApplyComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "some",
|
Name: "some",
|
||||||
Type: "example",
|
Type: "example",
|
||||||
@@ -3679,9 +3681,9 @@ func TestWorkspaceResource(t *testing.T) {
|
|||||||
}
|
}
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: []*proto.Response{{
|
ProvisionGraph: []*proto.Response{{
|
||||||
Type: &proto.Response_Apply{
|
Type: &proto.Response_Graph{
|
||||||
Apply: &proto.ApplyComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "some",
|
Name: "some",
|
||||||
Type: "example",
|
Type: "example",
|
||||||
@@ -3723,9 +3725,9 @@ func TestWorkspaceResource(t *testing.T) {
|
|||||||
user := coderdtest.CreateFirstUser(t, client)
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: []*proto.Response{{
|
ProvisionGraph: []*proto.Response{{
|
||||||
Type: &proto.Response_Apply{
|
Type: &proto.Response_Graph{
|
||||||
Apply: &proto.ApplyComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "some",
|
Name: "some",
|
||||||
Type: "example",
|
Type: "example",
|
||||||
@@ -3803,10 +3805,10 @@ func TestWorkspaceWithRichParameters(t *testing.T) {
|
|||||||
user := coderdtest.CreateFirstUser(t, client)
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{
|
{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Graph{
|
||||||
Plan: &proto.PlanComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Parameters: []*proto.RichParameter{
|
Parameters: []*proto.RichParameter{
|
||||||
{
|
{
|
||||||
Name: firstParameterName,
|
Name: firstParameterName,
|
||||||
@@ -3907,10 +3909,10 @@ func TestWorkspaceWithMultiSelectFailure(t *testing.T) {
|
|||||||
user := coderdtest.CreateFirstUser(t, client)
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{
|
{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Graph{
|
||||||
Plan: &proto.PlanComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Parameters: []*proto.RichParameter{
|
Parameters: []*proto.RichParameter{
|
||||||
{
|
{
|
||||||
Name: "param",
|
Name: "param",
|
||||||
@@ -3986,10 +3988,10 @@ func TestWorkspaceWithOptionalRichParameters(t *testing.T) {
|
|||||||
user := coderdtest.CreateFirstUser(t, client)
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{
|
{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Graph{
|
||||||
Plan: &proto.PlanComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Parameters: []*proto.RichParameter{
|
Parameters: []*proto.RichParameter{
|
||||||
{
|
{
|
||||||
Name: firstParameterName,
|
Name: firstParameterName,
|
||||||
@@ -4077,10 +4079,10 @@ func TestWorkspaceWithEphemeralRichParameters(t *testing.T) {
|
|||||||
user := coderdtest.CreateFirstUser(t, client)
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{
|
{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Graph{
|
||||||
Plan: &proto.PlanComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Parameters: []*proto.RichParameter{
|
Parameters: []*proto.RichParameter{
|
||||||
{
|
{
|
||||||
Name: firstParameterName,
|
Name: firstParameterName,
|
||||||
@@ -4879,8 +4881,8 @@ func TestWorkspaceListTasks(t *testing.T) {
|
|||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: echo.ApplyComplete,
|
ProvisionApply: echo.ApplyComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{Type: &proto.Response_Plan{Plan: &proto.PlanComplete{
|
{Type: &proto.Response_Graph{Graph: &proto.GraphComplete{
|
||||||
HasAiTasks: true,
|
HasAiTasks: true,
|
||||||
}}},
|
}}},
|
||||||
},
|
},
|
||||||
@@ -4949,9 +4951,9 @@ func TestWorkspaceAppUpsertRestart(t *testing.T) {
|
|||||||
// Create template version with workspace app
|
// Create template version with workspace app
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: []*proto.Response{{
|
ProvisionGraph: []*proto.Response{{
|
||||||
Type: &proto.Response_Apply{
|
Type: &proto.Response_Graph{
|
||||||
Apply: &proto.ApplyComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "test-resource",
|
Name: "test-resource",
|
||||||
Type: "example",
|
Type: "example",
|
||||||
@@ -5023,9 +5025,9 @@ func TestMultipleAITasksDisallowed(t *testing.T) {
|
|||||||
|
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: []*proto.Response{{
|
ProvisionGraph: []*proto.Response{{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Graph{
|
||||||
Plan: &proto.PlanComplete{
|
Graph: &proto.GraphComplete{
|
||||||
HasAiTasks: true,
|
HasAiTasks: true,
|
||||||
AiTasks: []*proto.AITask{
|
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) {
|
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{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{
|
{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Graph{
|
||||||
Plan: &proto.PlanComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Presets: presets,
|
Presets: presets,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1015,8 +1015,8 @@ func TestTools(t *testing.T) {
|
|||||||
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: echo.ApplyComplete,
|
ProvisionApply: echo.ApplyComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{Type: &proto.Response_Plan{Plan: &proto.PlanComplete{
|
{Type: &proto.Response_Graph{Graph: &proto.GraphComplete{
|
||||||
Parameters: []*proto.RichParameter{{Name: "AI Prompt", Type: "string"}},
|
Parameters: []*proto.RichParameter{{Name: "AI Prompt", Type: "string"}},
|
||||||
HasAiTasks: true,
|
HasAiTasks: true,
|
||||||
}}},
|
}}},
|
||||||
|
|||||||
@@ -560,20 +560,12 @@ func TestEnterpriseCreateWithPreset(t *testing.T) {
|
|||||||
func prepareEchoResponses(parameters []*proto.RichParameter, presets ...*proto.Preset) *echo.Responses {
|
func prepareEchoResponses(parameters []*proto.RichParameter, presets ...*proto.Preset) *echo.Responses {
|
||||||
return &echo.Responses{
|
return &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{
|
{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Graph{
|
||||||
Plan: &proto.PlanComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Parameters: parameters,
|
Parameters: parameters,
|
||||||
Presets: presets,
|
Presets: presets,
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ProvisionApply: []*proto.Response{
|
|
||||||
{
|
|
||||||
Type: &proto.Response_Apply{
|
|
||||||
Apply: &proto.ApplyComplete{
|
|
||||||
Resources: []*proto.Resource{
|
Resources: []*proto.Resource{
|
||||||
{
|
{
|
||||||
Type: "compute",
|
Type: "compute",
|
||||||
|
|||||||
@@ -24,10 +24,10 @@ import (
|
|||||||
func completeWithExternalAgent() *echo.Responses {
|
func completeWithExternalAgent() *echo.Responses {
|
||||||
return &echo.Responses{
|
return &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{
|
{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Graph{
|
||||||
Plan: &proto.PlanComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{
|
Resources: []*proto.Resource{
|
||||||
{
|
{
|
||||||
Type: "coder_external_agent",
|
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 {
|
func completeWithRegularAgent() *echo.Responses {
|
||||||
return &echo.Responses{
|
return &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{
|
{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Graph{
|
||||||
Plan: &proto.PlanComplete{
|
Graph: &proto.GraphComplete{
|
||||||
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{
|
|
||||||
Resources: []*proto.Resource{
|
Resources: []*proto.Resource{
|
||||||
{
|
{
|
||||||
Type: "compute",
|
Type: "compute",
|
||||||
|
|||||||
@@ -660,21 +660,21 @@ func TestManagedAgentLimit(t *testing.T) {
|
|||||||
// build.
|
// build.
|
||||||
appID := uuid.NewString()
|
appID := uuid.NewString()
|
||||||
echoRes := &echo.Responses{
|
echoRes := &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
|
ProvisionInit: echo.InitComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionPlan: []*proto.Response{
|
||||||
{
|
{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Plan{
|
||||||
Plan: &proto.PlanComplete{
|
Plan: &proto.PlanComplete{
|
||||||
Plan: []byte("{}"),
|
Plan: []byte("{}"),
|
||||||
ModuleFiles: []byte{},
|
|
||||||
HasAiTasks: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ProvisionApply: []*proto.Response{{
|
ProvisionApply: echo.ApplyComplete,
|
||||||
Type: &proto.Response_Apply{
|
ProvisionGraph: []*proto.Response{{
|
||||||
Apply: &proto.ApplyComplete{
|
Type: &proto.Response_Graph{
|
||||||
|
Graph: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "example",
|
Name: "example",
|
||||||
Type: "aws_instance",
|
Type: "aws_instance",
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ func TestAgentGitSSHKeyCustomRoles(t *testing.T) {
|
|||||||
version := coderdtest.CreateTemplateVersion(t, client, org.ID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, org.ID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||||
})
|
})
|
||||||
project := coderdtest.CreateTemplate(t, client, org.ID, version.ID)
|
project := coderdtest.CreateTemplate(t, client, org.ID, version.ID)
|
||||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||||
|
|||||||
@@ -384,10 +384,10 @@ func TestClaimPrebuild(t *testing.T) {
|
|||||||
func templateWithAgentAndPresetsWithPrebuilds(desiredInstances int32) *echo.Responses {
|
func templateWithAgentAndPresetsWithPrebuilds(desiredInstances int32) *echo.Responses {
|
||||||
return &echo.Responses{
|
return &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{
|
{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Graph{
|
||||||
Plan: &proto.PlanComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{
|
Resources: []*proto.Resource{
|
||||||
{
|
{
|
||||||
Type: "compute",
|
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()
|
authToken := uuid.NewString()
|
||||||
data, err := echo.Tar(&echo.Responses{
|
data, err := echo.Tar(&echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: []*sdkproto.Response{{
|
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken, func(g *sdkproto.GraphComplete) {
|
||||||
Type: &sdkproto.Response_Plan{
|
g.Resources = []*sdkproto.Resource{{
|
||||||
Plan: &sdkproto.PlanComplete{
|
Name: "example",
|
||||||
Resources: []*sdkproto.Resource{{
|
Type: "aws_instance",
|
||||||
Name: "example",
|
Agents: []*sdkproto.Agent{{
|
||||||
Type: "aws_instance",
|
Id: uuid.NewString(),
|
||||||
Agents: []*sdkproto.Agent{{
|
Name: "example",
|
||||||
Id: uuid.NewString(),
|
}},
|
||||||
Name: "example",
|
}}
|
||||||
}},
|
}),
|
||||||
}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
//nolint:gocritic // Not testing file upload in this test.
|
//nolint:gocritic // Not testing file upload in this test.
|
||||||
@@ -446,9 +441,9 @@ func TestProvisionerDaemonServe(t *testing.T) {
|
|||||||
authToken := uuid.NewString()
|
authToken := uuid.NewString()
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: []*sdkproto.Response{{
|
ProvisionGraph: []*sdkproto.Response{{
|
||||||
Type: &sdkproto.Response_Apply{
|
Type: &sdkproto.Response_Graph{
|
||||||
Apply: &sdkproto.ApplyComplete{
|
Graph: &sdkproto.GraphComplete{
|
||||||
Resources: []*sdkproto.Resource{{
|
Resources: []*sdkproto.Resource{{
|
||||||
Name: "example",
|
Name: "example",
|
||||||
Type: "aws_instance",
|
Type: "aws_instance",
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ func TestTemplates(t *testing.T) {
|
|||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: []*proto.Response{{
|
ProvisionGraph: []*proto.Response{{
|
||||||
Type: &proto.Response_Log{
|
Type: &proto.Response_Log{
|
||||||
Log: &proto.Log{
|
Log: &proto.Log{
|
||||||
Level: proto.LogLevel_INFO,
|
Level: proto.LogLevel_INFO,
|
||||||
@@ -155,8 +155,8 @@ func TestTemplates(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
Type: &proto.Response_Apply{
|
Type: &proto.Response_Graph{
|
||||||
Apply: &proto.ApplyComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "some",
|
Name: "some",
|
||||||
Type: "example",
|
Type: "example",
|
||||||
@@ -2161,10 +2161,10 @@ func TestInvalidateTemplatePrebuilds(t *testing.T) {
|
|||||||
})
|
})
|
||||||
templateAdminClient, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID, rbac.RoleTemplateAdmin())
|
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{
|
return &proto.Response{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Graph{
|
||||||
Plan: &proto.PlanComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Presets: presets,
|
Presets: presets,
|
||||||
Parameters: templateVersionParameters,
|
Parameters: templateVersionParameters,
|
||||||
},
|
},
|
||||||
@@ -2174,8 +2174,8 @@ func TestInvalidateTemplatePrebuilds(t *testing.T) {
|
|||||||
|
|
||||||
version1 := coderdtest.CreateTemplateVersion(t, templateAdminClient, owner.OrganizationID, &echo.Responses{
|
version1 := coderdtest.CreateTemplateVersion(t, templateAdminClient, owner.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: []*proto.Response{buildPlanResponse(presetWithParameters1, presetWithParameters2)},
|
|
||||||
ProvisionApply: echo.ApplyComplete,
|
ProvisionApply: echo.ApplyComplete,
|
||||||
|
ProvisionGraph: []*proto.Response{buildGraphResponse(presetWithParameters1, presetWithParameters2)},
|
||||||
})
|
})
|
||||||
coderdtest.AwaitTemplateVersionJobCompleted(t, templateAdminClient, version1.ID)
|
coderdtest.AwaitTemplateVersionJobCompleted(t, templateAdminClient, version1.ID)
|
||||||
template := coderdtest.CreateTemplate(t, templateAdminClient, owner.OrganizationID, 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...
|
// Given the template is updated...
|
||||||
version2 := coderdtest.UpdateTemplateVersion(t, templateAdminClient, owner.OrganizationID, &echo.Responses{
|
version2 := coderdtest.UpdateTemplateVersion(t, templateAdminClient, owner.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: []*proto.Response{buildPlanResponse(presetWithParameters2, presetWithParameters3)},
|
ProvisionGraph: []*proto.Response{buildGraphResponse(presetWithParameters2, presetWithParameters3)},
|
||||||
ProvisionApply: echo.ApplyComplete,
|
ProvisionApply: echo.ApplyComplete,
|
||||||
}, template.ID)
|
}, template.ID)
|
||||||
coderdtest.AwaitTemplateVersionJobCompleted(t, templateAdminClient, version2.ID)
|
coderdtest.AwaitTemplateVersionJobCompleted(t, templateAdminClient, version2.ID)
|
||||||
@@ -2239,10 +2239,10 @@ func TestInvalidateTemplatePrebuilds_RegularUser(t *testing.T) {
|
|||||||
// Given
|
// Given
|
||||||
version1 := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, &echo.Responses{
|
version1 := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{
|
{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Graph{
|
||||||
Plan: &proto.PlanComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Presets: []*proto.Preset{presetWithParameters1},
|
Presets: []*proto.Preset{presetWithParameters1},
|
||||||
Parameters: templateVersionParameters,
|
Parameters: templateVersionParameters,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -134,10 +134,10 @@ func TestReinitializeAgent(t *testing.T) {
|
|||||||
agentToken := uuid.UUID{3}
|
agentToken := uuid.UUID{3}
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, orgID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, orgID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{
|
{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Graph{
|
||||||
Plan: &proto.PlanComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Presets: []*proto.Preset{
|
Presets: []*proto.Preset{
|
||||||
{
|
{
|
||||||
Name: "test-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{
|
Resources: []*proto.Resource{
|
||||||
{
|
{
|
||||||
Type: "compute",
|
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)
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||||
|
|
||||||
@@ -273,9 +261,9 @@ func setupWorkspaceAgent(t *testing.T, client *codersdk.Client, user codersdk.Cr
|
|||||||
authToken := uuid.NewString()
|
authToken := uuid.NewString()
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: []*proto.Response{{
|
ProvisionGraph: []*proto.Response{{
|
||||||
Type: &proto.Response_Apply{
|
Type: &proto.Response_Graph{
|
||||||
Apply: &proto.ApplyComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "example",
|
Name: "example",
|
||||||
Type: "aws_instance",
|
Type: "aws_instance",
|
||||||
|
|||||||
@@ -633,7 +633,7 @@ func TestIssueSignedAppToken(t *testing.T) {
|
|||||||
authToken := uuid.NewString()
|
authToken := uuid.NewString()
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||||
})
|
})
|
||||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||||
@@ -756,7 +756,7 @@ func TestReconnectingPTYSignedToken(t *testing.T) {
|
|||||||
authToken := uuid.NewString()
|
authToken := uuid.NewString()
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||||
})
|
})
|
||||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||||
|
|||||||
@@ -121,9 +121,16 @@ func TestWorkspaceQuota(t *testing.T) {
|
|||||||
authToken := uuid.NewString()
|
authToken := uuid.NewString()
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: []*proto.Response{{
|
ProvisionPlan: []*proto.Response{{
|
||||||
Type: &proto.Response_Apply{
|
Type: &proto.Response_Plan{
|
||||||
Apply: &proto.ApplyComplete{
|
Plan: &proto.PlanComplete{
|
||||||
|
DailyCost: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
ProvisionGraph: []*proto.Response{{
|
||||||
|
Type: &proto.Response_Graph{
|
||||||
|
Graph: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "example",
|
Name: "example",
|
||||||
Type: "aws_instance",
|
Type: "aws_instance",
|
||||||
@@ -216,14 +223,17 @@ func TestWorkspaceQuota(t *testing.T) {
|
|||||||
verifyQuota(ctx, t, client, user.OrganizationID.String(), 0, 4)
|
verifyQuota(ctx, t, client, user.OrganizationID.String(), 0, 4)
|
||||||
|
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
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{
|
ProvisionPlanMap: map[proto.WorkspaceTransition][]*proto.Response{
|
||||||
proto.WorkspaceTransition_START: planWithCost(2),
|
proto.WorkspaceTransition_START: planWithCost(2),
|
||||||
proto.WorkspaceTransition_STOP: planWithCost(1),
|
proto.WorkspaceTransition_STOP: planWithCost(1),
|
||||||
},
|
},
|
||||||
ProvisionApplyMap: map[proto.WorkspaceTransition][]*proto.Response{
|
ProvisionGraphMap: map[proto.WorkspaceTransition][]*proto.Response{
|
||||||
proto.WorkspaceTransition_START: applyWithCost(2),
|
proto.WorkspaceTransition_START: graphWithCost(2),
|
||||||
proto.WorkspaceTransition_STOP: applyWithCost(1),
|
proto.WorkspaceTransition_STOP: graphWithCost(1),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -422,10 +432,19 @@ func TestWorkspaceQuota(t *testing.T) {
|
|||||||
// Create a template with a workspace that costs 1 credit
|
// Create a template with a workspace that costs 1 credit
|
||||||
authToken := uuid.NewString()
|
authToken := uuid.NewString()
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: []*proto.Response{{
|
ProvisionInit: echo.InitComplete,
|
||||||
Type: &proto.Response_Apply{
|
ProvisionPlan: []*proto.Response{{
|
||||||
Apply: &proto.ApplyComplete{
|
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{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "example",
|
Name: "example",
|
||||||
Type: "aws_instance",
|
Type: "aws_instance",
|
||||||
@@ -458,10 +477,19 @@ func TestWorkspaceQuota(t *testing.T) {
|
|||||||
|
|
||||||
// Test with a template that has zero cost - should pass
|
// Test with a template that has zero cost - should pass
|
||||||
versionZeroCost := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
versionZeroCost := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: []*proto.Response{{
|
ProvisionInit: echo.InitComplete,
|
||||||
Type: &proto.Response_Apply{
|
ProvisionPlan: []*proto.Response{{
|
||||||
Apply: &proto.ApplyComplete{
|
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{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "example",
|
Name: "example",
|
||||||
Type: "aws_instance",
|
Type: "aws_instance",
|
||||||
@@ -542,10 +570,19 @@ func TestWorkspaceQuota(t *testing.T) {
|
|||||||
// Create templates for both organizations
|
// Create templates for both organizations
|
||||||
authToken := uuid.NewString()
|
authToken := uuid.NewString()
|
||||||
version1 := coderdtest.CreateTemplateVersion(t, owner, first.OrganizationID, &echo.Responses{
|
version1 := coderdtest.CreateTemplateVersion(t, owner, first.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: []*proto.Response{{
|
ProvisionInit: echo.InitComplete,
|
||||||
Type: &proto.Response_Apply{
|
ProvisionPlan: []*proto.Response{{
|
||||||
Apply: &proto.ApplyComplete{
|
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{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "example",
|
Name: "example",
|
||||||
Type: "aws_instance",
|
Type: "aws_instance",
|
||||||
@@ -566,10 +603,19 @@ func TestWorkspaceQuota(t *testing.T) {
|
|||||||
template1 := coderdtest.CreateTemplate(t, owner, first.OrganizationID, version1.ID)
|
template1 := coderdtest.CreateTemplate(t, owner, first.OrganizationID, version1.ID)
|
||||||
|
|
||||||
version2 := coderdtest.CreateTemplateVersion(t, owner, second.ID, &echo.Responses{
|
version2 := coderdtest.CreateTemplateVersion(t, owner, second.ID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: []*proto.Response{{
|
ProvisionInit: echo.InitComplete,
|
||||||
Type: &proto.Response_Apply{
|
ProvisionPlan: []*proto.Response{{
|
||||||
Apply: &proto.ApplyComplete{
|
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{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "example",
|
Name: "example",
|
||||||
Type: "aws_instance",
|
Type: "aws_instance",
|
||||||
@@ -1156,20 +1202,16 @@ func planWithCost(cost int32) []*proto.Response {
|
|||||||
return []*proto.Response{{
|
return []*proto.Response{{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Plan{
|
||||||
Plan: &proto.PlanComplete{
|
Plan: &proto.PlanComplete{
|
||||||
Resources: []*proto.Resource{{
|
DailyCost: cost,
|
||||||
Name: "example",
|
|
||||||
Type: "aws_instance",
|
|
||||||
DailyCost: cost,
|
|
||||||
}},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyWithCost(cost int32) []*proto.Response {
|
func graphWithCost(cost int32) []*proto.Response {
|
||||||
return []*proto.Response{{
|
return []*proto.Response{{
|
||||||
Type: &proto.Response_Apply{
|
Type: &proto.Response_Graph{
|
||||||
Apply: &proto.ApplyComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "example",
|
Name: "example",
|
||||||
Type: "aws_instance",
|
Type: "aws_instance",
|
||||||
|
|||||||
@@ -629,6 +629,8 @@ func TestWorkspaceAutobuild(t *testing.T) {
|
|||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: echo.ApplyFailed,
|
ProvisionApply: echo.ApplyFailed,
|
||||||
|
ProvisionInit: echo.InitComplete,
|
||||||
|
ProvisionGraph: echo.GraphComplete,
|
||||||
})
|
})
|
||||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
|
||||||
ctr.FailureTTLMillis = ptr.Ref[int64](failureTTL.Milliseconds())
|
ctr.FailureTTLMillis = ptr.Ref[int64](failureTTL.Milliseconds())
|
||||||
@@ -680,6 +682,8 @@ func TestWorkspaceAutobuild(t *testing.T) {
|
|||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: echo.ApplyFailed,
|
ProvisionApply: echo.ApplyFailed,
|
||||||
|
ProvisionInit: echo.InitComplete,
|
||||||
|
ProvisionGraph: echo.GraphComplete,
|
||||||
})
|
})
|
||||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
|
||||||
ctr.FailureTTLMillis = ptr.Ref[int64](failureTTL.Milliseconds())
|
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{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||||
})
|
})
|
||||||
|
|
||||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
|
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,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: echo.ApplyComplete,
|
ProvisionApply: echo.ApplyComplete,
|
||||||
|
ProvisionInit: echo.InitComplete,
|
||||||
|
ProvisionGraph: echo.GraphComplete,
|
||||||
})
|
})
|
||||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||||
|
|
||||||
@@ -1397,6 +1403,8 @@ func TestWorkspaceAutobuild(t *testing.T) {
|
|||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: echo.ApplyFailed,
|
ProvisionApply: echo.ApplyFailed,
|
||||||
|
ProvisionInit: echo.InitComplete,
|
||||||
|
ProvisionGraph: echo.GraphComplete,
|
||||||
}, func(ctvr *codersdk.CreateTemplateVersionRequest) {
|
}, func(ctvr *codersdk.CreateTemplateVersionRequest) {
|
||||||
ctvr.TemplateID = template.ID
|
ctvr.TemplateID = template.ID
|
||||||
})
|
})
|
||||||
@@ -2579,21 +2587,11 @@ func templateWithAgentAndPresetsWithPrebuilds(desiredInstances int32) *echo.Resp
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
applyResponse := func(withAgent bool) *proto.Response {
|
graphResponse := func(withAgent bool) *proto.Response {
|
||||||
return &proto.Response{
|
return &proto.Response{
|
||||||
Type: &proto.Response_Apply{
|
Type: &proto.Response_Graph{
|
||||||
Apply: &proto.ApplyComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{resource(withAgent)},
|
Resources: []*proto.Resource{resource(withAgent)},
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &echo.Responses{
|
|
||||||
Parse: echo.ParseComplete,
|
|
||||||
ProvisionPlan: []*proto.Response{{
|
|
||||||
Type: &proto.Response_Plan{
|
|
||||||
Plan: &proto.PlanComplete{
|
|
||||||
Presets: []*proto.Preset{{
|
Presets: []*proto.Preset{{
|
||||||
Name: "preset-test",
|
Name: "preset-test",
|
||||||
Parameters: []*proto.PresetParameter{{Name: "k1", Value: "v1"}},
|
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{
|
ProvisionGraphMap: map[proto.WorkspaceTransition][]*proto.Response{
|
||||||
proto.WorkspaceTransition_START: {applyResponse(true)},
|
proto.WorkspaceTransition_START: {graphResponse(true)},
|
||||||
proto.WorkspaceTransition_STOP: {applyResponse(false)},
|
proto.WorkspaceTransition_STOP: {graphResponse(false)},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2612,10 +2619,10 @@ func templateWithAgentAndPresetsWithPrebuilds(desiredInstances int32) *echo.Resp
|
|||||||
func templateWithFailedResponseAndPresetsWithPrebuilds(desiredInstances int32) *echo.Responses {
|
func templateWithFailedResponseAndPresetsWithPrebuilds(desiredInstances int32) *echo.Responses {
|
||||||
return &echo.Responses{
|
return &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{
|
{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Graph{
|
||||||
Plan: &proto.PlanComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Presets: []*proto.Preset{
|
Presets: []*proto.Preset{
|
||||||
{
|
{
|
||||||
Name: "preset-test",
|
Name: "preset-test",
|
||||||
|
|||||||
@@ -74,10 +74,14 @@ func TestRemoteConnector_Mainline(t *testing.T) {
|
|||||||
c := resp.Client
|
c := resp.Client
|
||||||
s, err := c.Session(ctx)
|
s, err := c.Session(ctx)
|
||||||
require.NoError(t, err)
|
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,
|
TemplateSourceArchive: arc,
|
||||||
}}})
|
}}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
_, err = s.Recv()
|
||||||
|
require.NoError(t, err)
|
||||||
err = s.Send(&sdkproto.Request{Type: &sdkproto.Request_Parse{Parse: &sdkproto.ParseRequest{}}})
|
err = s.Send(&sdkproto.Request{Type: &sdkproto.Request_Parse{Parse: &sdkproto.ParseRequest{}}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
r, err := s.Recv()
|
r, err := s.Recv()
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ func TestDERP(t *testing.T) {
|
|||||||
authToken := uuid.NewString()
|
authToken := uuid.NewString()
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||||
})
|
})
|
||||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||||
@@ -411,7 +411,7 @@ func TestDERPEndToEnd(t *testing.T) {
|
|||||||
authToken := uuid.NewString()
|
authToken := uuid.NewString()
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
ProvisionGraph: echo.ProvisionGraphWithAgent(authToken),
|
||||||
})
|
})
|
||||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||||
|
|||||||
+246
-47
@@ -12,6 +12,7 @@ import (
|
|||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"github.com/spf13/afero"
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
protobuf "google.golang.org/protobuf/proto"
|
protobuf "google.golang.org/protobuf/proto"
|
||||||
|
|
||||||
@@ -21,12 +22,12 @@ import (
|
|||||||
"github.com/coder/coder/v2/provisionersdk/proto"
|
"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.
|
// "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{{
|
return []*proto.Response{{
|
||||||
Type: &proto.Response_Apply{
|
Type: &proto.Response_Graph{
|
||||||
Apply: &proto.ApplyComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "example_with_scope",
|
Name: "example_with_scope",
|
||||||
Type: "aws_instance",
|
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.
|
// "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{{
|
return []*proto.Response{{
|
||||||
Type: &proto.Response_Apply{
|
Type: &proto.Response_Graph{
|
||||||
Apply: &proto.ApplyComplete{
|
Graph: gc,
|
||||||
Resources: []*proto.Resource{{
|
|
||||||
Name: "example",
|
|
||||||
Type: "aws_instance",
|
|
||||||
Agents: []*proto.Agent{{
|
|
||||||
Id: uuid.NewString(),
|
|
||||||
Name: "example",
|
|
||||||
Auth: &proto.Agent_Token{
|
|
||||||
Token: authToken,
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
@@ -73,12 +79,19 @@ var (
|
|||||||
Parse: &proto.ParseComplete{},
|
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 is a helper to indicate an empty provision completion.
|
||||||
PlanComplete = []*proto.Response{{
|
PlanComplete = []*proto.Response{{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Plan{
|
||||||
Plan: &proto.PlanComplete{
|
Plan: &proto.PlanComplete{
|
||||||
Plan: []byte("{}"),
|
Plan: []byte("{}"),
|
||||||
ModuleFiles: []byte{},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
@@ -88,7 +101,19 @@ var (
|
|||||||
Apply: &proto.ApplyComplete{},
|
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 is a helper to convey a failed plan operation
|
||||||
PlanFailed = []*proto.Response{{
|
PlanFailed = []*proto.Response{{
|
||||||
Type: &proto.Response_Plan{
|
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.
|
// Serve starts the echo provisioner.
|
||||||
@@ -174,6 +206,59 @@ func (*echo) Parse(sess *provisionersdk.Session, _ *proto.ParseRequest, _ <-chan
|
|||||||
return provisionersdk.ParseErrorf("complete response missing")
|
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.
|
// Plan reads requests from the provided directory to stream responses.
|
||||||
func (*echo) Plan(sess *provisionersdk.Session, req *proto.PlanRequest, canceledOrComplete <-chan struct{}) *proto.PlanComplete {
|
func (*echo) Plan(sess *provisionersdk.Session, req *proto.PlanRequest, canceledOrComplete <-chan struct{}) *proto.PlanComplete {
|
||||||
responses, err := readResponses(
|
responses, err := readResponses(
|
||||||
@@ -228,19 +313,73 @@ func (*echo) Shutdown(_ context.Context, _ *proto.Empty) (*proto.Empty, error) {
|
|||||||
type Responses struct {
|
type Responses struct {
|
||||||
Parse []*proto.Response
|
Parse []*proto.Response
|
||||||
|
|
||||||
// ProvisionApply and ProvisionPlan are used to mock ALL responses of
|
// Used to mock ALL responses regardless of transition.
|
||||||
// Apply and Plan, regardless of transition.
|
ProvisionInit []*proto.Response
|
||||||
ProvisionApply []*proto.Response
|
|
||||||
ProvisionPlan []*proto.Response
|
ProvisionPlan []*proto.Response
|
||||||
|
ProvisionApply []*proto.Response
|
||||||
|
ProvisionGraph []*proto.Response
|
||||||
|
|
||||||
// ProvisionApplyMap and ProvisionPlanMap are used to mock specific
|
// Used to mock specific transition responses. They are prioritized over the generic responses.
|
||||||
// transition responses. They are prioritized over the generic responses.
|
|
||||||
ProvisionApplyMap map[proto.WorkspaceTransition][]*proto.Response
|
|
||||||
ProvisionPlanMap map[proto.WorkspaceTransition][]*proto.Response
|
ProvisionPlanMap map[proto.WorkspaceTransition][]*proto.Response
|
||||||
|
ProvisionApplyMap map[proto.WorkspaceTransition][]*proto.Response
|
||||||
|
ProvisionGraphMap map[proto.WorkspaceTransition][]*proto.Response
|
||||||
|
|
||||||
ExtraFiles map[string][]byte
|
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.
|
// Tar returns a tar archive of responses to provisioner operations.
|
||||||
func Tar(responses *Responses) ([]byte, error) {
|
func Tar(responses *Responses) ([]byte, error) {
|
||||||
logger := slog.Make()
|
logger := slog.Make()
|
||||||
@@ -255,31 +394,56 @@ func TarWithOptions(ctx context.Context, logger slog.Logger, responses *Response
|
|||||||
if responses == nil {
|
if responses == nil {
|
||||||
responses = &Responses{
|
responses = &Responses{
|
||||||
Parse: ParseComplete,
|
Parse: ParseComplete,
|
||||||
ProvisionApply: ApplyComplete,
|
ProvisionInit: InitComplete,
|
||||||
ProvisionPlan: PlanComplete,
|
ProvisionPlan: PlanComplete,
|
||||||
|
ProvisionApply: ApplyComplete,
|
||||||
|
ProvisionGraph: GraphComplete,
|
||||||
ProvisionApplyMap: nil,
|
ProvisionApplyMap: nil,
|
||||||
ProvisionPlanMap: nil,
|
ProvisionPlanMap: nil,
|
||||||
ExtraFiles: 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 {
|
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 {
|
if resp.GetLog() != nil {
|
||||||
responses.ProvisionPlan = append(responses.ProvisionPlan, resp)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
responses.ProvisionPlan = append(responses.ProvisionPlan, &proto.Response{
|
if g := resp.GetGraph(); g != nil {
|
||||||
Type: &proto.Response_Plan{Plan: &proto.PlanComplete{
|
dailycost := int32(0)
|
||||||
Error: resp.GetApply().GetError(),
|
for _, r := range g.GetResources() {
|
||||||
Resources: resp.GetApply().GetResources(),
|
dailycost += r.DailyCost
|
||||||
Parameters: resp.GetApply().GetParameters(),
|
}
|
||||||
ExternalAuthProviders: resp.GetApply().GetExternalAuthProviders(),
|
responses.ProvisionPlan = []*proto.Response{{
|
||||||
Plan: []byte("{}"),
|
Type: &proto.Response_Plan{
|
||||||
ModuleFiles: []byte{},
|
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 {
|
for _, resp := range responses.ProvisionPlan {
|
||||||
plan := resp.GetPlan()
|
plan := resp.GetPlan()
|
||||||
@@ -315,6 +479,13 @@ func TarWithOptions(ctx context.Context, logger slog.Logger, responses *Response
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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))
|
logger.Debug(context.Background(), "proto written", slog.F("name", name), slog.F("bytes_written", n))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -325,6 +496,12 @@ func TarWithOptions(ctx context.Context, logger slog.Logger, responses *Response
|
|||||||
return nil, err
|
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 {
|
for index, response := range responses.ProvisionApply {
|
||||||
err := writeProto(fmt.Sprintf("%d.apply.protobuf", index), response)
|
err := writeProto(fmt.Sprintf("%d.apply.protobuf", index), response)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -337,6 +514,12 @@ func TarWithOptions(ctx context.Context, logger slog.Logger, responses *Response
|
|||||||
return nil, err
|
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 trans, m := range responses.ProvisionApplyMap {
|
||||||
for i, rs := range m {
|
for i, rs := range m {
|
||||||
err := writeProto(fmt.Sprintf("%d.%s.apply.protobuf", i, strings.ToLower(trans.String())), rs)
|
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{}
|
dirs := []string{}
|
||||||
for name, content := range responses.ExtraFiles {
|
for name, content := range responses.ExtraFiles {
|
||||||
logger.Debug(ctx, "extra file", slog.F("name", name))
|
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
|
// that matches the parameters defined in the responses. Dynamic parameters
|
||||||
// parsed these, even in the echo provisioner.
|
// parsed these, even in the echo provisioner.
|
||||||
var mainTF bytes.Buffer
|
var mainTF bytes.Buffer
|
||||||
for _, respPlan := range responses.ProvisionPlan {
|
for _, respPlan := range responses.ProvisionGraph {
|
||||||
plan := respPlan.GetPlan()
|
plan := respPlan.GetGraph()
|
||||||
if plan == nil {
|
if plan == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -440,6 +631,11 @@ terraform {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := responses.Valid(); err != nil {
|
||||||
|
return nil, xerrors.Errorf("responses invalid: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return buffer.Bytes(), nil
|
return buffer.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -508,13 +704,14 @@ data "coder_parameter" "{{ .Name }}" {
|
|||||||
|
|
||||||
func WithResources(resources []*proto.Resource) *Responses {
|
func WithResources(resources []*proto.Resource) *Responses {
|
||||||
return &Responses{
|
return &Responses{
|
||||||
Parse: ParseComplete,
|
Parse: ParseComplete,
|
||||||
ProvisionApply: []*proto.Response{{Type: &proto.Response_Apply{Apply: &proto.ApplyComplete{
|
ProvisionInit: InitComplete,
|
||||||
|
ProvisionApply: []*proto.Response{{Type: &proto.Response_Apply{Apply: &proto.ApplyComplete{}}}},
|
||||||
|
ProvisionGraph: []*proto.Response{{Type: &proto.Response_Graph{Graph: &proto.GraphComplete{
|
||||||
Resources: resources,
|
Resources: resources,
|
||||||
}}}},
|
}}}},
|
||||||
ProvisionPlan: []*proto.Response{{Type: &proto.Response_Plan{Plan: &proto.PlanComplete{
|
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 {
|
func WithExtraFiles(extraFiles map[string][]byte) *Responses {
|
||||||
return &Responses{
|
return &Responses{
|
||||||
Parse: ParseComplete,
|
Parse: ParseComplete,
|
||||||
|
ProvisionInit: InitComplete,
|
||||||
ProvisionApply: ApplyComplete,
|
ProvisionApply: ApplyComplete,
|
||||||
ProvisionPlan: PlanComplete,
|
ProvisionPlan: PlanComplete,
|
||||||
|
ProvisionGraph: GraphComplete,
|
||||||
ExtraFiles: extraFiles,
|
ExtraFiles: extraFiles,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,8 @@ func TestEcho(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
data, err := echo.Tar(&echo.Responses{
|
data, err := echo.Tar(&echo.Responses{
|
||||||
Parse: responses,
|
Parse: responses,
|
||||||
|
ProvisionInit: echo.InitComplete,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
client, err := api.Session(ctx)
|
client, err := api.Session(ctx)
|
||||||
@@ -65,13 +66,19 @@ func TestEcho(t *testing.T) {
|
|||||||
err := client.Close()
|
err := client.Close()
|
||||||
require.NoError(t, err)
|
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,
|
TemplateSourceArchive: data,
|
||||||
}}})
|
}}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
_, err = client.Recv()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = client.Send(&proto.Request{Type: &proto.Request_Parse{Parse: &proto.ParseRequest{}}})
|
err = client.Send(&proto.Request{Type: &proto.Request_Parse{Parse: &proto.ParseRequest{}}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
log, err := client.Recv()
|
log, err := client.Recv()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, responses[0].GetLog().Output, log.GetLog().Output)
|
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)
|
ctx, cancel := context.WithTimeout(ctx, testutil.WaitShort)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
planResponses := []*proto.Response{
|
graphResponses := []*proto.Response{
|
||||||
{
|
{
|
||||||
Type: &proto.Response_Log{
|
Type: &proto.Response_Log{
|
||||||
Log: &proto.Log{
|
Log: &proto.Log{
|
||||||
@@ -95,27 +102,8 @@ func TestEcho(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Graph{
|
||||||
Plan: &proto.PlanComplete{
|
Graph: &proto.GraphComplete{
|
||||||
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{
|
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "resource",
|
Name: "resource",
|
||||||
}},
|
}},
|
||||||
@@ -123,9 +111,12 @@ func TestEcho(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := echo.Tar(&echo.Responses{
|
data, err := echo.Tar(&echo.Responses{
|
||||||
ProvisionPlan: planResponses,
|
ProvisionGraph: graphResponses,
|
||||||
ProvisionApply: applyResponses,
|
ProvisionApply: echo.ApplyComplete,
|
||||||
|
ProvisionPlan: echo.PlanComplete,
|
||||||
|
ProvisionInit: echo.InitComplete,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
client, err := api.Session(ctx)
|
client, err := api.Session(ctx)
|
||||||
@@ -134,30 +125,38 @@ func TestEcho(t *testing.T) {
|
|||||||
err := client.Close()
|
err := client.Close()
|
||||||
require.NoError(t, err)
|
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,
|
TemplateSourceArchive: data,
|
||||||
}}})
|
}}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
_, err = client.Recv()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = client.Send(&proto.Request{Type: &proto.Request_Plan{Plan: &proto.PlanRequest{}}})
|
err = client.Send(&proto.Request{Type: &proto.Request_Plan{Plan: &proto.PlanRequest{}}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
log, err := client.Recv()
|
_, err = client.Recv()
|
||||||
require.NoError(t, err)
|
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{}}})
|
err = client.Send(&proto.Request{Type: &proto.Request_Apply{Apply: &proto.ApplyRequest{}}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
log, err = client.Recv()
|
_, err = client.Recv()
|
||||||
require.NoError(t, err)
|
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.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) {
|
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.
|
// Stop responses should be returned when the workspace is being stopped.
|
||||||
data, err := echo.Tar(&echo.Responses{
|
data, err := echo.Tar(&echo.Responses{
|
||||||
ProvisionApply: applyCompleteResource("DEFAULT"),
|
ProvisionApply: echo.ApplyComplete,
|
||||||
ProvisionPlan: planCompleteResource("DEFAULT"),
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionPlanMap: map[proto.WorkspaceTransition][]*proto.Response{
|
ProvisionGraph: graphCompleteResource("DEFAULT"),
|
||||||
proto.WorkspaceTransition_STOP: planCompleteResource("STOP"),
|
ProvisionGraphMap: map[proto.WorkspaceTransition][]*proto.Response{
|
||||||
},
|
proto.WorkspaceTransition_STOP: graphCompleteResource("STOP"),
|
||||||
ProvisionApplyMap: map[proto.WorkspaceTransition][]*proto.Response{
|
|
||||||
proto.WorkspaceTransition_STOP: applyCompleteResource("STOP"),
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -182,10 +179,15 @@ func TestEcho(t *testing.T) {
|
|||||||
err := client.Close()
|
err := client.Close()
|
||||||
require.NoError(t, err)
|
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,
|
TemplateSourceArchive: data,
|
||||||
}}})
|
}}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
_, err = client.Recv()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Do stop.
|
// Do stop.
|
||||||
err = client.Send(&proto.Request{
|
err = client.Send(&proto.Request{
|
||||||
@@ -199,17 +201,32 @@ func TestEcho(t *testing.T) {
|
|||||||
})
|
})
|
||||||
require.NoError(t, err)
|
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()
|
complete, err := client.Recv()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t,
|
require.Equal(t,
|
||||||
"STOP",
|
"STOP",
|
||||||
complete.GetPlan().Resources[0].Name,
|
complete.GetGraph().Resources[0].Name,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Do start.
|
// Do start.
|
||||||
err = client.Send(&proto.Request{
|
err = client.Send(&proto.Request{
|
||||||
Type: &proto.Request_Plan{
|
Type: &proto.Request_Graph{
|
||||||
Plan: &proto.PlanRequest{
|
Graph: &proto.GraphRequest{
|
||||||
Metadata: &proto.Metadata{
|
Metadata: &proto.Metadata{
|
||||||
WorkspaceTransition: proto.WorkspaceTransition_START,
|
WorkspaceTransition: proto.WorkspaceTransition_START,
|
||||||
},
|
},
|
||||||
@@ -222,7 +239,7 @@ func TestEcho(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t,
|
require.Equal(t,
|
||||||
"DEFAULT",
|
"DEFAULT",
|
||||||
complete.GetPlan().Resources[0].Name,
|
complete.GetGraph().Resources[0].Name,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -246,8 +263,8 @@ func TestEcho(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
Type: &proto.Response_Apply{
|
Type: &proto.Response_Graph{
|
||||||
Apply: &proto.ApplyComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "resource",
|
Name: "resource",
|
||||||
}},
|
}},
|
||||||
@@ -256,7 +273,9 @@ func TestEcho(t *testing.T) {
|
|||||||
}}
|
}}
|
||||||
data, err := echo.Tar(&echo.Responses{
|
data, err := echo.Tar(&echo.Responses{
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: responses,
|
ProvisionApply: echo.ApplyComplete,
|
||||||
|
ProvisionInit: echo.InitComplete,
|
||||||
|
ProvisionGraph: responses,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
client, err := api.Session(ctx)
|
client, err := api.Session(ctx)
|
||||||
@@ -266,11 +285,17 @@ func TestEcho(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
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{
|
||||||
TemplateSourceArchive: data,
|
ProvisionerLogLevel: "debug",
|
||||||
ProvisionerLogLevel: "debug",
|
|
||||||
}}})
|
}}})
|
||||||
require.NoError(t, err)
|
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
|
// Plan is required before apply
|
||||||
err = client.Send(&proto.Request{Type: &proto.Request_Plan{Plan: &proto.PlanRequest{}}})
|
err = client.Send(&proto.Request{Type: &proto.Request_Plan{Plan: &proto.PlanRequest{}}})
|
||||||
require.NoError(t, err)
|
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{}}})
|
err = client.Send(&proto.Request{Type: &proto.Request_Apply{Apply: &proto.ApplyRequest{}}})
|
||||||
require.NoError(t, err)
|
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()
|
log, err := client.Recv()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
// Skip responses[0] as it's trace level
|
// Skip responses[0] as it's trace level
|
||||||
require.Equal(t, responses[1].GetLog().Output, log.GetLog().Output)
|
require.Equal(t, responses[1].GetLog().Output, log.GetLog().Output)
|
||||||
complete, err = client.Recv()
|
complete, err = client.Recv()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, responses[2].GetApply().Resources[0].Name,
|
require.Equal(t, responses[2].GetGraph().Resources[0].Name,
|
||||||
complete.GetApply().Resources[0].Name)
|
complete.GetGraph().Resources[0].Name)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func planCompleteResource(name string) []*proto.Response {
|
func graphCompleteResource(name string) []*proto.Response {
|
||||||
return []*proto.Response{{
|
return []*proto.Response{{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Graph{
|
||||||
Plan: &proto.PlanComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
|
||||||
Name: name,
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func applyCompleteResource(name string) []*proto.Response {
|
|
||||||
return []*proto.Response{{
|
|
||||||
Type: &proto.Response_Apply{
|
|
||||||
Apply: &proto.ApplyComplete{
|
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: name,
|
Name: name,
|
||||||
}},
|
}},
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import (
|
|||||||
"cdr.dev/slog"
|
"cdr.dev/slog"
|
||||||
"github.com/coder/coder/v2/provisionersdk/tfpath"
|
"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/coderd/tracing"
|
||||||
"github.com/coder/coder/v2/provisionersdk/proto"
|
"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 {
|
func checksumFileCRC32(ctx context.Context, logger slog.Logger, path string) uint32 {
|
||||||
content, err := os.ReadFile(path)
|
content, err := os.ReadFile(path)
|
||||||
if err != nil {
|
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 0
|
||||||
}
|
}
|
||||||
return crc32.ChecksumIEEE(content)
|
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)
|
return nil, xerrors.Errorf("terraform plan: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Capture the duration of the call to `terraform graph`.
|
plan, err := e.parsePlan(ctx, killCtx, planfilePath)
|
||||||
graphTimings := newTimingAggregator(database.ProvisionerJobTimingStageGraph)
|
|
||||||
graphTimings.ingest(createGraphTimingsEvent(timingGraphStart))
|
|
||||||
|
|
||||||
state, plan, err := e.planResources(ctx, killCtx, planfilePath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
graphTimings.ingest(createGraphTimingsEvent(timingGraphErrored))
|
return nil, xerrors.Errorf("show terraform plan file: %w", err)
|
||||||
return nil, xerrors.Errorf("plan resources: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
planJSON, err := json.Marshal(plan)
|
planJSON, err := json.Marshal(plan)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Errorf("marshal plan: %w", err)
|
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
|
// 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!
|
// the point of prebuilding if the expensive resource is replaced once claimed!
|
||||||
var (
|
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{
|
msg := &proto.PlanComplete{
|
||||||
Parameters: state.Parameters,
|
Plan: planJSON,
|
||||||
Resources: state.Resources,
|
DailyCost: state.DailyCost,
|
||||||
ExternalAuthProviders: state.ExternalAuthProviders,
|
ResourceReplacements: resReps,
|
||||||
Timings: graphTimings.aggregate(),
|
AiTaskCount: state.AITaskCount,
|
||||||
Presets: state.Presets,
|
|
||||||
Plan: planJSON,
|
|
||||||
ResourceReplacements: resReps,
|
|
||||||
ModuleFiles: moduleFiles,
|
|
||||||
HasAiTasks: state.HasAITasks,
|
|
||||||
AiTasks: state.AITasks,
|
|
||||||
HasExternalAgents: state.HasExternalAgents,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return msg, nil
|
return msg, nil
|
||||||
@@ -418,42 +397,6 @@ func onlyDataResources(sm tfjson.StateModule) tfjson.StateModule {
|
|||||||
return filtered
|
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.
|
// parsePlan must only be called while the lock is held.
|
||||||
func (e *executor) parsePlan(ctx, killCtx context.Context, planfilePath string) (*tfjson.Plan, error) {
|
func (e *executor) parsePlan(ctx, killCtx context.Context, planfilePath string) (*tfjson.Plan, error) {
|
||||||
ctx, span := e.server.startTrace(ctx, tracing.FuncName())
|
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?
|
// TODO: When the plan is present, we should probably use it?
|
||||||
// "-plan=" + e.files.PlanFilePath(),
|
// "-plan=" + e.files.PlanFilePath(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if ver.GreaterThanOrEqual(version170) {
|
if ver.GreaterThanOrEqual(version170) {
|
||||||
args = append(args, "-type=plan")
|
args = append(args, "-type=plan")
|
||||||
}
|
}
|
||||||
|
|
||||||
var out strings.Builder
|
var out strings.Builder
|
||||||
cmd := exec.CommandContext(killCtx, e.binaryPath, args...) // #nosec
|
cmd := exec.CommandContext(killCtx, e.binaryPath, args...) // #nosec
|
||||||
cmd.Stdout = &out
|
cmd.Stdout = &out
|
||||||
@@ -602,11 +547,6 @@ func (e *executor) apply(
|
|||||||
return nil, xerrors.Errorf("terraform apply: %w", err)
|
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()
|
statefilePath := e.files.StateFilePath()
|
||||||
stateContent, err := os.ReadFile(statefilePath)
|
stateContent, err := os.ReadFile(statefilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -614,41 +554,10 @@ func (e *executor) apply(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &proto.ApplyComplete{
|
return &proto.ApplyComplete{
|
||||||
Parameters: state.Parameters,
|
State: stateContent,
|
||||||
Resources: state.Resources,
|
|
||||||
ExternalAuthProviders: state.ExternalAuthProviders,
|
|
||||||
State: stateContent,
|
|
||||||
AiTasks: state.AITasks,
|
|
||||||
}, nil
|
}, 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.
|
// state must only be called while the lock is held.
|
||||||
func (e *executor) state(ctx, killCtx context.Context) (*tfjson.State, error) {
|
func (e *executor) state(ctx, killCtx context.Context) (*tfjson.State, error) {
|
||||||
ctx, span := e.server.startTrace(ctx, tracing.FuncName())
|
ctx, span := e.server.startTrace(ctx, tracing.FuncName())
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ package terraform_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -15,14 +17,19 @@ import (
|
|||||||
func TestParse(t *testing.T) {
|
func TestParse(t *testing.T) {
|
||||||
t.Parallel()
|
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 {
|
testCases := []struct {
|
||||||
Name string
|
Name string
|
||||||
Files map[string]string
|
Files map[string]string
|
||||||
Response *proto.ParseComplete
|
Response *proto.ParseComplete
|
||||||
// If ErrorContains is not empty, then the ParseComplete should have an Error containing the given string
|
ParseErrorContains string
|
||||||
ErrorContains string
|
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
Name: "single-variable",
|
Name: "single-variable",
|
||||||
@@ -63,6 +70,7 @@ func TestParse(t *testing.T) {
|
|||||||
"main.tf": `variable "A" {
|
"main.tf": `variable "A" {
|
||||||
validation {
|
validation {
|
||||||
condition = var.A == "value"
|
condition = var.A == "value"
|
||||||
|
error_message = "A must be 'value'"
|
||||||
}
|
}
|
||||||
}`,
|
}`,
|
||||||
},
|
},
|
||||||
@@ -80,7 +88,7 @@ func TestParse(t *testing.T) {
|
|||||||
Files: map[string]string{
|
Files: map[string]string{
|
||||||
"main.tf": "a;sd;ajsd;lajsd;lasjdf;a",
|
"main.tf": "a;sd;ajsd;lajsd;lasjdf;a",
|
||||||
},
|
},
|
||||||
ErrorContains: `The ";" character is not valid.`,
|
ParseErrorContains: `The ";" character is not valid.`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "multiple-variables",
|
Name: "multiple-variables",
|
||||||
@@ -205,6 +213,8 @@ func TestParse(t *testing.T) {
|
|||||||
{
|
{
|
||||||
Name: "workspace-tags",
|
Name: "workspace-tags",
|
||||||
Files: map[string]string{
|
Files: map[string]string{
|
||||||
|
`main.tf`: `
|
||||||
|
`,
|
||||||
"parameters.tf": `data "coder_parameter" "os_selector" {
|
"parameters.tf": `data "coder_parameter" "os_selector" {
|
||||||
name = "os_selector"
|
name = "os_selector"
|
||||||
display_name = "Operating System"
|
display_name = "Operating System"
|
||||||
@@ -266,7 +276,6 @@ func TestParse(t *testing.T) {
|
|||||||
Name: "workspace-tags-in-a-single-file",
|
Name: "workspace-tags-in-a-single-file",
|
||||||
Files: map[string]string{
|
Files: map[string]string{
|
||||||
"main.tf": `
|
"main.tf": `
|
||||||
|
|
||||||
data "coder_parameter" "os_selector" {
|
data "coder_parameter" "os_selector" {
|
||||||
name = "os_selector"
|
name = "os_selector"
|
||||||
display_name = "Operating System"
|
display_name = "Operating System"
|
||||||
@@ -330,7 +339,6 @@ func TestParse(t *testing.T) {
|
|||||||
Name: "workspace-tags-duplicate-tag",
|
Name: "workspace-tags-duplicate-tag",
|
||||||
Files: map[string]string{
|
Files: map[string]string{
|
||||||
"main.tf": `
|
"main.tf": `
|
||||||
|
|
||||||
data "coder_workspace_tags" "custom_workspace_tags" {
|
data "coder_workspace_tags" "custom_workspace_tags" {
|
||||||
tags = {
|
tags = {
|
||||||
"cluster" = "developers"
|
"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",
|
Name: "workspace-tags-wrong-tag-format",
|
||||||
Files: map[string]string{
|
Files: map[string]string{
|
||||||
"main.tf": `
|
"main.tf": `
|
||||||
|
data "coder_workspace_tags" "custom_workspace_tags" {
|
||||||
data "coder_workspace_tags" "custom_workspace_tags" {
|
tags {
|
||||||
tags {
|
cluster = "developers"
|
||||||
cluster = "developers"
|
debug = "yes"
|
||||||
debug = "yes"
|
cache = "no-cache"
|
||||||
cache = "no-cache"
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
ErrorContains: `"tags" attribute is required by coder_workspace_tags`,
|
ParseErrorContains: `"tags" attribute is required by coder_workspace_tags`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "empty-main",
|
Name: "empty-main",
|
||||||
@@ -379,27 +386,38 @@ func TestParse(t *testing.T) {
|
|||||||
t.Run(testCase.Name, func(t *testing.T) {
|
t.Run(testCase.Name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
session := configure(ctx, t, api, &proto.Config{
|
session := configure(ctx, t, api, &proto.Config{})
|
||||||
TemplateSourceArchive: testutil.CreateTar(t, testCase.Files),
|
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)
|
require.NoError(t, err)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
msg, err := session.Recv()
|
msg, err := session.Recv()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
if testCase.ErrorContains != "" {
|
|
||||||
require.Contains(t, msg.GetParse().GetError(), testCase.ErrorContains)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore logs in this test
|
// Ignore logs in this test
|
||||||
if msg.GetLog() != nil {
|
if msg.GetLog() != nil {
|
||||||
continue
|
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!
|
// Ensure the want and got are equivalent!
|
||||||
want, err := json.Marshal(testCase.Response)
|
want, err := json.Marshal(testCase.Response)
|
||||||
require.NoError(t, err)
|
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"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
tfjson "github.com/hashicorp/terraform-json"
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
@@ -67,51 +68,34 @@ func (s *server) setupContexts(parent context.Context, canceledOrComplete <-chan
|
|||||||
return ctx, cancel, killCtx, kill
|
return ctx, cancel, killCtx, kill
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) Plan(
|
func (s *server) Init(
|
||||||
sess *provisionersdk.Session, request *proto.PlanRequest, canceledOrComplete <-chan struct{},
|
sess *provisionersdk.Session, request *proto.InitRequest, canceledOrComplete <-chan struct{},
|
||||||
) *proto.PlanComplete {
|
) *proto.InitComplete {
|
||||||
ctx, span := s.startTrace(sess.Context(), tracing.FuncName())
|
ctx, span := s.startTrace(sess.Context(), tracing.FuncName())
|
||||||
defer span.End()
|
defer span.End()
|
||||||
ctx, cancel, killCtx, kill := s.setupContexts(ctx, canceledOrComplete)
|
ctx, cancel, killCtx, kill := s.setupContexts(ctx, canceledOrComplete)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
defer kill()
|
defer kill()
|
||||||
|
|
||||||
e := s.executor(sess.Files, database.ProvisionerJobTimingStagePlan)
|
e := s.executor(sess.Files, database.ProvisionerJobTimingStageInit)
|
||||||
if err := e.checkMinVersion(ctx); err != nil {
|
if err := e.checkMinVersion(ctx); err != nil {
|
||||||
return provisionersdk.PlanErrorf("%s", err.Error())
|
return provisionersdk.InitErrorf("%s", err.Error())
|
||||||
}
|
}
|
||||||
logTerraformEnvVars(sess)
|
logTerraformEnvVars(sess)
|
||||||
|
|
||||||
// If we're destroying, exit early if there's no state. This is necessary to
|
// TODO: These logs should probably be streamed back to the provisioner runner.
|
||||||
// avoid any cases where a workspace is "locked out" of terraform due to
|
err := sess.Files.ExtractArchive(ctx, s.logger, afero.NewOsFs(), request.GetTemplateSourceArchive())
|
||||||
// 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)
|
|
||||||
if err != nil {
|
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")
|
err = CleanStaleTerraformPlugins(sess.Context(), s.cachePath, afero.NewOsFs(), time.Now(), s.logger)
|
||||||
|
if err != nil {
|
||||||
// The JSON output of `terraform init` doesn't include discrete fields for capturing timings of each plugin,
|
return provisionersdk.InitErrorf("unable to clean stale Terraform plugins: %s", err)
|
||||||
// so we capture the whole init process.
|
}
|
||||||
initTimings := newTimingAggregator(database.ProvisionerJobTimingStageInit)
|
|
||||||
endStage := initTimings.startStage(database.ProvisionerJobTimingStageInit)
|
|
||||||
|
|
||||||
|
s.logger.Debug(ctx, "running terraform initialization")
|
||||||
|
endStage := e.timings.startStage(database.ProvisionerJobTimingStageInit)
|
||||||
err = e.init(ctx, killCtx, sess)
|
err = e.init(ctx, killCtx, sess)
|
||||||
endStage(err)
|
endStage(err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -137,7 +121,7 @@ func (s *server) Plan(
|
|||||||
slog.F("provider_coder_stacktrace", stacktrace),
|
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)
|
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))
|
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")
|
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)
|
env, err := provisionEnv(sess.Config, request.Metadata, request.PreviousParameterValues, request.RichParameterValues, request.ExternalAuthProviders)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return provisionersdk.PlanErrorf("setup env: %s", err)
|
return provisionersdk.PlanErrorf("setup env: %s", err)
|
||||||
@@ -160,20 +197,78 @@ func (s *server) Plan(
|
|||||||
return provisionersdk.PlanErrorf("plan vars: %s", err)
|
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)
|
resp, err := e.plan(ctx, killCtx, env, vars, sess, request)
|
||||||
endPlanStage(err)
|
endStage(err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return provisionersdk.PlanErrorf("%s", err.Error())
|
return provisionersdk.PlanErrorf("%s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepend init timings since they occur prior to plan timings.
|
resp.Timings = e.timings.aggregate()
|
||||||
// Order is irrelevant; this is merely indicative.
|
|
||||||
resp.Timings = append(resp.Timings, append(initTimings.aggregate(), e.timings.aggregate()...)...)
|
|
||||||
resp.Modules = modules
|
|
||||||
return resp
|
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(
|
func (s *server) Apply(
|
||||||
sess *provisionersdk.Session, request *proto.ApplyRequest, canceledOrComplete <-chan struct{},
|
sess *provisionersdk.Session, request *proto.ApplyRequest, canceledOrComplete <-chan struct{},
|
||||||
) *proto.ApplyComplete {
|
) *proto.ApplyComplete {
|
||||||
@@ -194,7 +289,7 @@ func (s *server) Apply(
|
|||||||
// e.g. bad template param values and cannot be deleted. This is just for
|
// 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
|
// contingency, in the future we will try harder to prevent workspaces being
|
||||||
// broken this hard.
|
// 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")
|
sess.ProvisionLog(proto.LogLevel_INFO, "The terraform plan does not exist, there is nothing to do")
|
||||||
return &proto.ApplyComplete{}
|
return &proto.ApplyComplete{}
|
||||||
}
|
}
|
||||||
@@ -217,8 +312,9 @@ func (s *server) Apply(
|
|||||||
// In this case, we return Complete with an explicit error message.
|
// In this case, we return Complete with an explicit error message.
|
||||||
stateData, _ := os.ReadFile(statefilePath)
|
stateData, _ := os.ReadFile(statefilePath)
|
||||||
return &proto.ApplyComplete{
|
return &proto.ApplyComplete{
|
||||||
State: stateData,
|
State: stateData,
|
||||||
Error: errorMessage,
|
Error: errorMessage,
|
||||||
|
Timings: e.timings.aggregate(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resp.Timings = e.timings.aggregate()
|
resp.Timings = e.timings.aggregate()
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -81,6 +82,27 @@ func setupProvisioner(t *testing.T, opts *provisionerServeOptions) (context.Cont
|
|||||||
return ctx, api
|
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 {
|
func configure(ctx context.Context, t *testing.T, client proto.DRPCProvisionerClient, config *proto.Config) proto.DRPCProvisioner_SessionClient {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
sess, err := client.Session(ctx)
|
sess, err := client.Session(ctx)
|
||||||
@@ -107,6 +129,12 @@ func readProvisionLog(t *testing.T, response proto.DRPCProvisioner_SessionClient
|
|||||||
return logBuf.String()
|
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 {
|
func sendPlan(sess proto.DRPCProvisioner_SessionClient, transition proto.WorkspaceTransition) error {
|
||||||
return sess.Send(&proto.Request{Type: &proto.Request_Plan{Plan: &proto.PlanRequest{
|
return sess.Send(&proto.Request{Type: &proto.Request_Plan{Plan: &proto.PlanRequest{
|
||||||
Metadata: &proto.Metadata{WorkspaceTransition: transition},
|
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
|
// 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"
|
// one process tries to do this simultaneously, it can cause "text file busy"
|
||||||
// nolint: paralleltest
|
// nolint: paralleltest
|
||||||
@@ -161,30 +195,46 @@ func TestProvision_Cancel(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
t.Logf("wrote fake terraform script to %s", binPath)
|
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{
|
ctx, api := setupProvisioner(t, &provisionerServeOptions{
|
||||||
binaryPath: binPath,
|
binaryPath: binPath,
|
||||||
|
logger: &logger,
|
||||||
})
|
})
|
||||||
sess := configure(ctx, t, api, &proto.Config{
|
sess := configure(ctx, t, api, &proto.Config{})
|
||||||
TemplateSourceArchive: testutil.CreateTar(t, nil),
|
|
||||||
})
|
|
||||||
|
|
||||||
err = sendPlan(sess, proto.WorkspaceTransition_START)
|
err = sendInit(sess, testutil.CreateTar(t, nil))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var planOnce sync.Once
|
||||||
|
|
||||||
for _, line := range tt.startSequence {
|
for _, line := range tt.startSequence {
|
||||||
LoopStart:
|
LoopStart:
|
||||||
msg, err := sess.Recv()
|
msg, err := sess.Recv()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
t.Log(msg.Type)
|
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()
|
log := msg.GetLog()
|
||||||
if log == nil {
|
if log == nil {
|
||||||
goto LoopStart
|
goto LoopStart
|
||||||
}
|
}
|
||||||
|
|
||||||
require.Equal(t, line, log.Output)
|
require.Equal(t, line, log.Output)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t.Log("Sending the cancel request")
|
||||||
err = sess.Send(&proto.Request{
|
err = sess.Send(&proto.Request{
|
||||||
Type: &proto.Request_Cancel{
|
Type: &proto.Request_Cancel{
|
||||||
Cancel: &proto.CancelRequest{},
|
Cancel: &proto.CancelRequest{},
|
||||||
@@ -199,10 +249,14 @@ func TestProvision_Cancel(t *testing.T) {
|
|||||||
|
|
||||||
if log := msg.GetLog(); log != nil {
|
if log := msg.GetLog(); log != nil {
|
||||||
gotLog = append(gotLog, log.Output)
|
gotLog = append(gotLog, log.Output)
|
||||||
}
|
} else if c := msg.GetPlan(); c != nil {
|
||||||
if c := msg.GetPlan(); c != nil {
|
|
||||||
require.Contains(t, c.Error, "exit status 1")
|
require.Contains(t, c.Error, "exit status 1")
|
||||||
break
|
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)
|
require.Equal(t, tt.wantLog, gotLog)
|
||||||
@@ -231,15 +285,14 @@ func TestProvision_CancelTimeout(t *testing.T) {
|
|||||||
exitTimeout: time.Second,
|
exitTimeout: time.Second,
|
||||||
})
|
})
|
||||||
|
|
||||||
sess := configure(ctx, t, api, &proto.Config{
|
sess := configure(ctx, t, api, &proto.Config{})
|
||||||
TemplateSourceArchive: testutil.CreateTar(t, nil),
|
sendInitAndGetResp(t, sess, testutil.CreateTar(t, nil))
|
||||||
})
|
|
||||||
|
|
||||||
// provisioner requires plan before apply, so test cancel with plan.
|
// provisioner requires plan before apply, so test cancel with plan.
|
||||||
err = sendPlan(sess, proto.WorkspaceTransition_START)
|
err = sendPlan(sess, proto.WorkspaceTransition_START)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
for _, line := range []string{"init", "plan_start"} {
|
for _, line := range []string{"plan_start"} {
|
||||||
LoopStart:
|
LoopStart:
|
||||||
msg, err := sess.Recv()
|
msg, err := sess.Recv()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -316,11 +369,9 @@ func TestProvision_TextFileBusy(t *testing.T) {
|
|||||||
logger: &logger,
|
logger: &logger,
|
||||||
})
|
})
|
||||||
|
|
||||||
sess := configure(ctx, t, api, &proto.Config{
|
sess := configure(ctx, t, api, &proto.Config{})
|
||||||
TemplateSourceArchive: testutil.CreateTar(t, nil),
|
|
||||||
})
|
|
||||||
|
|
||||||
err = sendPlan(sess, proto.WorkspaceTransition_START)
|
err = sendInit(sess, testutil.CreateTar(t, nil))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
found := false
|
found := false
|
||||||
@@ -328,7 +379,7 @@ func TestProvision_TextFileBusy(t *testing.T) {
|
|||||||
msg, err := sess.Recv()
|
msg, err := sess.Recv()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
if c := msg.GetPlan(); c != nil {
|
if c := msg.GetInit(); c != nil {
|
||||||
require.Contains(t, c.Error, "exit status 1")
|
require.Contains(t, c.Error, "exit status 1")
|
||||||
found = true
|
found = true
|
||||||
break
|
break
|
||||||
@@ -347,11 +398,14 @@ func TestProvision(t *testing.T) {
|
|||||||
Metadata *proto.Metadata
|
Metadata *proto.Metadata
|
||||||
Request *proto.PlanRequest
|
Request *proto.PlanRequest
|
||||||
// Response may be nil to not check the response.
|
// 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
|
// If ErrorContains is not empty, PlanComplete should have an Error containing the given string
|
||||||
ErrorContains string
|
PlanErrorContains string
|
||||||
// If ExpectLogContains is not empty, then the logs should contain it.
|
// If PlanExpectLogContains is not empty, then the logs should contain it.
|
||||||
ExpectLogContains string
|
PlanExpectLogContains string
|
||||||
// If Apply is true, then send an Apply request and check we get the same Resources as in Response.
|
// If Apply is true, then send an Apply request and check we get the same Resources as in Response.
|
||||||
Apply bool
|
Apply bool
|
||||||
// Some tests may need to be skipped until the relevant provider version is released.
|
// 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" {
|
"main.tf": `variable "A" {
|
||||||
}`,
|
}`,
|
||||||
},
|
},
|
||||||
ErrorContains: "terraform plan:",
|
PlanErrorContains: "terraform plan:",
|
||||||
ExpectLogContains: "No value for required variable",
|
PlanExpectLogContains: "No value for required variable",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "missing-variable-dry-run",
|
Name: "missing-variable-dry-run",
|
||||||
@@ -374,15 +428,15 @@ func TestProvision(t *testing.T) {
|
|||||||
"main.tf": `variable "A" {
|
"main.tf": `variable "A" {
|
||||||
}`,
|
}`,
|
||||||
},
|
},
|
||||||
ErrorContains: "terraform plan:",
|
PlanErrorContains: "terraform plan:",
|
||||||
ExpectLogContains: "No value for required variable",
|
PlanExpectLogContains: "No value for required variable",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "single-resource-dry-run",
|
Name: "single-resource-dry-run",
|
||||||
Files: map[string]string{
|
Files: map[string]string{
|
||||||
"main.tf": `resource "null_resource" "A" {}`,
|
"main.tf": `resource "null_resource" "A" {}`,
|
||||||
},
|
},
|
||||||
Response: &proto.PlanComplete{
|
Response: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "A",
|
Name: "A",
|
||||||
Type: "null_resource",
|
Type: "null_resource",
|
||||||
@@ -394,7 +448,7 @@ func TestProvision(t *testing.T) {
|
|||||||
Files: map[string]string{
|
Files: map[string]string{
|
||||||
"main.tf": `resource "null_resource" "A" {}`,
|
"main.tf": `resource "null_resource" "A" {}`,
|
||||||
},
|
},
|
||||||
Response: &proto.PlanComplete{
|
Response: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "A",
|
Name: "A",
|
||||||
Type: "null_resource",
|
Type: "null_resource",
|
||||||
@@ -415,7 +469,7 @@ func TestProvision(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}`,
|
}`,
|
||||||
},
|
},
|
||||||
Response: &proto.PlanComplete{
|
Response: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "A",
|
Name: "A",
|
||||||
Type: "null_resource",
|
Type: "null_resource",
|
||||||
@@ -428,18 +482,18 @@ func TestProvision(t *testing.T) {
|
|||||||
Files: map[string]string{
|
Files: map[string]string{
|
||||||
"main.tf": `a`,
|
"main.tf": `a`,
|
||||||
},
|
},
|
||||||
ErrorContains: "initialize terraform",
|
InitErrorContains: "initialize terraform",
|
||||||
ExpectLogContains: "Argument or block definition required",
|
InitExpectLogContains: "Argument or block definition required",
|
||||||
SkipCacheProviders: true,
|
SkipCacheProviders: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "bad-syntax-2",
|
Name: "bad-syntax-2",
|
||||||
Files: map[string]string{
|
Files: map[string]string{
|
||||||
"main.tf": `;asdf;`,
|
"main.tf": `;asdf;`,
|
||||||
},
|
},
|
||||||
ErrorContains: "initialize terraform",
|
InitErrorContains: "initialize terraform",
|
||||||
ExpectLogContains: `The ";" character is not valid.`,
|
InitExpectLogContains: `The ";" character is not valid.`,
|
||||||
SkipCacheProviders: true,
|
SkipCacheProviders: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "destroy-no-state",
|
Name: "destroy-no-state",
|
||||||
@@ -449,7 +503,7 @@ func TestProvision(t *testing.T) {
|
|||||||
Metadata: &proto.Metadata{
|
Metadata: &proto.Metadata{
|
||||||
WorkspaceTransition: proto.WorkspaceTransition_DESTROY,
|
WorkspaceTransition: proto.WorkspaceTransition_DESTROY,
|
||||||
},
|
},
|
||||||
ExpectLogContains: "nothing to do",
|
PlanExpectLogContains: "nothing to do",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "rich-parameter-with-value",
|
Name: "rich-parameter-with-value",
|
||||||
@@ -493,7 +547,7 @@ func TestProvision(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Response: &proto.PlanComplete{
|
Response: &proto.GraphComplete{
|
||||||
Parameters: []*proto.RichParameter{
|
Parameters: []*proto.RichParameter{
|
||||||
{
|
{
|
||||||
Name: "Example",
|
Name: "Example",
|
||||||
@@ -571,7 +625,7 @@ func TestProvision(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Response: &proto.PlanComplete{
|
Response: &proto.GraphComplete{
|
||||||
Parameters: []*proto.RichParameter{
|
Parameters: []*proto.RichParameter{
|
||||||
{
|
{
|
||||||
Name: "Example",
|
Name: "Example",
|
||||||
@@ -623,7 +677,7 @@ func TestProvision(t *testing.T) {
|
|||||||
AccessToken: "some-value",
|
AccessToken: "some-value",
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
Response: &proto.PlanComplete{
|
Response: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "example",
|
Name: "example",
|
||||||
Type: "null_resource",
|
Type: "null_resource",
|
||||||
@@ -666,7 +720,7 @@ func TestProvision(t *testing.T) {
|
|||||||
WorkspaceOwnerSshPrivateKey: "fake private key",
|
WorkspaceOwnerSshPrivateKey: "fake private key",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Response: &proto.PlanComplete{
|
Response: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "example",
|
Name: "example",
|
||||||
Type: "null_resource",
|
Type: "null_resource",
|
||||||
@@ -709,7 +763,7 @@ func TestProvision(t *testing.T) {
|
|||||||
WorkspaceOwnerLoginType: "github",
|
WorkspaceOwnerLoginType: "github",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Response: &proto.PlanComplete{
|
Response: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "example",
|
Name: "example",
|
||||||
Type: "null_resource",
|
Type: "null_resource",
|
||||||
@@ -738,16 +792,7 @@ func TestProvision(t *testing.T) {
|
|||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
Request: &proto.PlanRequest{},
|
Request: &proto.PlanRequest{},
|
||||||
Response: &proto.PlanComplete{
|
InitResponse: &proto.InitComplete{
|
||||||
Resources: []*proto.Resource{{
|
|
||||||
Name: "example",
|
|
||||||
Type: "null_resource",
|
|
||||||
ModulePath: "module.hello",
|
|
||||||
}, {
|
|
||||||
Name: "inner_example",
|
|
||||||
Type: "null_resource",
|
|
||||||
ModulePath: "module.hello.module.there",
|
|
||||||
}},
|
|
||||||
Modules: []*proto.Module{{
|
Modules: []*proto.Module{{
|
||||||
Key: "hello",
|
Key: "hello",
|
||||||
Version: "",
|
Version: "",
|
||||||
@@ -758,6 +803,17 @@ func TestProvision(t *testing.T) {
|
|||||||
Source: "./inner_module",
|
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",
|
Name: "workspace-owner-rbac-roles",
|
||||||
@@ -792,7 +848,7 @@ func TestProvision(t *testing.T) {
|
|||||||
WorkspaceOwnerRbacRoles: []*proto.Role{{Name: "member", OrgId: ""}},
|
WorkspaceOwnerRbacRoles: []*proto.Role{{Name: "member", OrgId: ""}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Response: &proto.PlanComplete{
|
Response: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "example",
|
Name: "example",
|
||||||
Type: "null_resource",
|
Type: "null_resource",
|
||||||
@@ -833,7 +889,7 @@ func TestProvision(t *testing.T) {
|
|||||||
PrebuiltWorkspaceBuildStage: proto.PrebuiltWorkspaceBuildStage_CREATE,
|
PrebuiltWorkspaceBuildStage: proto.PrebuiltWorkspaceBuildStage_CREATE,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Response: &proto.PlanComplete{
|
Response: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "example",
|
Name: "example",
|
||||||
Type: "null_resource",
|
Type: "null_resource",
|
||||||
@@ -871,7 +927,7 @@ func TestProvision(t *testing.T) {
|
|||||||
PrebuiltWorkspaceBuildStage: proto.PrebuiltWorkspaceBuildStage_CLAIM,
|
PrebuiltWorkspaceBuildStage: proto.PrebuiltWorkspaceBuildStage_CLAIM,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Response: &proto.PlanComplete{
|
Response: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "example",
|
Name: "example",
|
||||||
Type: "null_resource",
|
Type: "null_resource",
|
||||||
@@ -910,7 +966,7 @@ func TestProvision(t *testing.T) {
|
|||||||
`, provider.TaskPromptParameterName),
|
`, provider.TaskPromptParameterName),
|
||||||
},
|
},
|
||||||
Request: &proto.PlanRequest{},
|
Request: &proto.PlanRequest{},
|
||||||
Response: &proto.PlanComplete{
|
Response: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{
|
Resources: []*proto.Resource{
|
||||||
{
|
{
|
||||||
Name: "a",
|
Name: "a",
|
||||||
@@ -962,7 +1018,7 @@ func TestProvision(t *testing.T) {
|
|||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
Response: &proto.PlanComplete{
|
Response: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "example",
|
Name: "example",
|
||||||
Type: "coder_external_agent",
|
Type: "coder_external_agent",
|
||||||
@@ -987,7 +1043,7 @@ func TestProvision(t *testing.T) {
|
|||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
Response: &proto.PlanComplete{
|
Response: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{
|
Resources: []*proto.Resource{
|
||||||
{
|
{
|
||||||
Name: "my-task",
|
Name: "my-task",
|
||||||
@@ -1004,6 +1060,14 @@ func TestProvision(t *testing.T) {
|
|||||||
},
|
},
|
||||||
SkipCacheProviders: true,
|
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.
|
// Remove unused cache dirs before running tests.
|
||||||
@@ -1043,9 +1107,18 @@ func TestProvision(t *testing.T) {
|
|||||||
ctx, api := setupProvisioner(t, &provisionerServeOptions{
|
ctx, api := setupProvisioner(t, &provisionerServeOptions{
|
||||||
cliConfigPath: cliConfigPath,
|
cliConfigPath: cliConfigPath,
|
||||||
})
|
})
|
||||||
sess := configure(ctx, t, api, &proto.Config{
|
sess := configure(ctx, t, api, &proto.Config{})
|
||||||
TemplateSourceArchive: testutil.CreateTar(t, testCase.Files),
|
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{
|
planRequest := &proto.Request{Type: &proto.Request_Plan{Plan: &proto.PlanRequest{
|
||||||
Metadata: testCase.Metadata,
|
Metadata: testCase.Metadata,
|
||||||
@@ -1054,7 +1127,7 @@ func TestProvision(t *testing.T) {
|
|||||||
planRequest = &proto.Request{Type: &proto.Request_Plan{Plan: testCase.Request}}
|
planRequest = &proto.Request{Type: &proto.Request_Plan{Plan: testCase.Request}}
|
||||||
}
|
}
|
||||||
|
|
||||||
gotExpectedLog := testCase.ExpectLogContains == ""
|
gotExpectedLog := testCase.PlanExpectLogContains == ""
|
||||||
|
|
||||||
provision := func(req *proto.Request) *proto.Response {
|
provision := func(req *proto.Request) *proto.Response {
|
||||||
err := sess.Send(req)
|
err := sess.Send(req)
|
||||||
@@ -1063,7 +1136,7 @@ func TestProvision(t *testing.T) {
|
|||||||
msg, err := sess.Recv()
|
msg, err := sess.Recv()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
if msg.GetLog() != nil {
|
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
|
gotExpectedLog = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1078,35 +1151,43 @@ func TestProvision(t *testing.T) {
|
|||||||
planComplete := resp.GetPlan()
|
planComplete := resp.GetPlan()
|
||||||
require.NotNil(t, planComplete)
|
require.NotNil(t, planComplete)
|
||||||
|
|
||||||
if testCase.ErrorContains != "" {
|
if testCase.PlanErrorContains != "" {
|
||||||
require.Contains(t, planComplete.GetError(), testCase.ErrorContains)
|
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 {
|
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.
|
// Remove randomly generated data and sort by name.
|
||||||
normalizeResources(planComplete.Resources)
|
normalizeResources(graphComplete.Resources)
|
||||||
resourcesGot, err := json.Marshal(planComplete.Resources)
|
resourcesGot, err := json.Marshal(graphComplete.Resources)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
resourcesWant, err := json.Marshal(testCase.Response.Resources)
|
resourcesWant, err := json.Marshal(testCase.Response.Resources)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, string(resourcesWant), string(resourcesGot))
|
require.Equal(t, string(resourcesWant), string(resourcesGot))
|
||||||
|
|
||||||
parametersGot, err := json.Marshal(planComplete.Parameters)
|
parametersGot, err := json.Marshal(graphComplete.Parameters)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
parametersWant, err := json.Marshal(testCase.Response.Parameters)
|
parametersWant, err := json.Marshal(testCase.Response.Parameters)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, string(parametersWant), string(parametersGot))
|
require.Equal(t, string(parametersWant), string(parametersGot))
|
||||||
|
|
||||||
modulesGot, err := json.Marshal(planComplete.Modules)
|
modulesGot, err := json.Marshal(initComplete.Modules)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
modulesWant, err := json.Marshal(testCase.Response.Modules)
|
if testCase.InitResponse != nil {
|
||||||
require.NoError(t, err)
|
modulesWant, err := json.Marshal(testCase.InitResponse.Modules)
|
||||||
require.Equal(t, string(modulesWant), string(modulesGot))
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, string(modulesWant), string(modulesGot))
|
||||||
|
}
|
||||||
|
|
||||||
require.Equal(t, planComplete.HasAiTasks, testCase.Response.HasAiTasks)
|
require.Equal(t, graphComplete.HasAiTasks, testCase.Response.HasAiTasks)
|
||||||
require.Equal(t, planComplete.HasExternalAgents, testCase.Response.HasExternalAgents)
|
require.Equal(t, graphComplete.HasExternalAgents, testCase.Response.HasExternalAgents)
|
||||||
}
|
}
|
||||||
|
|
||||||
if testCase.Apply {
|
if testCase.Apply {
|
||||||
@@ -1117,8 +1198,8 @@ func TestProvision(t *testing.T) {
|
|||||||
require.NotNil(t, applyComplete)
|
require.NotNil(t, applyComplete)
|
||||||
|
|
||||||
if testCase.Response != nil {
|
if testCase.Response != nil {
|
||||||
normalizeResources(applyComplete.Resources)
|
normalizeResources(graphComplete.Resources)
|
||||||
resourcesGot, err := json.Marshal(applyComplete.Resources)
|
resourcesGot, err := json.Marshal(graphComplete.Resources)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
resourcesWant, err := json.Marshal(testCase.Response.Resources)
|
resourcesWant, err := json.Marshal(testCase.Response.Resources)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -1127,7 +1208,7 @@ func TestProvision(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !gotExpectedLog {
|
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)
|
t.Setenv("TF_SUPERSECRET", secretValue)
|
||||||
|
|
||||||
ctx, api := setupProvisioner(t, nil)
|
ctx, api := setupProvisioner(t, nil)
|
||||||
sess := configure(ctx, t, api, &proto.Config{
|
sess := configure(ctx, t, api, &proto.Config{})
|
||||||
TemplateSourceArchive: testutil.CreateTar(t, map[string]string{"main.tf": `resource "null_resource" "A" {}`}),
|
|
||||||
})
|
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)
|
err := sendPlan(sess, proto.WorkspaceTransition_START)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -1210,9 +1292,10 @@ func TestProvision_SafeEnv(t *testing.T) {
|
|||||||
`
|
`
|
||||||
|
|
||||||
ctx, api := setupProvisioner(t, nil)
|
ctx, api := setupProvisioner(t, nil)
|
||||||
sess := configure(ctx, t, api, &proto.Config{
|
sess := configure(ctx, t, api, &proto.Config{})
|
||||||
TemplateSourceArchive: testutil.CreateTar(t, map[string]string{"main.tf": echoResource}),
|
|
||||||
})
|
resp := sendInitAndGetResp(t, sess, testutil.CreateTar(t, map[string]string{"main.tf": echoResource}))
|
||||||
|
require.Empty(t, resp.Error)
|
||||||
|
|
||||||
err := sendPlan(sess, proto.WorkspaceTransition_START)
|
err := sendPlan(sess, proto.WorkspaceTransition_START)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -1232,15 +1315,14 @@ func TestProvision_MalformedModules(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
ctx, api := setupProvisioner(t, nil)
|
ctx, api := setupProvisioner(t, nil)
|
||||||
sess := configure(ctx, t, api, &proto.Config{
|
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_`,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
|
|
||||||
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)
|
require.NoError(t, err)
|
||||||
|
|
||||||
log := readProvisionLog(t, sess)
|
log := readProvisionLog(t, sess)
|
||||||
require.Contains(t, log, "Invalid block definition")
|
require.Contains(t, log, "Invalid block definition")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -281,12 +281,3 @@ func (e *timingSpan) toProto() *proto.Timing {
|
|||||||
State: e.state,
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -35,65 +36,66 @@ func TestTimingsFromProvision(t *testing.T) {
|
|||||||
ctx, api := setupProvisioner(t, &provisionerServeOptions{
|
ctx, api := setupProvisioner(t, &provisionerServeOptions{
|
||||||
binaryPath: fakeBin,
|
binaryPath: fakeBin,
|
||||||
})
|
})
|
||||||
sess := configure(ctx, t, api, &proto.Config{
|
sess := configure(ctx, t, api, &proto.Config{})
|
||||||
TemplateSourceArchive: testutil.CreateTar(t, nil),
|
|
||||||
})
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, testutil.WaitLong)
|
ctx, cancel := context.WithTimeout(ctx, testutil.WaitLong)
|
||||||
t.Cleanup(cancel)
|
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
|
// When: a plan is executed in the provisioner, our fake terraform will be executed and will produce a
|
||||||
// state file and some log content.
|
// state file and some log content.
|
||||||
err = sendPlan(sess, proto.WorkspaceTransition_START)
|
err = sendPlan(sess, proto.WorkspaceTransition_START)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var timings []*proto.Timing
|
handleResponse(t, "plan")
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// When: the plan has completed, let's trigger an apply.
|
// When: the plan has completed, let's trigger an apply.
|
||||||
err = sendApply(sess, proto.WorkspaceTransition_START)
|
err = sendApply(sess, proto.WorkspaceTransition_START)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
for {
|
handleResponse(t, "apply")
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
t.Fatal(ctx.Err())
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
msg, err := sess.Recv()
|
// When: the apply has completed, graph the results
|
||||||
require.NoError(t, err)
|
err = sendGraph(sess, proto.GraphSource_SOURCE_STATE)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
if log := msg.GetLog(); log != nil {
|
handleResponse(t, "graph")
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort the timings stably to keep reduce flakiness.
|
// Sort the timings stably to keep reduce flakiness.
|
||||||
terraform_internal.StableSortTimings(t, timings)
|
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.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.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"}`))
|
{"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"}`))
|
// Graphing is omitted as it is captured by the stage timing, which uses now()
|
||||||
graphTiming := graphTimings[0]
|
|
||||||
|
|
||||||
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
|
// 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.
|
// provisioner/terraform/testdata/timings-aggregation/fake-terraform.sh.
|
||||||
@@ -134,9 +145,6 @@ func TestTimingsFromProvision(t *testing.T) {
|
|||||||
case string(database.ProvisionerJobTimingStageInit):
|
case string(database.ProvisionerJobTimingStageInit):
|
||||||
require.True(t, terraform_internal.TimingsAreEqual(t, []*proto.Timing{initTimings[iCursor]}, []*proto.Timing{tim}))
|
require.True(t, terraform_internal.TimingsAreEqual(t, []*proto.Timing{initTimings[iCursor]}, []*proto.Timing{tim}))
|
||||||
iCursor++
|
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):
|
case string(database.ProvisionerJobTimingStagePlan):
|
||||||
require.True(t, terraform_internal.TimingsAreEqual(t, []*proto.Timing{planTimings[pCursor]}, []*proto.Timing{tim}))
|
require.True(t, terraform_internal.TimingsAreEqual(t, []*proto.Timing{planTimings[pCursor]}, []*proto.Timing{tim}))
|
||||||
pCursor++
|
pCursor++
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/yamux"
|
"github.com/hashicorp/yamux"
|
||||||
|
"github.com/spf13/afero"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"go.uber.org/atomic"
|
"go.uber.org/atomic"
|
||||||
@@ -131,6 +132,16 @@ func TestProvisionerd(t *testing.T) {
|
|||||||
}
|
}
|
||||||
return c
|
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()
|
closerMutex.Unlock()
|
||||||
@@ -138,47 +149,6 @@ func TestProvisionerd(t *testing.T) {
|
|||||||
require.NoError(t, closer.Close())
|
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
|
// LargePayloads sends a 3mb tar file to the provisioner. The provisioner also
|
||||||
// returns large payload messages back. The limit should be 4mb, so all
|
// returns large payload messages back. The limit should be 4mb, so all
|
||||||
// these messages should work.
|
// these messages should work.
|
||||||
@@ -227,14 +197,16 @@ func TestProvisionerd(t *testing.T) {
|
|||||||
Readme: make([]byte, largeSize),
|
Readme: make([]byte, largeSize),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
init: func(s *provisionersdk.Session, r *sdkproto.InitRequest, canceledOrComplete <-chan struct{}) *sdkproto.InitComplete {
|
||||||
|
return &sdkproto.InitComplete{}
|
||||||
|
},
|
||||||
plan: func(
|
plan: func(
|
||||||
_ *provisionersdk.Session,
|
_ *provisionersdk.Session,
|
||||||
_ *sdkproto.PlanRequest,
|
_ *sdkproto.PlanRequest,
|
||||||
_ <-chan struct{},
|
_ <-chan struct{},
|
||||||
) *sdkproto.PlanComplete {
|
) *sdkproto.PlanComplete {
|
||||||
return &sdkproto.PlanComplete{
|
return &sdkproto.PlanComplete{
|
||||||
Resources: []*sdkproto.Resource{},
|
Plan: make([]byte, largeSize),
|
||||||
Plan: make([]byte, largeSize),
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
apply: func(
|
apply: func(
|
||||||
@@ -246,6 +218,11 @@ func TestProvisionerd(t *testing.T) {
|
|||||||
State: make([]byte, largeSize),
|
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))
|
require.Condition(t, closedWithin(completeChan, testutil.WaitShort))
|
||||||
@@ -299,6 +276,9 @@ func TestProvisionerd(t *testing.T) {
|
|||||||
<-cancelOrComplete
|
<-cancelOrComplete
|
||||||
return &sdkproto.ParseComplete{}
|
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))
|
require.Condition(t, closedWithin(completeChan, testutil.WaitShort))
|
||||||
@@ -349,6 +329,7 @@ func TestProvisionerd(t *testing.T) {
|
|||||||
}), nil
|
}), nil
|
||||||
}, provisionerd.LocalProvisioners{
|
}, provisionerd.LocalProvisioners{
|
||||||
"someprovisioner": createProvisionerClient(t, done, provisionerTestServer{
|
"someprovisioner": createProvisionerClient(t, done, provisionerTestServer{
|
||||||
|
init: extractInit(t),
|
||||||
parse: func(
|
parse: func(
|
||||||
s *provisionersdk.Session,
|
s *provisionersdk.Session,
|
||||||
_ *sdkproto.ParseRequest,
|
_ *sdkproto.ParseRequest,
|
||||||
@@ -366,9 +347,7 @@ func TestProvisionerd(t *testing.T) {
|
|||||||
cancelOrComplete <-chan struct{},
|
cancelOrComplete <-chan struct{},
|
||||||
) *sdkproto.PlanComplete {
|
) *sdkproto.PlanComplete {
|
||||||
s.ProvisionLog(sdkproto.LogLevel_INFO, "hello")
|
s.ProvisionLog(sdkproto.LogLevel_INFO, "hello")
|
||||||
return &sdkproto.PlanComplete{
|
return &sdkproto.PlanComplete{}
|
||||||
Resources: []*sdkproto.Resource{},
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
apply: func(
|
apply: func(
|
||||||
_ *provisionersdk.Session,
|
_ *provisionersdk.Session,
|
||||||
@@ -378,6 +357,11 @@ func TestProvisionerd(t *testing.T) {
|
|||||||
t.Error("dry run should not apply")
|
t.Error("dry run should not apply")
|
||||||
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{},
|
||||||
|
}
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -433,14 +417,15 @@ func TestProvisionerd(t *testing.T) {
|
|||||||
}), nil
|
}), nil
|
||||||
}, provisionerd.LocalProvisioners{
|
}, provisionerd.LocalProvisioners{
|
||||||
"someprovisioner": createProvisionerClient(t, done, provisionerTestServer{
|
"someprovisioner": createProvisionerClient(t, done, provisionerTestServer{
|
||||||
|
init: func(s *provisionersdk.Session, r *sdkproto.InitRequest, canceledOrComplete <-chan struct{}) *sdkproto.InitComplete {
|
||||||
|
return &sdkproto.InitComplete{}
|
||||||
|
},
|
||||||
plan: func(
|
plan: func(
|
||||||
_ *provisionersdk.Session,
|
_ *provisionersdk.Session,
|
||||||
_ *sdkproto.PlanRequest,
|
_ *sdkproto.PlanRequest,
|
||||||
_ <-chan struct{},
|
_ <-chan struct{},
|
||||||
) *sdkproto.PlanComplete {
|
) *sdkproto.PlanComplete {
|
||||||
return &sdkproto.PlanComplete{
|
return &sdkproto.PlanComplete{}
|
||||||
Resources: []*sdkproto.Resource{},
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
apply: func(
|
apply: func(
|
||||||
_ *provisionersdk.Session,
|
_ *provisionersdk.Session,
|
||||||
@@ -450,6 +435,11 @@ func TestProvisionerd(t *testing.T) {
|
|||||||
t.Error("dry run should not apply")
|
t.Error("dry run should not apply")
|
||||||
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{},
|
||||||
|
}
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -498,6 +488,9 @@ func TestProvisionerd(t *testing.T) {
|
|||||||
}), nil
|
}), nil
|
||||||
}, provisionerd.LocalProvisioners{
|
}, provisionerd.LocalProvisioners{
|
||||||
"someprovisioner": createProvisionerClient(t, done, provisionerTestServer{
|
"someprovisioner": createProvisionerClient(t, done, provisionerTestServer{
|
||||||
|
init: func(s *provisionersdk.Session, r *sdkproto.InitRequest, canceledOrComplete <-chan struct{}) *sdkproto.InitComplete {
|
||||||
|
return &sdkproto.InitComplete{}
|
||||||
|
},
|
||||||
plan: func(
|
plan: func(
|
||||||
s *provisionersdk.Session,
|
s *provisionersdk.Session,
|
||||||
_ *sdkproto.PlanRequest,
|
_ *sdkproto.PlanRequest,
|
||||||
@@ -513,6 +506,9 @@ func TestProvisionerd(t *testing.T) {
|
|||||||
) *sdkproto.ApplyComplete {
|
) *sdkproto.ApplyComplete {
|
||||||
return &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))
|
require.Condition(t, closedWithin(acq.complete, testutil.WaitShort))
|
||||||
@@ -570,6 +566,9 @@ func TestProvisionerd(t *testing.T) {
|
|||||||
}), nil
|
}), nil
|
||||||
}, provisionerd.LocalProvisioners{
|
}, provisionerd.LocalProvisioners{
|
||||||
"someprovisioner": createProvisionerClient(t, done, provisionerTestServer{
|
"someprovisioner": createProvisionerClient(t, done, provisionerTestServer{
|
||||||
|
init: func(s *provisionersdk.Session, r *sdkproto.InitRequest, canceledOrComplete <-chan struct{}) *sdkproto.InitComplete {
|
||||||
|
return &sdkproto.InitComplete{}
|
||||||
|
},
|
||||||
plan: func(
|
plan: func(
|
||||||
s *provisionersdk.Session,
|
s *provisionersdk.Session,
|
||||||
_ *sdkproto.PlanRequest,
|
_ *sdkproto.PlanRequest,
|
||||||
@@ -577,14 +576,7 @@ func TestProvisionerd(t *testing.T) {
|
|||||||
) *sdkproto.PlanComplete {
|
) *sdkproto.PlanComplete {
|
||||||
s.ProvisionLog(sdkproto.LogLevel_DEBUG, "wow")
|
s.ProvisionLog(sdkproto.LogLevel_DEBUG, "wow")
|
||||||
return &sdkproto.PlanComplete{
|
return &sdkproto.PlanComplete{
|
||||||
Resources: []*sdkproto.Resource{
|
DailyCost: 25,
|
||||||
{
|
|
||||||
DailyCost: 10,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
DailyCost: 15,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
apply: func(
|
apply: func(
|
||||||
@@ -593,7 +585,10 @@ func TestProvisionerd(t *testing.T) {
|
|||||||
_ <-chan struct{},
|
_ <-chan struct{},
|
||||||
) *sdkproto.ApplyComplete {
|
) *sdkproto.ApplyComplete {
|
||||||
t.Error("should not apply when resources exceed quota")
|
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{
|
Resources: []*sdkproto.Resource{
|
||||||
{
|
{
|
||||||
DailyCost: 10,
|
DailyCost: 10,
|
||||||
@@ -646,6 +641,12 @@ func TestProvisionerd(t *testing.T) {
|
|||||||
}), nil
|
}), nil
|
||||||
}, provisionerd.LocalProvisioners{
|
}, provisionerd.LocalProvisioners{
|
||||||
"someprovisioner": createProvisionerClient(t, done, provisionerTestServer{
|
"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(
|
plan: func(
|
||||||
s *provisionersdk.Session,
|
s *provisionersdk.Session,
|
||||||
_ *sdkproto.PlanRequest,
|
_ *sdkproto.PlanRequest,
|
||||||
@@ -756,6 +757,9 @@ func TestProvisionerd(t *testing.T) {
|
|||||||
}), nil
|
}), nil
|
||||||
}, provisionerd.LocalProvisioners{
|
}, provisionerd.LocalProvisioners{
|
||||||
"someprovisioner": createProvisionerClient(t, done, provisionerTestServer{
|
"someprovisioner": createProvisionerClient(t, done, provisionerTestServer{
|
||||||
|
init: func(s *provisionersdk.Session, r *sdkproto.InitRequest, canceledOrComplete <-chan struct{}) *sdkproto.InitComplete {
|
||||||
|
return &sdkproto.InitComplete{}
|
||||||
|
},
|
||||||
plan: func(
|
plan: func(
|
||||||
s *provisionersdk.Session,
|
s *provisionersdk.Session,
|
||||||
_ *sdkproto.PlanRequest,
|
_ *sdkproto.PlanRequest,
|
||||||
@@ -844,6 +848,9 @@ func TestProvisionerd(t *testing.T) {
|
|||||||
}), nil
|
}), nil
|
||||||
}, provisionerd.LocalProvisioners{
|
}, provisionerd.LocalProvisioners{
|
||||||
"someprovisioner": createProvisionerClient(t, done, provisionerTestServer{
|
"someprovisioner": createProvisionerClient(t, done, provisionerTestServer{
|
||||||
|
init: func(s *provisionersdk.Session, r *sdkproto.InitRequest, canceledOrComplete <-chan struct{}) *sdkproto.InitComplete {
|
||||||
|
return &sdkproto.InitComplete{}
|
||||||
|
},
|
||||||
plan: func(
|
plan: func(
|
||||||
s *provisionersdk.Session,
|
s *provisionersdk.Session,
|
||||||
_ *sdkproto.PlanRequest,
|
_ *sdkproto.PlanRequest,
|
||||||
@@ -938,6 +945,9 @@ func TestProvisionerd(t *testing.T) {
|
|||||||
return client, nil
|
return client, nil
|
||||||
}, provisionerd.LocalProvisioners{
|
}, provisionerd.LocalProvisioners{
|
||||||
"someprovisioner": createProvisionerClient(t, done, provisionerTestServer{
|
"someprovisioner": createProvisionerClient(t, done, provisionerTestServer{
|
||||||
|
init: func(s *provisionersdk.Session, r *sdkproto.InitRequest, canceledOrComplete <-chan struct{}) *sdkproto.InitComplete {
|
||||||
|
return &sdkproto.InitComplete{}
|
||||||
|
},
|
||||||
plan: func(
|
plan: func(
|
||||||
_ *provisionersdk.Session,
|
_ *provisionersdk.Session,
|
||||||
_ *sdkproto.PlanRequest,
|
_ *sdkproto.PlanRequest,
|
||||||
@@ -1031,6 +1041,9 @@ func TestProvisionerd(t *testing.T) {
|
|||||||
return client, nil
|
return client, nil
|
||||||
}, provisionerd.LocalProvisioners{
|
}, provisionerd.LocalProvisioners{
|
||||||
"someprovisioner": createProvisionerClient(t, done, provisionerTestServer{
|
"someprovisioner": createProvisionerClient(t, done, provisionerTestServer{
|
||||||
|
init: func(s *provisionersdk.Session, r *sdkproto.InitRequest, canceledOrComplete <-chan struct{}) *sdkproto.InitComplete {
|
||||||
|
return &sdkproto.InitComplete{}
|
||||||
|
},
|
||||||
plan: func(
|
plan: func(
|
||||||
_ *provisionersdk.Session,
|
_ *provisionersdk.Session,
|
||||||
_ *sdkproto.PlanRequest,
|
_ *sdkproto.PlanRequest,
|
||||||
@@ -1045,6 +1058,9 @@ func TestProvisionerd(t *testing.T) {
|
|||||||
) *sdkproto.ApplyComplete {
|
) *sdkproto.ApplyComplete {
|
||||||
return &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))
|
require.Condition(t, closedWithin(completeChan, testutil.WaitShort))
|
||||||
@@ -1125,6 +1141,12 @@ func TestProvisionerd(t *testing.T) {
|
|||||||
}), nil
|
}), nil
|
||||||
}, provisionerd.LocalProvisioners{
|
}, provisionerd.LocalProvisioners{
|
||||||
"someprovisioner": createProvisionerClient(t, done, provisionerTestServer{
|
"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(
|
plan: func(
|
||||||
s *provisionersdk.Session,
|
s *provisionersdk.Session,
|
||||||
_ *sdkproto.PlanRequest,
|
_ *sdkproto.PlanRequest,
|
||||||
@@ -1253,9 +1275,15 @@ func createProvisionerClient(t *testing.T, done <-chan struct{}, server provisio
|
|||||||
}
|
}
|
||||||
|
|
||||||
type provisionerTestServer struct {
|
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
|
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
|
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
|
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 {
|
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)
|
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 {
|
func (p *provisionerDaemonTestServer) UploadFile(stream proto.DRPCProvisionerDaemon_UploadFileStream) error {
|
||||||
return p.uploadFile(stream)
|
return p.uploadFile(stream)
|
||||||
}
|
}
|
||||||
@@ -1359,3 +1391,16 @@ func (a *acquireOne) acquireWithCancel(stream proto.DRPCProvisionerDaemon_Acquir
|
|||||||
}
|
}
|
||||||
return nil
|
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
|
package runner
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
@@ -515,7 +514,6 @@ func (r *Runner) runTemplateImport(ctx context.Context) (*proto.CompletedJob, *p
|
|||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
failedJob := r.configure(&sdkproto.Config{
|
failedJob := r.configure(&sdkproto.Config{
|
||||||
TemplateSourceArchive: r.job.GetTemplateSourceArchive(),
|
|
||||||
TemplateId: strings2.EmptyToNil(r.job.GetTemplateImport().Metadata.TemplateId),
|
TemplateId: strings2.EmptyToNil(r.job.GetTemplateImport().Metadata.TemplateId),
|
||||||
TemplateVersionId: strings2.EmptyToNil(r.job.GetTemplateImport().Metadata.TemplateVersionId),
|
TemplateVersionId: strings2.EmptyToNil(r.job.GetTemplateImport().Metadata.TemplateVersionId),
|
||||||
ExpReuseTerraformWorkspace: ptr.Ref(false),
|
ExpReuseTerraformWorkspace: ptr.Ref(false),
|
||||||
@@ -524,6 +522,18 @@ func (r *Runner) runTemplateImport(ctx context.Context) (*proto.CompletedJob, *p
|
|||||||
return nil, failedJob
|
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
|
// Parse parameters and update the job with the parameter specs
|
||||||
r.queueLog(ctx, &proto.Log{
|
r.queueLog(ctx, &proto.Log{
|
||||||
Source: proto.LogSource_PROVISIONER_DAEMON,
|
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,
|
CoderUrl: r.job.GetTemplateImport().Metadata.CoderUrl,
|
||||||
WorkspaceOwnerGroups: r.job.GetTemplateImport().Metadata.WorkspaceOwnerGroups,
|
WorkspaceOwnerGroups: r.job.GetTemplateImport().Metadata.WorkspaceOwnerGroups,
|
||||||
WorkspaceTransition: sdkproto.WorkspaceTransition_START,
|
WorkspaceTransition: sdkproto.WorkspaceTransition_START,
|
||||||
}, false)
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, r.failedJobf("template import provision for start: %s", err)
|
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,
|
CoderUrl: r.job.GetTemplateImport().Metadata.CoderUrl,
|
||||||
WorkspaceOwnerGroups: r.job.GetTemplateImport().Metadata.WorkspaceOwnerGroups,
|
WorkspaceOwnerGroups: r.job.GetTemplateImport().Metadata.WorkspaceOwnerGroups,
|
||||||
WorkspaceTransition: sdkproto.WorkspaceTransition_STOP,
|
WorkspaceTransition: sdkproto.WorkspaceTransition_STOP,
|
||||||
}, true, // Modules downloaded on the start provision
|
})
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, r.failedJobf("template import provision for stop: %s", err)
|
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,
|
RichParameters: startProvision.Parameters,
|
||||||
ExternalAuthProvidersNames: externalAuthProviderNames,
|
ExternalAuthProvidersNames: externalAuthProviderNames,
|
||||||
ExternalAuthProviders: startProvision.ExternalAuthProviders,
|
ExternalAuthProviders: startProvision.ExternalAuthProviders,
|
||||||
StartModules: startProvision.Modules,
|
// TODO: These are defined as different, but can they be?
|
||||||
StopModules: stopProvision.Modules,
|
// Terraform downloads modules regardless of `count`, so this should be the same
|
||||||
Presets: startProvision.Presets,
|
StartModules: initResp.Modules,
|
||||||
Plan: startProvision.Plan,
|
StopModules: initResp.Modules,
|
||||||
// ModuleFiles are not on the stopProvision. So grab from the startProvision.
|
Presets: startProvision.Presets,
|
||||||
ModuleFiles: startProvision.ModuleFiles,
|
Plan: startProvision.Plan,
|
||||||
|
ModuleFiles: initResp.ModuleFiles,
|
||||||
// ModuleFileHash will be populated if the file is uploaded async
|
// ModuleFileHash will be populated if the file is uploaded async
|
||||||
ModuleFilesHash: []byte{},
|
ModuleFilesHash: []byte{},
|
||||||
HasAiTasks: startProvision.HasAITasks,
|
HasAiTasks: startProvision.HasAITasks,
|
||||||
@@ -666,10 +676,8 @@ type templateImportProvision struct {
|
|||||||
Resources []*sdkproto.Resource
|
Resources []*sdkproto.Resource
|
||||||
Parameters []*sdkproto.RichParameter
|
Parameters []*sdkproto.RichParameter
|
||||||
ExternalAuthProviders []*sdkproto.ExternalAuthProviderResource
|
ExternalAuthProviders []*sdkproto.ExternalAuthProviderResource
|
||||||
Modules []*sdkproto.Module
|
|
||||||
Presets []*sdkproto.Preset
|
Presets []*sdkproto.Preset
|
||||||
Plan json.RawMessage
|
Plan json.RawMessage
|
||||||
ModuleFiles []byte
|
|
||||||
HasAITasks bool
|
HasAITasks bool
|
||||||
HasExternalAgents bool
|
HasExternalAgents bool
|
||||||
}
|
}
|
||||||
@@ -677,8 +685,8 @@ type templateImportProvision struct {
|
|||||||
// Performs a dry-run provision when importing a template.
|
// 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.
|
// 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.
|
// 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) {
|
func (r *Runner) runTemplateImportProvision(ctx context.Context, variableValues []*sdkproto.VariableValue, metadata *sdkproto.Metadata) (*templateImportProvision, error) {
|
||||||
return r.runTemplateImportProvisionWithRichParameters(ctx, variableValues, nil, metadata, omitModules)
|
return r.runTemplateImportProvisionWithRichParameters(ctx, variableValues, nil, metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Performs a dry-run provision with provided rich parameters.
|
// Performs a dry-run provision with provided rich parameters.
|
||||||
@@ -688,7 +696,6 @@ func (r *Runner) runTemplateImportProvisionWithRichParameters(
|
|||||||
variableValues []*sdkproto.VariableValue,
|
variableValues []*sdkproto.VariableValue,
|
||||||
richParameterValues []*sdkproto.RichParameterValue,
|
richParameterValues []*sdkproto.RichParameterValue,
|
||||||
metadata *sdkproto.Metadata,
|
metadata *sdkproto.Metadata,
|
||||||
omitModules bool,
|
|
||||||
) (*templateImportProvision, error) {
|
) (*templateImportProvision, error) {
|
||||||
ctx, span := r.startTrace(ctx, tracing.FuncName())
|
ctx, span := r.startTrace(ctx, tracing.FuncName())
|
||||||
defer span.End()
|
defer span.End()
|
||||||
@@ -700,126 +707,48 @@ func (r *Runner) runTemplateImportProvisionWithRichParameters(
|
|||||||
case sdkproto.WorkspaceTransition_STOP:
|
case sdkproto.WorkspaceTransition_STOP:
|
||||||
stage = "Detecting ephemeral resources"
|
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
|
planComplete, failed := r.plan(ctx, stage, &sdkproto.PlanRequest{
|
||||||
err := r.session.Send(&sdkproto.Request{Type: &sdkproto.Request_Plan{Plan: &sdkproto.PlanRequest{
|
Metadata: metadata,
|
||||||
Metadata: metadata,
|
RichParameterValues: richParameterValues,
|
||||||
RichParameterValues: richParameterValues,
|
|
||||||
// Template import has no previous values
|
|
||||||
PreviousParameterValues: make([]*sdkproto.RichParameterValue, 0),
|
|
||||||
VariableValues: variableValues,
|
VariableValues: variableValues,
|
||||||
OmitModuleFiles: omitModules,
|
ExternalAuthProviders: nil,
|
||||||
}}})
|
PreviousParameterValues: nil,
|
||||||
if err != nil {
|
State: nil,
|
||||||
return nil, xerrors.Errorf("start provision: %w", err)
|
})
|
||||||
|
if failed != nil {
|
||||||
|
return nil, xerrors.Errorf("plan during template import provision: %w", failed)
|
||||||
}
|
}
|
||||||
nevermind := make(chan struct{})
|
if planComplete == nil {
|
||||||
defer close(nevermind)
|
return nil, xerrors.New("plan during template import provision returned nil response")
|
||||||
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.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) {
|
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()
|
metadata.WorkspaceOwnerId = id.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
failedJob := r.configure(&sdkproto.Config{
|
failedJob := r.configure(&sdkproto.Config{})
|
||||||
TemplateSourceArchive: r.job.GetTemplateSourceArchive(),
|
|
||||||
})
|
|
||||||
if failedJob != nil {
|
if failedJob != nil {
|
||||||
return nil, failedJob
|
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.
|
// Run the template import provision task since it's already a dry run.
|
||||||
provision, err := r.runTemplateImportProvisionWithRichParameters(ctx,
|
provision, err := r.runTemplateImportProvisionWithRichParameters(ctx,
|
||||||
r.job.GetTemplateDryRun().GetVariableValues(),
|
r.job.GetTemplateDryRun().GetVariableValues(),
|
||||||
r.job.GetTemplateDryRun().GetRichParameterValues(),
|
r.job.GetTemplateDryRun().GetRichParameterValues(),
|
||||||
metadata,
|
metadata,
|
||||||
false,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, r.failedJobf("run dry-run provision job: %s", err)
|
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_{
|
Type: &proto.CompletedJob_TemplateDryRun_{
|
||||||
TemplateDryRun: &proto.CompletedJob_TemplateDryRun{
|
TemplateDryRun: &proto.CompletedJob_TemplateDryRun{
|
||||||
Resources: provision.Resources,
|
Resources: provision.Resources,
|
||||||
Modules: provision.Modules,
|
Modules: initResp.Modules,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Runner) buildWorkspace(ctx context.Context, stage string, req *sdkproto.Request) (
|
func (r *Runner) commitQuota(ctx context.Context, cost int32) *proto.FailedJob {
|
||||||
*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)
|
|
||||||
r.logger.Debug(ctx, "committing quota",
|
r.logger.Debug(ctx, "committing quota",
|
||||||
slog.F("resources", resourceNames(resources)),
|
|
||||||
slog.F("cost", cost),
|
slog.F("cost", cost),
|
||||||
)
|
)
|
||||||
if cost == 0 {
|
if cost == 0 {
|
||||||
@@ -953,9 +832,8 @@ func (r *Runner) commitQuota(ctx context.Context, resources []*sdkproto.Resource
|
|||||||
const stage = "Commit quota"
|
const stage = "Commit quota"
|
||||||
|
|
||||||
resp, err := r.quotaCommitter.CommitQuota(ctx, &proto.CommitQuotaRequest{
|
resp, err := r.quotaCommitter.CommitQuota(ctx, &proto.CommitQuotaRequest{
|
||||||
JobId: r.job.JobId,
|
JobId: r.job.JobId,
|
||||||
// #nosec G115 - Safe conversion as cost is expected to be within int32 range for provisioning costs
|
DailyCost: cost,
|
||||||
DailyCost: int32(cost),
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.queueLog(ctx, &proto.Log{
|
r.queueLog(ctx, &proto.Log{
|
||||||
@@ -1014,8 +892,6 @@ func (r *Runner) runWorkspaceBuild(ctx context.Context) (*proto.CompletedJob, *p
|
|||||||
}
|
}
|
||||||
|
|
||||||
failedJob := r.configure(&sdkproto.Config{
|
failedJob := r.configure(&sdkproto.Config{
|
||||||
TemplateSourceArchive: r.job.GetTemplateSourceArchive(),
|
|
||||||
State: r.job.GetWorkspaceBuild().State,
|
|
||||||
ProvisionerLogLevel: r.job.GetWorkspaceBuild().LogLevel,
|
ProvisionerLogLevel: r.job.GetWorkspaceBuild().LogLevel,
|
||||||
TemplateId: strings2.EmptyToNil(r.job.GetWorkspaceBuild().Metadata.TemplateId),
|
TemplateId: strings2.EmptyToNil(r.job.GetWorkspaceBuild().Metadata.TemplateId),
|
||||||
TemplateVersionId: strings2.EmptyToNil(r.job.GetWorkspaceBuild().Metadata.TemplateVersionId),
|
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
|
return nil, failedJob
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, failed := r.buildWorkspace(ctx, "Planning infrastructure", &sdkproto.Request{
|
// timings collects all timings from each phase of the build
|
||||||
Type: &sdkproto.Request_Plan{
|
timings := make([]*sdkproto.Timing, 0)
|
||||||
Plan: &sdkproto.PlanRequest{
|
|
||||||
OmitModuleFiles: true, // Only useful for template imports
|
// Initialize the Terraform working directory
|
||||||
Metadata: r.job.GetWorkspaceBuild().Metadata,
|
initComplete, failedJob := r.init(ctx, true, r.job.GetTemplateSourceArchive())
|
||||||
RichParameterValues: r.job.GetWorkspaceBuild().RichParameterValues,
|
if failedJob != nil {
|
||||||
PreviousParameterValues: r.job.GetWorkspaceBuild().PreviousParameterValues,
|
return nil, failedJob
|
||||||
VariableValues: r.job.GetWorkspaceBuild().VariableValues,
|
}
|
||||||
ExternalAuthProviders: r.job.GetWorkspaceBuild().ExternalAuthProviders,
|
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 {
|
if failed != nil {
|
||||||
return nil, failed
|
return nil, failed
|
||||||
}
|
}
|
||||||
planComplete := resp.GetPlan()
|
|
||||||
if planComplete == nil {
|
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 != "" {
|
if planComplete.Error != "" {
|
||||||
r.logger.Warn(context.Background(), "plan request failed",
|
r.logger.Warn(context.Background(), "plan request failed",
|
||||||
slog.F("error", planComplete.Error),
|
slog.F("error", planComplete.Error),
|
||||||
@@ -1053,27 +957,28 @@ func (r *Runner) runWorkspaceBuild(ctx context.Context) (*proto.CompletedJob, *p
|
|||||||
JobId: r.job.JobId,
|
JobId: r.job.JobId,
|
||||||
Error: planComplete.Error,
|
Error: planComplete.Error,
|
||||||
Type: &proto.FailedJob_WorkspaceBuild_{
|
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",
|
r.logger.Info(context.Background(), "plan request successful")
|
||||||
slog.F("resource_count", len(planComplete.Resources)),
|
|
||||||
slog.F("resources", resourceNames(planComplete.Resources)),
|
|
||||||
)
|
|
||||||
r.flushQueuedLogs(ctx)
|
r.flushQueuedLogs(ctx)
|
||||||
if commitQuota {
|
if commitQuota {
|
||||||
failed = r.commitQuota(ctx, planComplete.Resources)
|
failed = r.commitQuota(ctx, planComplete.GetDailyCost())
|
||||||
r.flushQueuedLogs(ctx)
|
r.flushQueuedLogs(ctx)
|
||||||
if failed != nil {
|
if failed != nil {
|
||||||
return nil, failed
|
return nil, failed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run Terraform Apply
|
||||||
r.queueLog(ctx, &proto.Log{
|
r.queueLog(ctx, &proto.Log{
|
||||||
Source: proto.LogSource_PROVISIONER_DAEMON,
|
Source: proto.LogSource_PROVISIONER_DAEMON,
|
||||||
Level: sdkproto.LogLevel_INFO,
|
Level: sdkproto.LogLevel_INFO,
|
||||||
@@ -1081,24 +986,17 @@ func (r *Runner) runWorkspaceBuild(ctx context.Context) (*proto.CompletedJob, *p
|
|||||||
CreatedAt: time.Now().UnixMilli(),
|
CreatedAt: time.Now().UnixMilli(),
|
||||||
})
|
})
|
||||||
|
|
||||||
resp, failed = r.buildWorkspace(ctx, applyStage, &sdkproto.Request{
|
applyComplete, failed := r.apply(ctx, applyStage, &sdkproto.ApplyRequest{
|
||||||
Type: &sdkproto.Request_Apply{
|
Metadata: r.job.GetWorkspaceBuild().Metadata,
|
||||||
Apply: &sdkproto.ApplyRequest{
|
|
||||||
Metadata: r.job.GetWorkspaceBuild().Metadata,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
if failed != nil {
|
if failed != nil {
|
||||||
return nil, failed
|
return nil, failed
|
||||||
}
|
}
|
||||||
applyComplete := resp.GetApply()
|
|
||||||
if applyComplete == nil {
|
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")
|
||||||
}
|
}
|
||||||
|
// Collect apply timings
|
||||||
// Prepend the plan timings (since they occurred first).
|
timings = append(timings, applyComplete.Timings...)
|
||||||
applyComplete.Timings = append(planComplete.Timings, applyComplete.Timings...)
|
|
||||||
|
|
||||||
if applyComplete.Error != "" {
|
if applyComplete.Error != "" {
|
||||||
r.logger.Warn(context.Background(), "apply failed; updating state",
|
r.logger.Warn(context.Background(), "apply failed; updating state",
|
||||||
slog.F("error", applyComplete.Error),
|
slog.F("error", applyComplete.Error),
|
||||||
@@ -1111,15 +1009,46 @@ func (r *Runner) runWorkspaceBuild(ctx context.Context) (*proto.CompletedJob, *p
|
|||||||
Type: &proto.FailedJob_WorkspaceBuild_{
|
Type: &proto.FailedJob_WorkspaceBuild_{
|
||||||
WorkspaceBuild: &proto.FailedJob_WorkspaceBuild{
|
WorkspaceBuild: &proto.FailedJob_WorkspaceBuild{
|
||||||
State: applyComplete.State,
|
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",
|
r.logger.Info(context.Background(), "apply successful",
|
||||||
slog.F("resource_count", len(applyComplete.Resources)),
|
slog.F("resource_count", len(graphComplete.Resources)),
|
||||||
slog.F("resources", resourceNames(applyComplete.Resources)),
|
slog.F("resources", resourceNames(graphComplete.Resources)),
|
||||||
slog.F("state_len", len(applyComplete.State)),
|
slog.F("state_len", len(applyComplete.State)),
|
||||||
)
|
)
|
||||||
r.flushQueuedLogs(ctx)
|
r.flushQueuedLogs(ctx)
|
||||||
@@ -1129,15 +1058,14 @@ func (r *Runner) runWorkspaceBuild(ctx context.Context) (*proto.CompletedJob, *p
|
|||||||
Type: &proto.CompletedJob_WorkspaceBuild_{
|
Type: &proto.CompletedJob_WorkspaceBuild_{
|
||||||
WorkspaceBuild: &proto.CompletedJob_WorkspaceBuild{
|
WorkspaceBuild: &proto.CompletedJob_WorkspaceBuild{
|
||||||
State: applyComplete.State,
|
State: applyComplete.State,
|
||||||
Resources: applyComplete.Resources,
|
Resources: graphComplete.Resources,
|
||||||
Timings: applyComplete.Timings,
|
Timings: timings,
|
||||||
// Modules are created on disk by `terraform init`, and that is only
|
// Modules files are omitted for workspace builds, but the modules.json metadata
|
||||||
// called by `plan`. `apply` does not modify them, so we can use the
|
// is available from init to return.
|
||||||
// modules from the plan response.
|
Modules: initComplete.Modules,
|
||||||
Modules: planComplete.Modules,
|
|
||||||
// Resource replacements are discovered at plan time, only.
|
// Resource replacements are discovered at plan time, only.
|
||||||
ResourceReplacements: planComplete.ResourceReplacements,
|
ResourceReplacements: planComplete.ResourceReplacements,
|
||||||
AiTasks: applyComplete.AiTasks,
|
AiTasks: graphComplete.AiTasks,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ func ParseErrorf(format string, args ...any) *proto.ParseComplete {
|
|||||||
return &proto.ParseComplete{Error: fmt.Sprintf(format, args...)}
|
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 {
|
func PlanErrorf(format string, args ...any) *proto.PlanComplete {
|
||||||
return &proto.PlanComplete{Error: fmt.Sprintf(format, args...)}
|
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 {
|
func ApplyErrorf(format string, args ...any) *proto.ApplyComplete {
|
||||||
return &proto.ApplyComplete{Error: fmt.Sprintf(format, args...)}
|
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
|
// Config represents execution configuration shared by all subsequent requests in the Session
|
||||||
message Config {
|
message Config {
|
||||||
// template_source_archive is a tar of the template source files
|
string provisioner_log_level = 1;
|
||||||
bytes template_source_archive = 1;
|
|
||||||
// state is the provisioner state (if any)
|
|
||||||
bytes state = 2;
|
|
||||||
string provisioner_log_level = 3;
|
|
||||||
// Template imports can omit template id
|
// Template imports can omit template id
|
||||||
optional string template_id = 4;
|
optional string template_id = 2;
|
||||||
// Dry runs omit version id
|
// Dry runs omit version id
|
||||||
optional string template_version_id = 5;
|
optional string template_version_id = 3;
|
||||||
optional bool exp_reuse_terraform_workspace = 6; // Whether to reuse existing terraform workspaces if they exist.
|
optional bool exp_reuse_terraform_workspace = 4; // Whether to reuse existing terraform workspaces if they exist.
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseRequest consumes source-code to produce inputs.
|
// ParseRequest consumes source-code to produce inputs.
|
||||||
@@ -393,6 +389,25 @@ message ParseComplete {
|
|||||||
map<string, string> workspace_tags = 4;
|
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
|
// PlanRequest asks the provisioner to plan what resources & parameters it will create
|
||||||
message PlanRequest {
|
message PlanRequest {
|
||||||
Metadata metadata = 1;
|
Metadata metadata = 1;
|
||||||
@@ -401,52 +416,61 @@ message PlanRequest {
|
|||||||
repeated ExternalAuthProvider external_auth_providers = 4;
|
repeated ExternalAuthProvider external_auth_providers = 4;
|
||||||
repeated RichParameterValue previous_parameter_values = 5;
|
repeated RichParameterValue previous_parameter_values = 5;
|
||||||
|
|
||||||
// If true, the provisioner can safely assume the caller does not need the
|
// state is the provisioner state (if any)
|
||||||
// module files downloaded by the `terraform init` command.
|
bytes state = 6;
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PlanComplete indicates a request to plan completed.
|
// PlanComplete indicates a request to plan completed.
|
||||||
message PlanComplete {
|
message PlanComplete {
|
||||||
string error = 1;
|
string error = 1;
|
||||||
repeated Resource resources = 2;
|
repeated Timing timings = 2;
|
||||||
repeated RichParameter parameters = 3;
|
bytes plan = 3;
|
||||||
repeated ExternalAuthProviderResource external_auth_providers = 4;
|
int32 dailyCost = 4;
|
||||||
repeated Timing timings = 6;
|
repeated ResourceReplacement resource_replacements = 5;
|
||||||
repeated Module modules = 7;
|
int32 ai_task_count = 6;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApplyRequest asks the provisioner to apply the changes. Apply MUST be preceded by a successful plan request/response
|
// 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.
|
// in the same Session. The plan data is not transmitted over the wire and is cached by the provisioner in the Session.
|
||||||
message ApplyRequest {
|
message ApplyRequest {
|
||||||
Metadata metadata = 1;
|
Metadata metadata = 1;
|
||||||
|
// state is the provisioner state (if any)
|
||||||
|
bytes state = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApplyComplete indicates a request to apply completed.
|
// ApplyComplete indicates a request to apply completed.
|
||||||
message ApplyComplete {
|
message ApplyComplete {
|
||||||
bytes state = 1;
|
bytes state = 1;
|
||||||
string error = 2;
|
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 Resource resources = 3;
|
||||||
repeated RichParameter parameters = 4;
|
repeated RichParameter parameters = 4;
|
||||||
repeated ExternalAuthProviderResource external_auth_providers = 5;
|
repeated ExternalAuthProviderResource external_auth_providers = 5;
|
||||||
repeated Timing timings = 6;
|
repeated Preset presets = 6;
|
||||||
repeated provisioner.AITask ai_tasks = 7;
|
// 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 {
|
message Timing {
|
||||||
@@ -472,9 +496,11 @@ message Request {
|
|||||||
oneof type {
|
oneof type {
|
||||||
Config config = 1;
|
Config config = 1;
|
||||||
ParseRequest parse = 2;
|
ParseRequest parse = 2;
|
||||||
PlanRequest plan = 3;
|
InitRequest init = 3;
|
||||||
ApplyRequest apply = 4;
|
PlanRequest plan = 4;
|
||||||
CancelRequest cancel = 5;
|
ApplyRequest apply = 5;
|
||||||
|
GraphRequest graph = 6;
|
||||||
|
CancelRequest cancel = 7;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -482,10 +508,12 @@ message Response {
|
|||||||
oneof type {
|
oneof type {
|
||||||
Log log = 1;
|
Log log = 1;
|
||||||
ParseComplete parse = 2;
|
ParseComplete parse = 2;
|
||||||
PlanComplete plan = 3;
|
InitComplete init = 3;
|
||||||
ApplyComplete apply = 4;
|
PlanComplete plan = 4;
|
||||||
DataUpload data_upload = 5;
|
ApplyComplete apply = 5;
|
||||||
ChunkPiece chunk_piece = 6;
|
GraphComplete graph = 6;
|
||||||
|
DataUpload data_upload = 7;
|
||||||
|
ChunkPiece chunk_piece = 8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -519,14 +547,28 @@ message ChunkPiece {
|
|||||||
|
|
||||||
service Provisioner {
|
service Provisioner {
|
||||||
// Session represents provisioning a single template import or workspace. The daemon always sends Config followed
|
// 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
|
// by one of the requests (InitRequest, ParseRequest, PlanRequest, ApplyRequest, GraphRequest). The provisioner
|
||||||
// of zero or more Logs, followed by the corresponding complete message (ParseComplete, PlanComplete,
|
// should respond with a stream of zero or more Logs, followed by the corresponding complete message
|
||||||
// ApplyComplete). The daemon may then send a new request. A request to apply MUST be preceded by a request plan,
|
// (InitComplete, ParseComplete, PlanComplete, ApplyComplete, GraphComplete).
|
||||||
// and the provisioner should store the plan data on the Session after a successful plan, so that the daemon may
|
// The daemon may then send a new request.
|
||||||
// request an apply. If the daemon closes the Session without an apply, the plan data may be safely discarded.
|
|
||||||
//
|
//
|
||||||
// The daemon may send a CancelRequest, asynchronously to ask the provisioner to cancel the previous ParseRequest,
|
// A request to Parse or Plan MUST be preceded by a request init. The provisioner should store the init data on
|
||||||
// PlanRequest, or ApplyRequest. The provisioner MUST reply with a complete message corresponding to the request
|
// the session after a successful init. If the daemon closes the session, the init data may be safely discarded.
|
||||||
// that was canceled. If the provisioner has already completed the request, it may ignore the CancelRequest.
|
//
|
||||||
|
// 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);
|
rpc Session(stream Request) returns (stream Response);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,9 +35,11 @@ type ServeOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Server interface {
|
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
|
Parse(s *Session, r *proto.ParseRequest, canceledOrComplete <-chan struct{}) *proto.ParseComplete
|
||||||
Plan(s *Session, r *proto.PlanRequest, canceledOrComplete <-chan struct{}) *proto.PlanComplete
|
Plan(s *Session, r *proto.PlanRequest, canceledOrComplete <-chan struct{}) *proto.PlanComplete
|
||||||
Apply(s *Session, r *proto.ApplyRequest, canceledOrComplete <-chan struct{}) *proto.ApplyComplete
|
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.
|
// 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{}}})
|
err = s.Send(&proto.Request{Type: &proto.Request_Config{Config: &proto.Config{}}})
|
||||||
require.NoError(t, err)
|
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{}}})
|
err = s.Send(&proto.Request{Type: &proto.Request_Parse{Parse: &proto.ParseRequest{}}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
msg, err := s.Recv()
|
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{}}})
|
err = s.Send(&proto.Request{Type: &proto.Request_Config{Config: &proto.Config{}}})
|
||||||
require.NoError(t, err)
|
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{}}})
|
err = s.Send(&proto.Request{Type: &proto.Request_Parse{Parse: &proto.ParseRequest{}}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
msg, err := s.Recv()
|
msg, err := s.Recv()
|
||||||
@@ -135,8 +145,18 @@ func TestProvisionerSDK(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ provisionersdk.Server = unimplementedServer{}
|
||||||
|
|
||||||
type unimplementedServer struct{}
|
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 {
|
func (unimplementedServer) Parse(_ *provisionersdk.Session, _ *proto.ParseRequest, _ <-chan struct{}) *proto.ParseComplete {
|
||||||
return &proto.ParseComplete{Error: "unimplemented"}
|
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)
|
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()
|
return s.handleRequests()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,6 +106,10 @@ func (s *Session) handleRequests() error {
|
|||||||
}
|
}
|
||||||
resp := &proto.Response{}
|
resp := &proto.Response{}
|
||||||
if parse := req.GetParse(); parse != nil {
|
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]{
|
r := &request[*proto.ParseRequest, *proto.ParseComplete]{
|
||||||
req: parse,
|
req: parse,
|
||||||
session: s,
|
session: s,
|
||||||
@@ -129,48 +129,28 @@ func (s *Session) handleRequests() error {
|
|||||||
}
|
}
|
||||||
resp.Type = &proto.Response_Parse{Parse: complete}
|
resp.Type = &proto.Response_Parse{Parse: complete}
|
||||||
}
|
}
|
||||||
if plan := req.GetPlan(); plan != nil {
|
if init := req.GetInit(); init != nil {
|
||||||
r := &request[*proto.PlanRequest, *proto.PlanComplete]{
|
if s.initialized {
|
||||||
req: plan,
|
return xerrors.New("cannot init more than once per session")
|
||||||
session: s,
|
|
||||||
serverFn: s.server.Plan,
|
|
||||||
cancels: requests,
|
|
||||||
}
|
}
|
||||||
complete, err := r.do()
|
initResp, err := s.handleInitRequest(init, requests)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
resp.Type = &proto.Response_Plan{Plan: complete}
|
resp.Type = &proto.Response_Init{Init: initResp}
|
||||||
|
}
|
||||||
if protobuf.Size(resp) > drpcsdk.MaxMessageSize {
|
if plan := req.GetPlan(); plan != nil {
|
||||||
// It is likely the modules that is pushing the message size over the limit.
|
if !s.initialized {
|
||||||
// Send the modules over a stream of messages instead.
|
return xerrors.New("cannot plan before successful init")
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
planResp, err := s.handlePlanRequest(plan, requests)
|
||||||
if complete.Error == "" {
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if planResp.Error == "" {
|
||||||
planned = true
|
planned = true
|
||||||
}
|
}
|
||||||
|
resp.Type = &proto.Response_Plan{Plan: planResp}
|
||||||
}
|
}
|
||||||
if apply := req.GetApply(); apply != nil {
|
if apply := req.GetApply(); apply != nil {
|
||||||
if !planned {
|
if !planned {
|
||||||
@@ -188,6 +168,23 @@ func (s *Session) handleRequests() error {
|
|||||||
}
|
}
|
||||||
resp.Type = &proto.Response_Apply{Apply: complete}
|
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)
|
err := s.stream.Send(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("send response: %w", err)
|
return xerrors.Errorf("send response: %w", err)
|
||||||
@@ -196,11 +193,75 @@ func (s *Session) handleRequests() error {
|
|||||||
return nil
|
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 {
|
type Session struct {
|
||||||
Logger slog.Logger
|
Logger slog.Logger
|
||||||
Files tfpath.Layouter
|
Files tfpath.Layouter
|
||||||
Config *proto.Config
|
Config *proto.Config
|
||||||
|
|
||||||
|
// initialized indicates if an init was run.
|
||||||
|
// Required for plan/apply.
|
||||||
|
initialized bool
|
||||||
|
|
||||||
server Server
|
server Server
|
||||||
stream proto.DRPCProvisioner_SessionStream
|
stream proto.DRPCProvisioner_SessionStream
|
||||||
logLevel int32
|
logLevel int32
|
||||||
@@ -226,11 +287,11 @@ func (s *Session) ProvisionLog(level proto.LogLevel, output string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type pRequest interface {
|
type pRequest interface {
|
||||||
*proto.ParseRequest | *proto.PlanRequest | *proto.ApplyRequest
|
*proto.ParseRequest | *proto.InitRequest | *proto.PlanRequest | *proto.ApplyRequest | *proto.GraphRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
type pComplete interface {
|
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
|
// 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"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
"cdr.dev/slog"
|
"cdr.dev/slog"
|
||||||
"github.com/coder/coder/v2/provisionersdk/proto"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Layouter interface {
|
type Layouter interface {
|
||||||
@@ -28,7 +27,7 @@ type Layouter interface {
|
|||||||
TerraformMetadataDir() string
|
TerraformMetadataDir() string
|
||||||
ModulesDirectory() string
|
ModulesDirectory() string
|
||||||
ModulesFilePath() 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)
|
Cleanup(ctx context.Context, logger slog.Logger, fs afero.Fs)
|
||||||
CleanStaleSessions(ctx context.Context, logger slog.Logger, fs afero.Fs, now time.Time) error
|
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")
|
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",
|
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)
|
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)
|
return xerrors.Errorf("create work directory %q: %w", l.WorkDirectory(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Pass in cfg.TemplateSourceArchive, not the full config.
|
reader := tar.NewReader(bytes.NewBuffer(templateSourceArchive))
|
||||||
// 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
|
|
||||||
for {
|
for {
|
||||||
header, err := reader.Next()
|
header, err := reader.Next()
|
||||||
if err != nil {
|
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))
|
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",
|
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)
|
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)
|
return xerrors.Errorf("select terraform workspace: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
reader := tar.NewReader(bytes.NewBuffer(cfg.TemplateSourceArchive))
|
reader := tar.NewReader(bytes.NewBuffer(archive))
|
||||||
// for safety, nil out the reference on Config, since the reader now owns it.
|
|
||||||
cfg.TemplateSourceArchive = nil
|
|
||||||
for {
|
for {
|
||||||
header, err := reader.Next()
|
header, err := reader.Next()
|
||||||
if err != nil {
|
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{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: []*proto.Response{{
|
ProvisionGraph: []*proto.Response{{
|
||||||
Type: &proto.Response_Apply{
|
Type: &proto.Response_Graph{
|
||||||
Apply: &proto.ApplyComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "example",
|
Name: "example",
|
||||||
Type: "aws_instance",
|
Type: "aws_instance",
|
||||||
|
|||||||
@@ -43,10 +43,10 @@ func TestRun(t *testing.T) {
|
|||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{
|
{
|
||||||
Type: &proto.Response_Apply{
|
Type: &proto.Response_Graph{
|
||||||
Apply: &proto.ApplyComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{
|
Resources: []*proto.Resource{
|
||||||
{
|
{
|
||||||
Name: "example",
|
Name: "example",
|
||||||
|
|||||||
@@ -60,27 +60,11 @@ func Test_Runner(t *testing.T) {
|
|||||||
authToken := uuid.NewString()
|
authToken := uuid.NewString()
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{
|
{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Graph{
|
||||||
Plan: &proto.PlanComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Parameters: testParameters,
|
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{
|
Resources: []*proto.Resource{
|
||||||
{
|
{
|
||||||
Name: "example",
|
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)
|
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{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{
|
{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Graph{
|
||||||
Plan: &proto.PlanComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Parameters: testParameters,
|
Parameters: testParameters,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -341,27 +340,11 @@ func Test_Runner(t *testing.T) {
|
|||||||
authToken := uuid.NewString()
|
authToken := uuid.NewString()
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{
|
{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Graph{
|
||||||
Plan: &proto.PlanComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Parameters: testParameters,
|
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{
|
Resources: []*proto.Resource{
|
||||||
{
|
{
|
||||||
Name: "example",
|
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)
|
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{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{
|
{
|
||||||
Type: &proto.Response_Plan{
|
Type: &proto.Response_Graph{
|
||||||
Plan: &proto.PlanComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Parameters: testParameters,
|
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{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: []*proto.Response{{
|
ProvisionGraph: []*proto.Response{{
|
||||||
Type: &proto.Response_Apply{
|
Type: &proto.Response_Graph{
|
||||||
Apply: &proto.ApplyComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "example",
|
Name: "example",
|
||||||
Type: "aws_instance",
|
Type: "aws_instance",
|
||||||
|
|||||||
@@ -58,7 +58,14 @@ func Test_Runner(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Type: &proto.Response_Apply{
|
Type: &proto.Response_Apply{
|
||||||
Apply: &proto.ApplyComplete{
|
Apply: &proto.ApplyComplete{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ProvisionGraph: []*proto.Response{
|
||||||
|
{
|
||||||
|
Type: &proto.Response_Graph{
|
||||||
|
Graph: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{
|
Resources: []*proto.Resource{
|
||||||
{
|
{
|
||||||
Name: "example1",
|
Name: "example1",
|
||||||
@@ -245,8 +252,10 @@ func Test_Runner(t *testing.T) {
|
|||||||
user := coderdtest.CreateFirstUser(t, client)
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
|
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
|
ProvisionInit: echo.InitComplete,
|
||||||
|
ProvisionGraph: echo.GraphComplete,
|
||||||
ProvisionApply: []*proto.Response{
|
ProvisionApply: []*proto.Response{
|
||||||
{
|
{
|
||||||
Type: &proto.Response_Apply{
|
Type: &proto.Response_Apply{
|
||||||
|
|||||||
@@ -49,9 +49,9 @@ func TestRun(t *testing.T) {
|
|||||||
version = coderdtest.CreateTemplateVersion(t, client, firstUser.OrganizationID, &echo.Responses{
|
version = coderdtest.CreateTemplateVersion(t, client, firstUser.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: []*proto.Response{{
|
ProvisionGraph: []*proto.Response{{
|
||||||
Type: &proto.Response_Apply{
|
Type: &proto.Response_Graph{
|
||||||
Apply: &proto.ApplyComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "example",
|
Name: "example",
|
||||||
Type: "aws_instance",
|
Type: "aws_instance",
|
||||||
@@ -168,9 +168,9 @@ func TestRun(t *testing.T) {
|
|||||||
version = coderdtest.CreateTemplateVersion(t, client, firstUser.OrganizationID, &echo.Responses{
|
version = coderdtest.CreateTemplateVersion(t, client, firstUser.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: []*proto.Response{{
|
ProvisionGraph: []*proto.Response{{
|
||||||
Type: &proto.Response_Apply{
|
Type: &proto.Response_Graph{
|
||||||
Apply: &proto.ApplyComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{{
|
Resources: []*proto.Resource{{
|
||||||
Name: "example",
|
Name: "example",
|
||||||
Type: "aws_instance",
|
Type: "aws_instance",
|
||||||
|
|||||||
@@ -39,10 +39,10 @@ func TestRun(t *testing.T) {
|
|||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
Parse: echo.ParseComplete,
|
Parse: echo.ParseComplete,
|
||||||
ProvisionPlan: echo.PlanComplete,
|
ProvisionPlan: echo.PlanComplete,
|
||||||
ProvisionApply: []*proto.Response{
|
ProvisionGraph: []*proto.Response{
|
||||||
{
|
{
|
||||||
Type: &proto.Response_Apply{
|
Type: &proto.Response_Graph{
|
||||||
Apply: &proto.ApplyComplete{
|
Graph: &proto.GraphComplete{
|
||||||
Resources: []*proto.Resource{
|
Resources: []*proto.Resource{
|
||||||
{
|
{
|
||||||
Name: "example",
|
Name: "example",
|
||||||
|
|||||||
+110
-49
@@ -32,6 +32,8 @@ import {
|
|||||||
type ApplyComplete,
|
type ApplyComplete,
|
||||||
AppSharingLevel,
|
AppSharingLevel,
|
||||||
type ExternalAuthProviderResource,
|
type ExternalAuthProviderResource,
|
||||||
|
type GraphComplete,
|
||||||
|
type InitComplete,
|
||||||
type ParseComplete,
|
type ParseComplete,
|
||||||
type PlanComplete,
|
type PlanComplete,
|
||||||
type Resource,
|
type Resource,
|
||||||
@@ -540,12 +542,14 @@ type RecursivePartial<T> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface EchoProvisionerResponses {
|
interface EchoProvisionerResponses {
|
||||||
|
init?: RecursivePartial<Response>[];
|
||||||
// parse is for observing any Terraform variables
|
// parse is for observing any Terraform variables
|
||||||
parse?: RecursivePartial<Response>[];
|
parse?: RecursivePartial<Response>[];
|
||||||
// plan occurs when the template is imported
|
// plan occurs when the template is imported
|
||||||
plan?: RecursivePartial<Response>[];
|
plan?: RecursivePartial<Response>[];
|
||||||
// apply occurs when the workspace is built
|
// apply occurs when the workspace is built
|
||||||
apply?: RecursivePartial<Response>[];
|
apply?: RecursivePartial<Response>[];
|
||||||
|
graph?: RecursivePartial<Response>[];
|
||||||
// extraFiles allows the bundling of terraform files in echo provisioner tars
|
// extraFiles allows the bundling of terraform files in echo provisioner tars
|
||||||
// in order to support dynamic parameters
|
// in order to support dynamic parameters
|
||||||
extraFiles?: Map<string, string>;
|
extraFiles?: Map<string, string>;
|
||||||
@@ -560,6 +564,40 @@ const emptyPlan = new TextEncoder().encode("{}");
|
|||||||
const createTemplateVersionTar = async (
|
const createTemplateVersionTar = async (
|
||||||
responses: EchoProvisionerResponses = {},
|
responses: EchoProvisionerResponses = {},
|
||||||
): Promise<Buffer> => {
|
): 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) {
|
if (!responses.parse) {
|
||||||
responses.parse = [
|
responses.parse = [
|
||||||
{
|
{
|
||||||
@@ -575,25 +613,18 @@ const createTemplateVersionTar = async (
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
if (!responses.plan) {
|
if (!responses.plan) {
|
||||||
responses.plan = responses.apply.map((response) => {
|
responses.plan = [
|
||||||
if (response.log) {
|
{
|
||||||
return response;
|
plan: {},
|
||||||
}
|
},
|
||||||
return {
|
];
|
||||||
plan: {
|
}
|
||||||
error: response.apply?.error ?? "",
|
if (!responses.graph) {
|
||||||
resources: response.apply?.resources ?? [],
|
responses.graph = [
|
||||||
parameters: response.apply?.parameters ?? [],
|
{
|
||||||
externalAuthProviders: response.apply?.externalAuthProviders ?? [],
|
graph: {},
|
||||||
timings: response.apply?.timings ?? [],
|
},
|
||||||
presets: [],
|
];
|
||||||
resourceReplacements: [],
|
|
||||||
plan: emptyPlan,
|
|
||||||
moduleFiles: new Uint8Array(),
|
|
||||||
moduleFilesHash: new Uint8Array(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const tar = new TarWriter();
|
const tar = new TarWriter();
|
||||||
@@ -617,6 +648,33 @@ const createTemplateVersionTar = async (
|
|||||||
Response.encode(response as Response).finish(),
|
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>) => {
|
const fillResource = (resource: RecursivePartial<Resource>) => {
|
||||||
if (resource.agents) {
|
if (resource.agents) {
|
||||||
@@ -701,40 +759,31 @@ const createTemplateVersionTar = async (
|
|||||||
response.apply = {
|
response.apply = {
|
||||||
error: "",
|
error: "",
|
||||||
state: new Uint8Array(),
|
state: new Uint8Array(),
|
||||||
resources: [],
|
|
||||||
parameters: [],
|
|
||||||
externalAuthProviders: [],
|
|
||||||
timings: [],
|
timings: [],
|
||||||
aiTasks: [],
|
|
||||||
...response.apply,
|
...response.apply,
|
||||||
} as ApplyComplete;
|
} as ApplyComplete;
|
||||||
response.apply.resources = response.apply.resources?.map(fillResource);
|
|
||||||
|
|
||||||
tar.addFile(
|
tar.addFile(
|
||||||
`${index}.apply.protobuf`,
|
`${index}.apply.protobuf`,
|
||||||
Response.encode(response as Response).finish(),
|
Response.encode(response as Response).finish(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
responses.plan.forEach((response, index) => {
|
responses.graph.forEach((response, index) => {
|
||||||
response.plan = {
|
response.graph = {
|
||||||
error: "",
|
error: "",
|
||||||
resources: [],
|
resources: [],
|
||||||
parameters: [],
|
parameters: [],
|
||||||
externalAuthProviders: [],
|
externalAuthProviders: [],
|
||||||
timings: [],
|
timings: [],
|
||||||
modules: [],
|
|
||||||
presets: [],
|
presets: [],
|
||||||
resourceReplacements: [],
|
resourceReplacements: [],
|
||||||
plan: emptyPlan,
|
|
||||||
moduleFiles: new Uint8Array(),
|
|
||||||
moduleFilesHash: new Uint8Array(),
|
|
||||||
aiTasks: [],
|
aiTasks: [],
|
||||||
...response.plan,
|
...response.graph,
|
||||||
} as PlanComplete;
|
} as GraphComplete;
|
||||||
response.plan.resources = response.plan.resources?.map(fillResource);
|
response.graph.resources = response.graph.resources?.map(fillResource);
|
||||||
|
|
||||||
tar.addFile(
|
tar.addFile(
|
||||||
`${index}.plan.protobuf`,
|
`${index}.graph.protobuf`,
|
||||||
Response.encode(response as Response).finish(),
|
Response.encode(response as Response).finish(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -889,16 +938,20 @@ ${options}}
|
|||||||
parse: {},
|
parse: {},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
plan: [
|
init: [
|
||||||
{
|
{
|
||||||
plan: {
|
init: {},
|
||||||
parameters: richParameters,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
apply: [
|
plan: [
|
||||||
{
|
{
|
||||||
apply: {
|
plan: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
graph: [
|
||||||
|
{
|
||||||
|
graph: {
|
||||||
|
parameters: richParameters,
|
||||||
resources: [
|
resources: [
|
||||||
{
|
{
|
||||||
name: "example",
|
name: "example",
|
||||||
@@ -907,6 +960,11 @@ ${options}}
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
apply: [
|
||||||
|
{
|
||||||
|
apply: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
extraFiles: new Map([["main.tf", tf]]),
|
extraFiles: new Map([["main.tf", tf]]),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -915,21 +973,19 @@ export const echoResponsesWithExternalAuth = (
|
|||||||
providers: ExternalAuthProviderResource[],
|
providers: ExternalAuthProviderResource[],
|
||||||
): EchoProvisionerResponses => {
|
): EchoProvisionerResponses => {
|
||||||
return {
|
return {
|
||||||
|
init: [
|
||||||
|
{
|
||||||
|
init: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
parse: [
|
parse: [
|
||||||
{
|
{
|
||||||
parse: {},
|
parse: {},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
plan: [
|
graph: [
|
||||||
{
|
{
|
||||||
plan: {
|
graph: {
|
||||||
externalAuthProviders: providers,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
apply: [
|
|
||||||
{
|
|
||||||
apply: {
|
|
||||||
externalAuthProviders: providers,
|
externalAuthProviders: providers,
|
||||||
resources: [
|
resources: [
|
||||||
{
|
{
|
||||||
@@ -939,6 +995,11 @@ export const echoResponsesWithExternalAuth = (
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
apply: [
|
||||||
|
{
|
||||||
|
apply: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Generated
+194
-99
@@ -69,6 +69,13 @@ export enum PrebuiltWorkspaceBuildStage {
|
|||||||
UNRECOGNIZED = -1,
|
UNRECOGNIZED = -1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum GraphSource {
|
||||||
|
SOURCE_UNKNOWN = 0,
|
||||||
|
SOURCE_PLAN = 1,
|
||||||
|
SOURCE_STATE = 2,
|
||||||
|
UNRECOGNIZED = -1,
|
||||||
|
}
|
||||||
|
|
||||||
export enum TimingState {
|
export enum TimingState {
|
||||||
STARTED = 0,
|
STARTED = 0,
|
||||||
COMPLETED = 1,
|
COMPLETED = 1,
|
||||||
@@ -410,10 +417,6 @@ export interface Metadata {
|
|||||||
|
|
||||||
/** Config represents execution configuration shared by all subsequent requests in the Session */
|
/** Config represents execution configuration shared by all subsequent requests in the Session */
|
||||||
export interface Config {
|
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;
|
provisionerLogLevel: string;
|
||||||
/** Template imports can omit template id */
|
/** Template imports can omit template id */
|
||||||
templateId?:
|
templateId?:
|
||||||
@@ -444,6 +447,26 @@ export interface ParseComplete_WorkspaceTagsEntry {
|
|||||||
value: string;
|
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 */
|
/** PlanRequest asks the provisioner to plan what resources & parameters it will create */
|
||||||
export interface PlanRequest {
|
export interface PlanRequest {
|
||||||
metadata: Metadata | undefined;
|
metadata: Metadata | undefined;
|
||||||
@@ -451,29 +474,51 @@ export interface PlanRequest {
|
|||||||
variableValues: VariableValue[];
|
variableValues: VariableValue[];
|
||||||
externalAuthProviders: ExternalAuthProvider[];
|
externalAuthProviders: ExternalAuthProvider[];
|
||||||
previousParameterValues: RichParameterValue[];
|
previousParameterValues: RichParameterValue[];
|
||||||
/**
|
/** state is the provisioner state (if any) */
|
||||||
* If true, the provisioner can safely assume the caller does not need the
|
state: Uint8Array;
|
||||||
* 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** PlanComplete indicates a request to plan completed. */
|
/** PlanComplete indicates a request to plan completed. */
|
||||||
export interface PlanComplete {
|
export interface PlanComplete {
|
||||||
error: string;
|
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[];
|
resources: Resource[];
|
||||||
parameters: RichParameter[];
|
parameters: RichParameter[];
|
||||||
externalAuthProviders: ExternalAuthProviderResource[];
|
externalAuthProviders: ExternalAuthProviderResource[];
|
||||||
timings: Timing[];
|
|
||||||
modules: Module[];
|
|
||||||
presets: Preset[];
|
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.
|
* 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
|
* 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;
|
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 {
|
export interface Timing {
|
||||||
start: Date | undefined;
|
start: Date | undefined;
|
||||||
end: Date | undefined;
|
end: Date | undefined;
|
||||||
@@ -522,16 +548,20 @@ export interface CancelRequest {
|
|||||||
export interface Request {
|
export interface Request {
|
||||||
config?: Config | undefined;
|
config?: Config | undefined;
|
||||||
parse?: ParseRequest | undefined;
|
parse?: ParseRequest | undefined;
|
||||||
|
init?: InitRequest | undefined;
|
||||||
plan?: PlanRequest | undefined;
|
plan?: PlanRequest | undefined;
|
||||||
apply?: ApplyRequest | undefined;
|
apply?: ApplyRequest | undefined;
|
||||||
|
graph?: GraphRequest | undefined;
|
||||||
cancel?: CancelRequest | undefined;
|
cancel?: CancelRequest | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Response {
|
export interface Response {
|
||||||
log?: Log | undefined;
|
log?: Log | undefined;
|
||||||
parse?: ParseComplete | undefined;
|
parse?: ParseComplete | undefined;
|
||||||
|
init?: InitComplete | undefined;
|
||||||
plan?: PlanComplete | undefined;
|
plan?: PlanComplete | undefined;
|
||||||
apply?: ApplyComplete | undefined;
|
apply?: ApplyComplete | undefined;
|
||||||
|
graph?: GraphComplete | undefined;
|
||||||
dataUpload?: DataUpload | undefined;
|
dataUpload?: DataUpload | undefined;
|
||||||
chunkPiece?: ChunkPiece | undefined;
|
chunkPiece?: ChunkPiece | undefined;
|
||||||
}
|
}
|
||||||
@@ -1318,23 +1348,17 @@ export const Metadata = {
|
|||||||
|
|
||||||
export const Config = {
|
export const Config = {
|
||||||
encode(message: Config, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
|
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 !== "") {
|
if (message.provisionerLogLevel !== "") {
|
||||||
writer.uint32(26).string(message.provisionerLogLevel);
|
writer.uint32(10).string(message.provisionerLogLevel);
|
||||||
}
|
}
|
||||||
if (message.templateId !== undefined) {
|
if (message.templateId !== undefined) {
|
||||||
writer.uint32(34).string(message.templateId);
|
writer.uint32(18).string(message.templateId);
|
||||||
}
|
}
|
||||||
if (message.templateVersionId !== undefined) {
|
if (message.templateVersionId !== undefined) {
|
||||||
writer.uint32(42).string(message.templateVersionId);
|
writer.uint32(26).string(message.templateVersionId);
|
||||||
}
|
}
|
||||||
if (message.expReuseTerraformWorkspace !== undefined) {
|
if (message.expReuseTerraformWorkspace !== undefined) {
|
||||||
writer.uint32(48).bool(message.expReuseTerraformWorkspace);
|
writer.uint32(32).bool(message.expReuseTerraformWorkspace);
|
||||||
}
|
}
|
||||||
return writer;
|
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 = {
|
export const PlanRequest = {
|
||||||
encode(message: PlanRequest, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
|
encode(message: PlanRequest, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
|
||||||
if (message.metadata !== undefined) {
|
if (message.metadata !== undefined) {
|
||||||
@@ -1393,8 +1450,8 @@ export const PlanRequest = {
|
|||||||
for (const v of message.previousParameterValues) {
|
for (const v of message.previousParameterValues) {
|
||||||
RichParameterValue.encode(v!, writer.uint32(42).fork()).ldelim();
|
RichParameterValue.encode(v!, writer.uint32(42).fork()).ldelim();
|
||||||
}
|
}
|
||||||
if (message.omitModuleFiles !== false) {
|
if (message.state.length !== 0) {
|
||||||
writer.uint32(48).bool(message.omitModuleFiles);
|
writer.uint32(50).bytes(message.state);
|
||||||
}
|
}
|
||||||
return writer;
|
return writer;
|
||||||
},
|
},
|
||||||
@@ -1405,44 +1462,20 @@ export const PlanComplete = {
|
|||||||
if (message.error !== "") {
|
if (message.error !== "") {
|
||||||
writer.uint32(10).string(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) {
|
for (const v of message.timings) {
|
||||||
Timing.encode(v!, writer.uint32(50).fork()).ldelim();
|
Timing.encode(v!, writer.uint32(18).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();
|
|
||||||
}
|
}
|
||||||
if (message.plan.length !== 0) {
|
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) {
|
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) {
|
if (message.aiTaskCount !== 0) {
|
||||||
writer.uint32(90).bytes(message.moduleFiles);
|
writer.uint32(48).int32(message.aiTaskCount);
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
return writer;
|
return writer;
|
||||||
},
|
},
|
||||||
@@ -1453,6 +1486,9 @@ export const ApplyRequest = {
|
|||||||
if (message.metadata !== undefined) {
|
if (message.metadata !== undefined) {
|
||||||
Metadata.encode(message.metadata, writer.uint32(10).fork()).ldelim();
|
Metadata.encode(message.metadata, writer.uint32(10).fork()).ldelim();
|
||||||
}
|
}
|
||||||
|
if (message.state.length !== 0) {
|
||||||
|
writer.uint32(50).bytes(message.state);
|
||||||
|
}
|
||||||
return writer;
|
return writer;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -1465,6 +1501,33 @@ export const ApplyComplete = {
|
|||||||
if (message.error !== "") {
|
if (message.error !== "") {
|
||||||
writer.uint32(18).string(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) {
|
for (const v of message.resources) {
|
||||||
Resource.encode(v!, writer.uint32(26).fork()).ldelim();
|
Resource.encode(v!, writer.uint32(26).fork()).ldelim();
|
||||||
}
|
}
|
||||||
@@ -1474,11 +1537,17 @@ export const ApplyComplete = {
|
|||||||
for (const v of message.externalAuthProviders) {
|
for (const v of message.externalAuthProviders) {
|
||||||
ExternalAuthProviderResource.encode(v!, writer.uint32(42).fork()).ldelim();
|
ExternalAuthProviderResource.encode(v!, writer.uint32(42).fork()).ldelim();
|
||||||
}
|
}
|
||||||
for (const v of message.timings) {
|
for (const v of message.presets) {
|
||||||
Timing.encode(v!, writer.uint32(50).fork()).ldelim();
|
Preset.encode(v!, writer.uint32(50).fork()).ldelim();
|
||||||
|
}
|
||||||
|
if (message.hasAiTasks !== false) {
|
||||||
|
writer.uint32(56).bool(message.hasAiTasks);
|
||||||
}
|
}
|
||||||
for (const v of message.aiTasks) {
|
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;
|
return writer;
|
||||||
},
|
},
|
||||||
@@ -1525,14 +1594,20 @@ export const Request = {
|
|||||||
if (message.parse !== undefined) {
|
if (message.parse !== undefined) {
|
||||||
ParseRequest.encode(message.parse, writer.uint32(18).fork()).ldelim();
|
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) {
|
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) {
|
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) {
|
if (message.cancel !== undefined) {
|
||||||
CancelRequest.encode(message.cancel, writer.uint32(42).fork()).ldelim();
|
CancelRequest.encode(message.cancel, writer.uint32(58).fork()).ldelim();
|
||||||
}
|
}
|
||||||
return writer;
|
return writer;
|
||||||
},
|
},
|
||||||
@@ -1546,17 +1621,23 @@ export const Response = {
|
|||||||
if (message.parse !== undefined) {
|
if (message.parse !== undefined) {
|
||||||
ParseComplete.encode(message.parse, writer.uint32(18).fork()).ldelim();
|
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) {
|
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) {
|
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) {
|
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) {
|
if (message.chunkPiece !== undefined) {
|
||||||
ChunkPiece.encode(message.chunkPiece, writer.uint32(50).fork()).ldelim();
|
ChunkPiece.encode(message.chunkPiece, writer.uint32(66).fork()).ldelim();
|
||||||
}
|
}
|
||||||
return writer;
|
return writer;
|
||||||
},
|
},
|
||||||
@@ -1598,15 +1679,29 @@ export const ChunkPiece = {
|
|||||||
export interface Provisioner {
|
export interface Provisioner {
|
||||||
/**
|
/**
|
||||||
* Session represents provisioning a single template import or workspace. The daemon always sends Config followed
|
* 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
|
* by one of the requests (InitRequest, ParseRequest, PlanRequest, ApplyRequest, GraphRequest). The provisioner
|
||||||
* of zero or more Logs, followed by the corresponding complete message (ParseComplete, PlanComplete,
|
* should respond with a stream of zero or more Logs, followed by the corresponding complete message
|
||||||
* ApplyComplete). The daemon may then send a new request. A request to apply MUST be preceded by a request plan,
|
* (InitComplete, ParseComplete, PlanComplete, ApplyComplete, GraphComplete).
|
||||||
* and the provisioner should store the plan data on the Session after a successful plan, so that the daemon may
|
* The daemon may then send a new request.
|
||||||
* request an apply. If the daemon closes the Session without an apply, the plan data may be safely discarded.
|
|
||||||
*
|
*
|
||||||
* The daemon may send a CancelRequest, asynchronously to ask the provisioner to cancel the previous ParseRequest,
|
* A request to Parse or Plan MUST be preceded by a request init. The provisioner should store the init data on
|
||||||
* PlanRequest, or ApplyRequest. The provisioner MUST reply with a complete message corresponding to the request
|
* the session after a successful init. If the daemon closes the session, the init data may be safely discarded.
|
||||||
* that was canceled. If the provisioner has already completed the request, it may ignore the CancelRequest.
|
*
|
||||||
|
* 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>;
|
Session(request: Observable<Request>): Observable<Response>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,9 +32,9 @@ test("app", async ({ context, page }) => {
|
|||||||
}
|
}
|
||||||
const appName = "test-app";
|
const appName = "test-app";
|
||||||
const template = await createTemplate(page, {
|
const template = await createTemplate(page, {
|
||||||
apply: [
|
graph: [
|
||||||
{
|
{
|
||||||
apply: {
|
graph: {
|
||||||
resources: [
|
resources: [
|
||||||
{
|
{
|
||||||
agents: [
|
agents: [
|
||||||
|
|||||||
@@ -25,9 +25,9 @@ test.skip(`ssh with agent ${agentVersion}`, async ({ page }) => {
|
|||||||
|
|
||||||
const token = randomUUID();
|
const token = randomUUID();
|
||||||
const template = await createTemplate(page, {
|
const template = await createTemplate(page, {
|
||||||
apply: [
|
graph: [
|
||||||
{
|
{
|
||||||
apply: {
|
graph: {
|
||||||
resources: [
|
resources: [
|
||||||
{
|
{
|
||||||
agents: [
|
agents: [
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ test.beforeEach(async ({ page }) => {
|
|||||||
test(`ssh with client ${clientVersion}`, async ({ page }) => {
|
test(`ssh with client ${clientVersion}`, async ({ page }) => {
|
||||||
const token = randomUUID();
|
const token = randomUUID();
|
||||||
const template = await createTemplate(page, {
|
const template = await createTemplate(page, {
|
||||||
apply: [
|
graph: [
|
||||||
{
|
{
|
||||||
apply: {
|
graph: {
|
||||||
resources: [
|
resources: [
|
||||||
{
|
{
|
||||||
agents: [
|
agents: [
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ test.beforeEach(async ({ page }) => {
|
|||||||
test("web terminal", async ({ context, page }) => {
|
test("web terminal", async ({ context, page }) => {
|
||||||
const token = randomUUID();
|
const token = randomUUID();
|
||||||
const template = await createTemplate(page, {
|
const template = await createTemplate(page, {
|
||||||
apply: [
|
graph: [
|
||||||
{
|
{
|
||||||
apply: {
|
graph: {
|
||||||
resources: [
|
resources: [
|
||||||
{
|
{
|
||||||
agents: [
|
agents: [
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ test.beforeEach(async ({ page }) => {
|
|||||||
test("create workspace", async ({ page }) => {
|
test("create workspace", async ({ page }) => {
|
||||||
await login(page, users.templateAdmin);
|
await login(page, users.templateAdmin);
|
||||||
const template = await createTemplate(page, {
|
const template = await createTemplate(page, {
|
||||||
apply: [{ apply: { resources: [{ name: "example" }] } }],
|
graph: [{ graph: { resources: [{ name: "example" }] } }],
|
||||||
});
|
});
|
||||||
|
|
||||||
await login(page, users.member);
|
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.",
|
"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",
|
name: "apply",
|
||||||
label: "apply",
|
label: "apply",
|
||||||
@@ -266,6 +256,16 @@ export const provisioningStages: Stage[] = [
|
|||||||
"Execute Terraform plan to create/modify/delete resources into desired states.",
|
"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[] => {
|
export const agentStages = (section: string): Stage[] => {
|
||||||
|
|||||||
Reference in New Issue
Block a user