diff --git a/enterprise/coderd/coderd.go b/enterprise/coderd/coderd.go index 9a7b1f318f..3875c83797 100644 --- a/enterprise/coderd/coderd.go +++ b/enterprise/coderd/coderd.go @@ -460,7 +460,7 @@ func New(ctx context.Context, options *Options) (_ *API, err error) { }) r.Route("/templates/{template}/prebuilds", func(r chi.Router) { r.Use( - api.templateRBACEnabledMW, + api.RequireFeatureMW(codersdk.FeatureWorkspacePrebuilds), apiKeyMiddleware, httpmw.ExtractTemplateParam(api.Database), ) diff --git a/enterprise/coderd/templates.go b/enterprise/coderd/templates.go index ff74d9035c..5d0f4bab45 100644 --- a/enterprise/coderd/templates.go +++ b/enterprise/coderd/templates.go @@ -378,7 +378,12 @@ func (api *API) postInvalidateTemplatePresets(rw http.ResponseWriter, r *http.Re slog.F("preset_count", len(invalidatedPresets)), ) + invalidated := db2sdk.InvalidatedPresets(invalidatedPresets) + if invalidated == nil { + invalidated = []codersdk.InvalidatedPreset{} // need to avoid nil value + } + httpapi.Write(ctx, rw, http.StatusOK, codersdk.InvalidatePresetsResponse{ - Invalidated: db2sdk.InvalidatedPresets(invalidatedPresets), + Invalidated: invalidated, }) } diff --git a/enterprise/coderd/templates_test.go b/enterprise/coderd/templates_test.go index f9c431b644..e5e9708571 100644 --- a/enterprise/coderd/templates_test.go +++ b/enterprise/coderd/templates_test.go @@ -3,6 +3,7 @@ package coderd_test import ( "bytes" "context" + "errors" "net/http" "slices" "testing" @@ -2154,7 +2155,7 @@ func TestInvalidateTemplatePrebuilds(t *testing.T) { }, LicenseOptions: &coderdenttest.LicenseOptions{ Features: license.Features{ - codersdk.FeatureTemplateRBAC: 1, + codersdk.FeatureWorkspacePrebuilds: 1, }, }, }) @@ -2208,3 +2209,121 @@ func TestInvalidateTemplatePrebuilds(t *testing.T) { require.Equal(t, codersdk.InvalidatedPreset{TemplateName: template.Name, TemplateVersionName: version2.Name, PresetName: presetWithParameters2.Name}, invalidated.Invalidated[0]) require.Equal(t, codersdk.InvalidatedPreset{TemplateName: template.Name, TemplateVersionName: version2.Name, PresetName: presetWithParameters3.Name}, invalidated.Invalidated[1]) } + +func TestInvalidateTemplatePrebuilds_RegularUser(t *testing.T) { + t.Parallel() + + // Given the following parameters and presets... + templateVersionParameters := []*proto.RichParameter{ + {Name: "param1", Type: "string", Required: false, DefaultValue: "default1"}, + } + presetWithParameters1 := &proto.Preset{ + Name: "Preset With Parameters 1", + Parameters: []*proto.PresetParameter{ + {Name: "param1", Value: "value1"}, + }, + } + + ownerClient, owner := coderdenttest.New(t, &coderdenttest.Options{ + Options: &coderdtest.Options{ + IncludeProvisionerDaemon: true, + }, + LicenseOptions: &coderdenttest.LicenseOptions{ + Features: license.Features{ + codersdk.FeatureWorkspacePrebuilds: 1, + }, + }, + }) + regularUserClient, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID) + + // Given + version1 := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, &echo.Responses{ + Parse: echo.ParseComplete, + ProvisionPlan: []*proto.Response{ + { + Type: &proto.Response_Plan{ + Plan: &proto.PlanComplete{ + Presets: []*proto.Preset{presetWithParameters1}, + Parameters: templateVersionParameters, + }, + }, + }, + }, + ProvisionApply: echo.ApplyComplete, + }) + coderdtest.AwaitTemplateVersionJobCompleted(t, ownerClient, version1.ID) + template := coderdtest.CreateTemplate(t, ownerClient, owner.OrganizationID, version1.ID) + + // When + ctx := testutil.Context(t, testutil.WaitShort) + _, err := regularUserClient.InvalidateTemplatePresets(ctx, template.ID) + + // Then + require.Error(t, err, "regular user cannot invalidate presets") + var sdkError *codersdk.Error + require.True(t, errors.As(err, &sdkError)) + require.ErrorAs(t, err, &sdkError) + require.Equal(t, http.StatusNotFound, sdkError.StatusCode()) +} + +func TestInvalidateTemplatePrebuilds_NoPresets(t *testing.T) { + t.Parallel() + + // Given the template versions and template... + ownerClient, owner := coderdenttest.New(t, &coderdenttest.Options{ + Options: &coderdtest.Options{ + IncludeProvisionerDaemon: true, + }, + LicenseOptions: &coderdenttest.LicenseOptions{ + Features: license.Features{ + codersdk.FeatureWorkspacePrebuilds: 1, + }, + }, + }) + templateAdminClient, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID, rbac.RoleTemplateAdmin()) + + version1 := coderdtest.CreateTemplateVersion(t, templateAdminClient, owner.OrganizationID, &echo.Responses{ + Parse: echo.ParseComplete, ProvisionPlan: echo.PlanComplete, ProvisionApply: echo.ApplyComplete, + }) + coderdtest.AwaitTemplateVersionJobCompleted(t, templateAdminClient, version1.ID) + template := coderdtest.CreateTemplate(t, templateAdminClient, owner.OrganizationID, version1.ID) + + // When + ctx := testutil.Context(t, testutil.WaitLong) + invalidated, err := templateAdminClient.InvalidateTemplatePresets(ctx, template.ID) + require.NoError(t, err) + + // Then + require.NotNil(t, invalidated.Invalidated) + require.Len(t, invalidated.Invalidated, 0) +} + +func TestInvalidateTemplatePrebuilds_LicenseFeatureDisabled(t *testing.T) { + t.Parallel() + + // Given the template versions and template... + ownerClient, owner := coderdenttest.New(t, &coderdenttest.Options{ + Options: &coderdtest.Options{ + IncludeProvisionerDaemon: true, + }, + LicenseOptions: &coderdenttest.LicenseOptions{}, + }) + templateAdminClient, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID, rbac.RoleTemplateAdmin()) + + version1 := coderdtest.CreateTemplateVersion(t, templateAdminClient, owner.OrganizationID, &echo.Responses{ + Parse: echo.ParseComplete, ProvisionPlan: echo.PlanComplete, ProvisionApply: echo.ApplyComplete, + }) + coderdtest.AwaitTemplateVersionJobCompleted(t, templateAdminClient, version1.ID) + template := coderdtest.CreateTemplate(t, templateAdminClient, owner.OrganizationID, version1.ID) + + // When + ctx := testutil.Context(t, testutil.WaitLong) + _, err := templateAdminClient.InvalidateTemplatePresets(ctx, template.ID) + + // Then + require.Error(t, err, "license feature prebuilds is required") + var sdkError *codersdk.Error + require.True(t, errors.As(err, &sdkError)) + require.ErrorAs(t, err, &sdkError) + require.Equal(t, http.StatusForbidden, sdkError.StatusCode()) +}