Skip to content

Commit ca8660c

Browse files
authored
chore: keep previous workspace build parameters for dynamic params (#18059)
The existing code persists all static parameters and their values. Using the previous build as the source if no new inputs are found. Dynamic params do not have a state of the parameters saved to disk. So instead, all previous values are persisted always, and new inputs override.
1 parent 6e255c7 commit ca8660c

File tree

2 files changed

+114
-11
lines changed

2 files changed

+114
-11
lines changed

coderd/parameters_test.go

Lines changed: 90 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/coder/coder/v2/coderd/database/dbtestutil"
1616
"github.com/coder/coder/v2/coderd/database/pubsub"
1717
"github.com/coder/coder/v2/coderd/rbac"
18+
"github.com/coder/coder/v2/coderd/util/ptr"
1819
"github.com/coder/coder/v2/codersdk"
1920
"github.com/coder/coder/v2/codersdk/wsjson"
2021
"github.com/coder/coder/v2/provisioner/echo"
@@ -211,6 +212,86 @@ func TestDynamicParametersWithTerraformValues(t *testing.T) {
211212
require.Zero(t, setup.api.FileCache.Count())
212213
})
213214

215+
t.Run("RebuildParameters", func(t *testing.T) {
216+
t.Parallel()
217+
218+
dynamicParametersTerraformSource, err := os.ReadFile("testdata/parameters/modules/main.tf")
219+
require.NoError(t, err)
220+
221+
modulesArchive, err := terraform.GetModulesArchive(os.DirFS("testdata/parameters/modules"))
222+
require.NoError(t, err)
223+
224+
setup := setupDynamicParamsTest(t, setupDynamicParamsTestParams{
225+
provisionerDaemonVersion: provProto.CurrentVersion.String(),
226+
mainTF: dynamicParametersTerraformSource,
227+
modulesArchive: modulesArchive,
228+
plan: nil,
229+
static: nil,
230+
})
231+
232+
ctx := testutil.Context(t, testutil.WaitMedium)
233+
stream := setup.stream
234+
previews := stream.Chan()
235+
236+
// Should see the output of the module represented
237+
preview := testutil.RequireReceive(ctx, t, previews)
238+
require.Equal(t, -1, preview.ID)
239+
require.Empty(t, preview.Diagnostics)
240+
241+
require.Len(t, preview.Parameters, 1)
242+
require.Equal(t, "jetbrains_ide", preview.Parameters[0].Name)
243+
require.True(t, preview.Parameters[0].Value.Valid)
244+
require.Equal(t, "CL", preview.Parameters[0].Value.Value)
245+
_ = stream.Close(websocket.StatusGoingAway)
246+
247+
wrk := coderdtest.CreateWorkspace(t, setup.client, setup.template.ID, func(request *codersdk.CreateWorkspaceRequest) {
248+
request.RichParameterValues = []codersdk.WorkspaceBuildParameter{
249+
{
250+
Name: preview.Parameters[0].Name,
251+
Value: "GO",
252+
},
253+
}
254+
})
255+
coderdtest.AwaitWorkspaceBuildJobCompleted(t, setup.client, wrk.LatestBuild.ID)
256+
257+
params, err := setup.client.WorkspaceBuildParameters(ctx, wrk.LatestBuild.ID)
258+
require.NoError(t, err)
259+
require.Len(t, params, 1)
260+
require.Equal(t, "jetbrains_ide", params[0].Name)
261+
require.Equal(t, "GO", params[0].Value)
262+
263+
// A helper function to assert params
264+
doTransition := func(t *testing.T, trans codersdk.WorkspaceTransition) {
265+
t.Helper()
266+
267+
fooVal := coderdtest.RandomUsername(t)
268+
bld, err := setup.client.CreateWorkspaceBuild(ctx, wrk.ID, codersdk.CreateWorkspaceBuildRequest{
269+
TemplateVersionID: setup.template.ActiveVersionID,
270+
Transition: trans,
271+
RichParameterValues: []codersdk.WorkspaceBuildParameter{
272+
// No validation, so this should work as is.
273+
// Overwrite the value on each transition
274+
{Name: "foo", Value: fooVal},
275+
},
276+
EnableDynamicParameters: ptr.Ref(true),
277+
})
278+
require.NoError(t, err)
279+
coderdtest.AwaitWorkspaceBuildJobCompleted(t, setup.client, wrk.LatestBuild.ID)
280+
281+
latestParams, err := setup.client.WorkspaceBuildParameters(ctx, bld.ID)
282+
require.NoError(t, err)
283+
require.ElementsMatch(t, latestParams, []codersdk.WorkspaceBuildParameter{
284+
{Name: "jetbrains_ide", Value: "GO"},
285+
{Name: "foo", Value: fooVal},
286+
})
287+
}
288+
289+
// Restart the workspace, then delete. Asserting params on all builds.
290+
doTransition(t, codersdk.WorkspaceTransitionStop)
291+
doTransition(t, codersdk.WorkspaceTransitionStart)
292+
doTransition(t, codersdk.WorkspaceTransitionDelete)
293+
})
294+
214295
t.Run("BadOwner", func(t *testing.T) {
215296
t.Parallel()
216297

@@ -266,9 +347,10 @@ type setupDynamicParamsTestParams struct {
266347
}
267348

268349
type dynamicParamsTest struct {
269-
client *codersdk.Client
270-
api *coderd.API
271-
stream *wsjson.Stream[codersdk.DynamicParametersResponse, codersdk.DynamicParametersRequest]
350+
client *codersdk.Client
351+
api *coderd.API
352+
stream *wsjson.Stream[codersdk.DynamicParametersResponse, codersdk.DynamicParametersRequest]
353+
template codersdk.Template
272354
}
273355

274356
func setupDynamicParamsTest(t *testing.T, args setupDynamicParamsTestParams) dynamicParamsTest {
@@ -300,7 +382,7 @@ func setupDynamicParamsTest(t *testing.T, args setupDynamicParamsTestParams) dyn
300382

301383
version := coderdtest.CreateTemplateVersion(t, templateAdmin, owner.OrganizationID, files)
302384
coderdtest.AwaitTemplateVersionJobCompleted(t, templateAdmin, version.ID)
303-
_ = coderdtest.CreateTemplate(t, templateAdmin, owner.OrganizationID, version.ID)
385+
tpl := coderdtest.CreateTemplate(t, templateAdmin, owner.OrganizationID, version.ID)
304386

305387
ctx := testutil.Context(t, testutil.WaitShort)
306388
stream, err := templateAdmin.TemplateVersionDynamicParameters(ctx, version.ID)
@@ -321,9 +403,10 @@ func setupDynamicParamsTest(t *testing.T, args setupDynamicParamsTestParams) dyn
321403
})
322404

323405
return dynamicParamsTest{
324-
client: ownerClient,
325-
stream: stream,
326-
api: api,
406+
client: ownerClient,
407+
api: api,
408+
stream: stream,
409+
template: tpl,
327410
}
328411
}
329412

