Skip to content

Commit 398b999

Browse files
authored
chore: pass previous values into terraform apply (#17696)
Pass previous workspace build parameter values into the terraform `plan/apply`. Enforces monotonicity in terraform as well as `coderd`.
1 parent d0ab91c commit 398b999

File tree

9 files changed

+515
-440
lines changed

9 files changed

+515
-440
lines changed

coderd/provisionerdserver/provisionerdserver.go

+31-6
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,30 @@ func (s *server) acquireProtoJob(ctx context.Context, job database.ProvisionerJo
549549
return nil, failJob(fmt.Sprintf("convert workspace transition: %s", err))
550550
}
551551

552+
// A previous workspace build exists
553+
var lastWorkspaceBuildParameters []database.WorkspaceBuildParameter
554+
if workspaceBuild.BuildNumber > 1 {
555+
// TODO: Should we fetch the last build that succeeded? This fetches the
556+
// previous build regardless of the status of the build.
557+
buildNum := workspaceBuild.BuildNumber - 1
558+
previous, err := s.Database.GetWorkspaceBuildByWorkspaceIDAndBuildNumber(ctx, database.GetWorkspaceBuildByWorkspaceIDAndBuildNumberParams{
559+
WorkspaceID: workspaceBuild.WorkspaceID,
560+
BuildNumber: buildNum,
561+
})
562+
563+
// If the error is ErrNoRows, then assume previous values are empty.
564+
if err != nil && !xerrors.Is(err, sql.ErrNoRows) {
565+
return nil, xerrors.Errorf("get last build with number=%d: %w", buildNum, err)
566+
}
567+
568+
if err == nil {
569+
lastWorkspaceBuildParameters, err = s.Database.GetWorkspaceBuildParameters(ctx, previous.ID)
570+
if err != nil {
571+
return nil, xerrors.Errorf("get last build parameters %q: %w", previous.ID, err)
572+
}
573+
}
574+
}
575+
552576
workspaceBuildParameters, err := s.Database.GetWorkspaceBuildParameters(ctx, workspaceBuild.ID)
553577
if err != nil {
554578
return nil, failJob(fmt.Sprintf("get workspace build parameters: %s", err))
@@ -625,12 +649,13 @@ func (s *server) acquireProtoJob(ctx context.Context, job database.ProvisionerJo
625649

626650
protoJob.Type = &proto.AcquiredJob_WorkspaceBuild_{
627651
WorkspaceBuild: &proto.AcquiredJob_WorkspaceBuild{
628-
WorkspaceBuildId: workspaceBuild.ID.String(),
629-
WorkspaceName: workspace.Name,
630-
State: workspaceBuild.ProvisionerState,
631-
RichParameterValues: convertRichParameterValues(workspaceBuildParameters),
632-
VariableValues: asVariableValues(templateVariables),
633-
ExternalAuthProviders: externalAuthProviders,
652+
WorkspaceBuildId: workspaceBuild.ID.String(),
653+
WorkspaceName: workspace.Name,
654+
State: workspaceBuild.ProvisionerState,
655+
RichParameterValues: convertRichParameterValues(workspaceBuildParameters),
656+
PreviousParameterValues: convertRichParameterValues(lastWorkspaceBuildParameters),
657+
VariableValues: asVariableValues(templateVariables),
658+
ExternalAuthProviders: externalAuthProviders,
634659
Metadata: &sdkproto.Metadata{
635660
CoderUrl: s.AccessURL.String(),
636661
WorkspaceTransition: transition,

provisioner/terraform/provision.go

+6-3
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ func (s *server) Plan(
152152

153153
s.logger.Debug(ctx, "ran initialization")
154154

155-
env, err := provisionEnv(sess.Config, request.Metadata, request.RichParameterValues, request.ExternalAuthProviders)
155+
env, err := provisionEnv(sess.Config, request.Metadata, request.PreviousParameterValues, request.RichParameterValues, request.ExternalAuthProviders)
156156
if err != nil {
157157
return provisionersdk.PlanErrorf("setup env: %s", err)
158158
}
@@ -205,7 +205,7 @@ func (s *server) Apply(
205205

206206
// Earlier in the session, Plan() will have written the state file and the plan file.
207207
statefilePath := getStateFilePath(sess.WorkDirectory)
208-
env, err := provisionEnv(sess.Config, request.Metadata, nil, nil)
208+
env, err := provisionEnv(sess.Config, request.Metadata, nil, nil, nil)
209209
if err != nil {
210210
return provisionersdk.ApplyErrorf("provision env: %s", err)
211211
}
@@ -236,7 +236,7 @@ func planVars(plan *proto.PlanRequest) ([]string, error) {
236236

237237
func provisionEnv(
238238
config *proto.Config, metadata *proto.Metadata,
239-
richParams []*proto.RichParameterValue, externalAuth []*proto.ExternalAuthProvider,
239+
previousParams, richParams []*proto.RichParameterValue, externalAuth []*proto.ExternalAuthProvider,
240240
) ([]string, error) {
241241
env := safeEnviron()
242242
ownerGroups, err := json.Marshal(metadata.GetWorkspaceOwnerGroups())
@@ -280,6 +280,9 @@ func provisionEnv(
280280
for key, value := range provisionersdk.AgentScriptEnv() {
281281
env = append(env, key+"="+value)
282282
}
283+
for _, param := range previousParams {
284+
env = append(env, provider.ParameterEnvironmentVariablePrevious(param.Name)+"="+param.Value)
285+
}
283286
for _, param := range richParams {
284287
env = append(env, provider.ParameterEnvironmentVariable(param.Name)+"="+param.Value)
285288
}

provisionerd/proto/provisionerd.pb.go

+278-260
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

provisionerd/proto/provisionerd.proto

+4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ message AcquiredJob {
2222
provisioner.Metadata metadata = 7;
2323
bytes state = 8;
2424
string log_level = 9;
25+
// previous_parameter_values is used to pass the values of the previous
26+
// workspace build. Omit these values if the workspace is being created
27+
// for the first time.
28+
repeated provisioner.RichParameterValue previous_parameter_values = 10;
2529
}
2630
message TemplateImport {
2731
provisioner.Metadata metadata = 1;

provisionerd/proto/version.go

+3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ import "github.com/coder/coder/v2/apiversion"
1616
// API v1.5:
1717
// - Add new field named `prebuilt_workspace_build_stage` enum in the Metadata message.
1818
// - Add `plan` and `module_files` fields to `CompletedJob.TemplateImport`.
19+
// - Add previous parameter values to 'WorkspaceBuild' jobs. Provisioner passes
20+
// the previous values for the `terraform apply` to enforce monotonicity
21+
// in the terraform provider.
1922
const (
2023
CurrentMajor = 1
2124
CurrentMinor = 5

provisionerd/runner/runner.go

+8-5
Original file line numberDiff line numberDiff line change
@@ -691,7 +691,9 @@ func (r *Runner) runTemplateImportProvisionWithRichParameters(
691691
err := r.session.Send(&sdkproto.Request{Type: &sdkproto.Request_Plan{Plan: &sdkproto.PlanRequest{
692692
Metadata: metadata,
693693
RichParameterValues: richParameterValues,
694-
VariableValues: variableValues,
694+
// Template import has no previous values
695+
PreviousParameterValues: make([]*sdkproto.RichParameterValue, 0),
696+
VariableValues: variableValues,
695697
}}})
696698
if err != nil {
697699
return nil, xerrors.Errorf("start provision: %w", err)
@@ -960,10 +962,11 @@ func (r *Runner) runWorkspaceBuild(ctx context.Context) (*proto.CompletedJob, *p
960962
resp, failed := r.buildWorkspace(ctx, "Planning infrastructure", &sdkproto.Request{
961963
Type: &sdkproto.Request_Plan{
962964
Plan: &sdkproto.PlanRequest{
963-
Metadata: r.job.GetWorkspaceBuild().Metadata,
964-
RichParameterValues: r.job.GetWorkspaceBuild().RichParameterValues,
965-
VariableValues: r.job.GetWorkspaceBuild().VariableValues,
966-
ExternalAuthProviders: r.job.GetWorkspaceBuild().ExternalAuthProviders,
965+
Metadata: r.job.GetWorkspaceBuild().Metadata,
966+
RichParameterValues: r.job.GetWorkspaceBuild().RichParameterValues,
967+
PreviousParameterValues: r.job.GetWorkspaceBuild().PreviousParameterValues,
968+
VariableValues: r.job.GetWorkspaceBuild().VariableValues,
969+
ExternalAuthProviders: r.job.GetWorkspaceBuild().ExternalAuthProviders,
967970
},
968971
},
969972
})

0 commit comments

Comments
 (0)