Skip to content

Commit 414bacf

Browse files
committed
add support for presets to coder provisioners
1 parent 39a33ec commit 414bacf

File tree

56 files changed

+2415
-1098
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+2415
-1098
lines changed

coderd/presets_test.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ import (
1515
)
1616

1717
func TestTemplateVersionPresets(t *testing.T) {
18-
// TODO (sasswart): Test case: what if a user tries to read presets or preset parameters from a different org?
19-
2018
t.Parallel()
2119

2220
givenPreset := codersdk.Preset{

coderd/provisionerdserver/provisionerdserver.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1340,6 +1340,11 @@ func (s *server) CompleteJob(ctx context.Context, completed *proto.CompletedJob)
13401340
}
13411341
}
13421342

1343+
err = InsertWorkspacePresetsAndParameters(ctx, s.Logger, s.Database, jobID, input.TemplateVersionID, jobType.TemplateImport.Presets, s.timeNow())
1344+
if err != nil {
1345+
return nil, xerrors.Errorf("insert workspace presets and parameters: %w", err)
1346+
}
1347+
13431348
var completedError sql.NullString
13441349

13451350
for _, externalAuthProvider := range jobType.TemplateImport.ExternalAuthProviders {
@@ -1809,6 +1814,52 @@ func InsertWorkspaceModule(ctx context.Context, db database.Store, jobID uuid.UU
18091814
return nil
18101815
}
18111816

1817+
func InsertWorkspacePresetsAndParameters(ctx context.Context, logger slog.Logger, db database.Store, jobID uuid.UUID, templateVersionID uuid.UUID, protoPresets []*sdkproto.Preset, t time.Time) error {
1818+
for _, preset := range protoPresets {
1819+
logger.Info(ctx, "inserting template import job preset",
1820+
slog.F("job_id", jobID.String()),
1821+
slog.F("preset_name", preset.Name),
1822+
)
1823+
if err := InsertWorkspacePresetAndParameters(ctx, db, templateVersionID, preset, t); err != nil {
1824+
return xerrors.Errorf("insert workspace preset: %w", err)
1825+
}
1826+
}
1827+
return nil
1828+
}
1829+
1830+
func InsertWorkspacePresetAndParameters(ctx context.Context, db database.Store, templateVersionID uuid.UUID, protoPreset *sdkproto.Preset, t time.Time) error {
1831+
err := db.InTx(func(tx database.Store) error {
1832+
dbPreset, err := tx.InsertPreset(ctx, database.InsertPresetParams{
1833+
TemplateVersionID: templateVersionID,
1834+
Name: protoPreset.Name,
1835+
CreatedAt: t,
1836+
})
1837+
if err != nil {
1838+
return xerrors.Errorf("insert preset: %w", err)
1839+
}
1840+
1841+
var presetParameterNames []string
1842+
var presetParameterValues []string
1843+
for _, parameter := range protoPreset.Parameters {
1844+
presetParameterNames = append(presetParameterNames, parameter.Name)
1845+
presetParameterValues = append(presetParameterValues, parameter.Value)
1846+
}
1847+
_, err = tx.InsertPresetParameters(ctx, database.InsertPresetParametersParams{
1848+
TemplateVersionPresetID: dbPreset.ID,
1849+
Names: presetParameterNames,
1850+
Values: presetParameterValues,
1851+
})
1852+
if err != nil {
1853+
return xerrors.Errorf("insert preset parameters: %w", err)
1854+
}
1855+
return nil
1856+
}, nil)
1857+
if err != nil {
1858+
return xerrors.Errorf("insert preset and parameters: %w", err)
1859+
}
1860+
return nil
1861+
}
1862+
18121863
func InsertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid.UUID, transition database.WorkspaceTransition, protoResource *sdkproto.Resource, snapshot *telemetry.Snapshot) error {
18131864
resource, err := db.InsertWorkspaceResource(ctx, database.InsertWorkspaceResourceParams{
18141865
ID: uuid.New(),

coderd/provisionerdserver/provisionerdserver_test.go

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"github.com/coder/coder/v2/coderd/database"
3131
"github.com/coder/coder/v2/coderd/database/dbgen"
3232
"github.com/coder/coder/v2/coderd/database/dbmem"
33+
"github.com/coder/coder/v2/coderd/database/dbtestutil"
3334
"github.com/coder/coder/v2/coderd/database/dbtime"
3435
"github.com/coder/coder/v2/coderd/database/pubsub"
3536
"github.com/coder/coder/v2/coderd/externalauth"
@@ -1708,6 +1709,155 @@ func TestCompleteJob(t *testing.T) {
17081709
})
17091710
}
17101711

1712+
func TestInsertWorkspacePresetsAndParameters(t *testing.T) {
1713+
t.Parallel()
1714+
1715+
type testCase struct {
1716+
name string
1717+
givenPresets []*sdkproto.Preset
1718+
}
1719+
1720+
testCases := []testCase{
1721+
{
1722+
name: "no presets",
1723+
},
1724+
{
1725+
name: "one preset with no parameters",
1726+
givenPresets: []*sdkproto.Preset{
1727+
{
1728+
Name: "preset1",
1729+
},
1730+
},
1731+
},
1732+
{
1733+
name: "one preset with multiple parameters",
1734+
givenPresets: []*sdkproto.Preset{
1735+
{
1736+
Name: "preset1",
1737+
Parameters: []*sdkproto.PresetParameter{
1738+
{
1739+
Name: "param1",
1740+
Value: "value1",
1741+
},
1742+
{
1743+
Name: "param2",
1744+
Value: "value2",
1745+
},
1746+
},
1747+
},
1748+
},
1749+
},
1750+
{
1751+
name: "multiple presets with parameters",
1752+
givenPresets: []*sdkproto.Preset{
1753+
{
1754+
Name: "preset1",
1755+
Parameters: []*sdkproto.PresetParameter{
1756+
{
1757+
Name: "param1",
1758+
Value: "value1",
1759+
},
1760+
{
1761+
Name: "param2",
1762+
Value: "value2",
1763+
},
1764+
},
1765+
},
1766+
{
1767+
Name: "preset2",
1768+
Parameters: []*sdkproto.PresetParameter{
1769+
{
1770+
Name: "param3",
1771+
Value: "value3",
1772+
},
1773+
{
1774+
Name: "param4",
1775+
Value: "value4",
1776+
},
1777+
},
1778+
},
1779+
},
1780+
},
1781+
}
1782+
1783+
for _, c := range testCases {
1784+
c := c
1785+
t.Run(c.name, func(t *testing.T) {
1786+
t.Parallel()
1787+
1788+
ctx := context.Background()
1789+
logger := testutil.Logger(t)
1790+
db, ps := dbtestutil.NewDB(t)
1791+
org := dbgen.Organization(t, db, database.Organization{})
1792+
user := dbgen.User(t, db, database.User{})
1793+
job := dbgen.ProvisionerJob(t, db, ps, database.ProvisionerJob{
1794+
Type: database.ProvisionerJobTypeWorkspaceBuild,
1795+
OrganizationID: org.ID,
1796+
})
1797+
templateVersion := dbgen.TemplateVersion(t, db, database.TemplateVersion{
1798+
JobID: job.ID,
1799+
OrganizationID: org.ID,
1800+
CreatedBy: user.ID,
1801+
})
1802+
1803+
err := provisionerdserver.InsertWorkspacePresetsAndParameters(
1804+
ctx,
1805+
logger,
1806+
db,
1807+
job.ID,
1808+
templateVersion.ID,
1809+
c.givenPresets,
1810+
time.Now(),
1811+
)
1812+
require.NoError(t, err)
1813+
1814+
gotPresets, err := db.GetPresetsByTemplateVersionID(ctx, templateVersion.ID)
1815+
require.NoError(t, err)
1816+
require.Len(t, gotPresets, len(c.givenPresets))
1817+
1818+
for _, givenPreset := range c.givenPresets {
1819+
foundMatch := false
1820+
for _, gotPreset := range gotPresets {
1821+
if givenPreset.Name == gotPreset.Name {
1822+
foundMatch = true
1823+
break
1824+
}
1825+
}
1826+
require.True(t, foundMatch, "preset %s not found in parameters", givenPreset.Name)
1827+
}
1828+
1829+
gotPresetParameters, err := db.GetPresetParametersByTemplateVersionID(ctx, templateVersion.ID)
1830+
require.NoError(t, err)
1831+
1832+
for _, givenPreset := range c.givenPresets {
1833+
for _, givenParameter := range givenPreset.Parameters {
1834+
foundMatch := false
1835+
for _, gotParameter := range gotPresetParameters {
1836+
nameMatches := givenParameter.Name == gotParameter.Name
1837+
valueMatches := givenParameter.Value == gotParameter.Value
1838+
1839+
// ensure that preset parameters are matched to the correct preset:
1840+
var gotPreset database.TemplateVersionPreset
1841+
for _, preset := range gotPresets {
1842+
if preset.ID == gotParameter.TemplateVersionPresetID {
1843+
gotPreset = preset
1844+
break
1845+
}
1846+
}
1847+
presetMatches := gotPreset.Name == givenPreset.Name
1848+
1849+
if nameMatches && valueMatches && presetMatches {
1850+
foundMatch = true
1851+
break
1852+
}
1853+
}
1854+
require.True(t, foundMatch, "preset parameter %s not found in presets", givenParameter.Name)
1855+
}
1856+
}
1857+
})
1858+
}
1859+
}
1860+
17111861
func TestInsertWorkspaceResource(t *testing.T) {
17121862
t.Parallel()
17131863
ctx := context.Background()

go.mod

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ require (
9494
github.com/coder/quartz v0.1.2
9595
github.com/coder/retry v1.5.1
9696
github.com/coder/serpent v0.10.0
97-
github.com/coder/terraform-provider-coder v1.0.5-0.20250131073245-5b9a30ca496b
97+
github.com/coder/terraform-provider-coder v1.0.4
9898
github.com/coder/websocket v1.8.12
9999
github.com/coder/wgtunnel v0.1.13-0.20240522110300-ade90dfb2da0
100100
github.com/coreos/go-oidc/v3 v3.12.0
@@ -467,3 +467,5 @@ require (
467467
kernel.org/pub/linux/libs/security/libcap/psx v1.2.73 // indirect
468468
sigs.k8s.io/yaml v1.4.0 // indirect
469469
)
470+
471+
require github.com/coder/terraform-provider-coder/v2 v2.1.3

go.sum

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -240,8 +240,10 @@ github.com/coder/tailscale v1.1.1-0.20250129014916-8086c871eae6 h1:prDIwUcsSEKbs
240240
github.com/coder/tailscale v1.1.1-0.20250129014916-8086c871eae6/go.mod h1:1ggFFdHTRjPRu9Yc1yA7nVHBYB50w9Ce7VIXNqcW6Ko=
241241
github.com/coder/terraform-config-inspect v0.0.0-20250107175719-6d06d90c630e h1:JNLPDi2P73laR1oAclY6jWzAbucf70ASAvf5mh2cME0=
242242
github.com/coder/terraform-config-inspect v0.0.0-20250107175719-6d06d90c630e/go.mod h1:Gz/z9Hbn+4KSp8A2FBtNszfLSdT2Tn/uAKGuVqqWmDI=
243-
github.com/coder/terraform-provider-coder v1.0.5-0.20250131073245-5b9a30ca496b h1:Z9ssmlGrbf+mRIiyRzQj1P6vH8drKOqgzeTG6D0Ldjg=
244-
github.com/coder/terraform-provider-coder v1.0.5-0.20250131073245-5b9a30ca496b/go.mod h1:dQ1e/IccUxnmh/1bXTA3PopSoBkHMyWT6EkdBw8Lx6Y=
243+
github.com/coder/terraform-provider-coder v1.0.4 h1:MJldCvykIQzzqBVUDjCJpPyqvKelAAHrtJKfIIx4Qxo=
244+
github.com/coder/terraform-provider-coder v1.0.4/go.mod h1:dQ1e/IccUxnmh/1bXTA3PopSoBkHMyWT6EkdBw8Lx6Y=
245+
github.com/coder/terraform-provider-coder/v2 v2.1.3 h1:zB7ObGsiOGBHcJUUMmcSauEPlTWRIYmMYieF05LxHSc=
246+
github.com/coder/terraform-provider-coder/v2 v2.1.3/go.mod h1:RHGyb+ghiy8UpDAMJM8duRFuzd+1VqA3AtkRLh2P3Ug=
245247
github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo=
246248
github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
247249
github.com/coder/wgtunnel v0.1.13-0.20240522110300-ade90dfb2da0 h1:C2/eCr+r0a5Auuw3YOiSyLNHkdMtyCZHPFBx7syN4rk=

provisioner/terraform/executor.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,7 @@ func (e *executor) plan(ctx, killCtx context.Context, env, vars []string, logr l
308308
Resources: state.Resources,
309309
ExternalAuthProviders: state.ExternalAuthProviders,
310310
Timings: append(e.timings.aggregate(), graphTimings.aggregate()...),
311+
Presets: state.Presets,
311312
}, nil
312313
}
313314

provisioner/terraform/resources.go

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212

1313
"cdr.dev/slog"
1414

15-
"github.com/coder/terraform-provider-coder/provider"
15+
"github.com/coder/terraform-provider-coder/v2/provider"
1616

1717
tfaddr "github.com/hashicorp/go-terraform-address"
1818

@@ -149,6 +149,7 @@ type resourceMetadataItem struct {
149149
type State struct {
150150
Resources []*proto.Resource
151151
Parameters []*proto.RichParameter
152+
Presets []*proto.Preset
152153
ExternalAuthProviders []*proto.ExternalAuthProviderResource
153154
}
154155

@@ -176,7 +177,7 @@ func ConvertState(ctx context.Context, modules []*tfjson.StateModule, rawGraph s
176177

177178
// Extra array to preserve the order of rich parameters.
178179
tfResourcesRichParameters := make([]*tfjson.StateResource, 0)
179-
180+
tfResourcesPresets := make([]*tfjson.StateResource, 0)
180181
var findTerraformResources func(mod *tfjson.StateModule)
181182
findTerraformResources = func(mod *tfjson.StateModule) {
182183
for _, module := range mod.ChildModules {
@@ -186,6 +187,9 @@ func ConvertState(ctx context.Context, modules []*tfjson.StateModule, rawGraph s
186187
if resource.Type == "coder_parameter" {
187188
tfResourcesRichParameters = append(tfResourcesRichParameters, resource)
188189
}
190+
if resource.Type == "coder_workspace_preset" {
191+
tfResourcesPresets = append(tfResourcesPresets, resource)
192+
}
189193

190194
label := convertAddressToLabel(resource.Address)
191195
if tfResourcesByLabel[label] == nil {
@@ -775,6 +779,79 @@ func ConvertState(ctx context.Context, modules []*tfjson.StateModule, rawGraph s
775779
)
776780
}
777781

782+
var duplicatedPresetNames []string
783+
presets := make([]*proto.Preset, 0)
784+
for _, resource := range tfResourcesPresets {
785+
var preset provider.WorkspacePreset
786+
err = mapstructure.Decode(resource.AttributeValues, &preset)
787+
if err != nil {
788+
return nil, xerrors.Errorf("decode preset attributes: %w", err)
789+
}
790+
791+
var duplicatedPresetParameterNames []string
792+
var nonExistentParameters []string
793+
var presetParameters []*proto.PresetParameter
794+
for name, value := range preset.Parameters {
795+
presetParameter := &proto.PresetParameter{
796+
Name: name,
797+
Value: value,
798+
}
799+
800+
formattedName := fmt.Sprintf("%q", name)
801+
if !slice.Contains(duplicatedPresetParameterNames, formattedName) &&
802+
slice.ContainsCompare(presetParameters, presetParameter, func(a, b *proto.PresetParameter) bool {
803+
return a.Name == b.Name
804+
}) {
805+
duplicatedPresetParameterNames = append(duplicatedPresetParameterNames, formattedName)
806+
}
807+
if !slice.ContainsCompare(parameters, &proto.RichParameter{Name: name}, func(a, b *proto.RichParameter) bool {
808+
return a.Name == b.Name
809+
}) {
810+
nonExistentParameters = append(nonExistentParameters, name)
811+
}
812+
813+
presetParameters = append(presetParameters, presetParameter)
814+
}
815+
816+
if len(duplicatedPresetParameterNames) > 0 {
817+
s := ""
818+
if len(duplicatedPresetParameterNames) == 1 {
819+
s = "s"
820+
}
821+
return nil, xerrors.Errorf(
822+
"coder_workspace_preset parameters must be unique but %s appear%s multiple times", stringutil.JoinWithConjunction(duplicatedPresetParameterNames), s,
823+
)
824+
}
825+
826+
if len(nonExistentParameters) > 0 {
827+
// TODO (sasswart): should this be an error? Or should we just log it?
828+
logger.Warn(
829+
ctx,
830+
"coder_workspace_preset defines preset values for at least one parameter that is not defined by the template",
831+
slog.F("parameters", stringutil.JoinWithConjunction(nonExistentParameters)),
832+
)
833+
}
834+
835+
protoPreset := &proto.Preset{
836+
Name: preset.Name,
837+
Parameters: presetParameters,
838+
}
839+
if slice.Contains(duplicatedPresetNames, preset.Name) {
840+
duplicatedPresetNames = append(duplicatedPresetNames, preset.Name)
841+
}
842+
presets = append(presets, protoPreset)
843+
}
844+
if len(duplicatedPresetNames) > 0 {
845+
s := ""
846+
if len(duplicatedPresetNames) == 1 {
847+
s = "s"
848+
}
849+
return nil, xerrors.Errorf(
850+
"coder_workspace_preset names must be unique but %s appear%s multiple times",
851+
stringutil.JoinWithConjunction(duplicatedPresetNames), s,
852+
)
853+
}
854+
778855
// A map is used to ensure we don't have duplicates!
779856
externalAuthProvidersMap := map[string]*proto.ExternalAuthProviderResource{}
780857
for _, tfResources := range tfResourcesByLabel {
@@ -808,6 +885,7 @@ func ConvertState(ctx context.Context, modules []*tfjson.StateModule, rawGraph s
808885
return &State{
809886
Resources: resources,
810887
Parameters: parameters,
888+
Presets: presets,
811889
ExternalAuthProviders: externalAuthProviders,
812890
}, nil
813891
}

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