coderd/wsbuilder/wsbuilder.go

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,11 @@ func (b *Builder) getParameters() (names, values []string, err error) {
623623
return nil, nil, BuildError{http.StatusBadRequest, "Unable to build workspace with unsupported parameters", err}
624624
}
625625

626+
lastBuildParameterValues := db2sdk.WorkspaceBuildParameters(lastBuildParameters)
627+
resolver := codersdk.ParameterResolver{
628+
Rich: lastBuildParameterValues,
629+
}
630+
626631
// Dynamic parameters skip all parameter validation.
627632
// Deleting a workspace also should skip parameter validation.
628633
// Pass the user's input as is.
@@ -632,19 +637,34 @@ func (b *Builder) getParameters() (names, values []string, err error) {
632637
// conditional parameter existence, the static frame of reference
633638
// is not sufficient. So assume the user is correct, or pull in the
634639
// dynamic param code to find the actual parameters.
640+
latestValues := make(map[string]string, len(b.richParameterValues))
641+
for _, latest := range b.richParameterValues {
642+
latestValues[latest.Name] = latest.Value
643+
}
644+
645+
// Merge the inputs with values from the previous build.
646+
for _, last := range lastBuildParameterValues {
647+
// TODO: Ideally we use the resolver here and look at parameter
648+
// fields such as 'ephemeral'. This requires loading the terraform
649+
// files. For now, just send the previous inputs as is.
650+
if _, exists := latestValues[last.Name]; exists {
651+
// latestValues take priority, so skip this previous value.
652+
continue
653+
}
654+
names = append(names, last.Name)
655+
values = append(values, last.Value)
656+
}
657+
635658
for _, value := range b.richParameterValues {
636659
names = append(names, value.Name)
637660
values = append(values, value.Value)
638661
}
662+
639663
b.parameterNames = &names
640664
b.parameterValues = &values
641665
return names, values, nil
642666
}
643667

644-
resolver := codersdk.ParameterResolver{
645-
Rich: db2sdk.WorkspaceBuildParameters(lastBuildParameters),
646-
}
647-
648668
for _, templateVersionParameter := range templateVersionParameters {
649669
tvp, err := db2sdk.TemplateVersionParameter(templateVersionParameter)
650670
if err != nil {

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy