From 85499af2e2aa4cca43f0321aae318fa5b616c706 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Fri, 21 Jun 2024 11:29:31 -0500 Subject: [PATCH 1/2] chore: add coderdtest helpers --- coderd/coderdtest/coderdtest.go | 44 ++++++++++++++++++ enterprise/cli/templatecreate_test.go | 66 +++++++++++++++++++++++++++ enterprise/coderd/templates_test.go | 50 ++++++++++++++++++++ 3 files changed, 160 insertions(+) diff --git a/coderd/coderdtest/coderdtest.go b/coderd/coderdtest/coderdtest.go index 49388aa3537a5..d4e473a17a610 100644 --- a/coderd/coderdtest/coderdtest.go +++ b/coderd/coderdtest/coderdtest.go @@ -600,6 +600,18 @@ func NewTaggedProvisionerDaemon(t testing.TB, coderAPI *coderd.API, name string, } func NewExternalProvisionerDaemon(t testing.TB, client *codersdk.Client, org uuid.UUID, tags map[string]string) io.Closer { + t.Helper() + + // Without this check, the provisioner will silently fail. + entitlements, err := client.Entitlements(context.Background()) + if err == nil { + feature := entitlements.Features[codersdk.FeatureExternalProvisionerDaemons] + if !feature.Enabled || feature.Entitlement != codersdk.EntitlementEntitled { + require.NoError(t, xerrors.Errorf("external provisioner daemons require an entitled license")) + return nil + } + } + echoClient, echoServer := drpc.MemTransportPipe() ctx, cancelFunc := context.WithCancel(context.Background()) serveDone := make(chan struct{}) @@ -638,6 +650,7 @@ func NewExternalProvisionerDaemon(t testing.TB, client *codersdk.Client, org uui t.Cleanup(func() { _ = closer.Close() }) + return closer } @@ -790,6 +803,37 @@ func createAnotherUserRetry(t testing.TB, client *codersdk.Client, organizationI return other, user } +type CreateOrganizationOptions struct { + // IncludeProvisionerDaemon will spin up an external provisioner for the organization. + // This requires enterprise and the feature 'codersdk.FeatureExternalProvisionerDaemons' + IncludeProvisionerDaemon bool +} + +func CreateOrganization(t *testing.T, client *codersdk.Client, opts CreateOrganizationOptions, mutators ...func(*codersdk.CreateOrganizationRequest)) codersdk.Organization { + ctx := testutil.Context(t, testutil.WaitMedium) + req := codersdk.CreateOrganizationRequest{ + Name: strings.ReplaceAll(strings.ToLower(namesgenerator.GetRandomName(0)), "_", "-"), + DisplayName: namesgenerator.GetRandomName(1), + Description: namesgenerator.GetRandomName(1), + Icon: "", + } + for _, mutator := range mutators { + mutator(&req) + } + + org, err := client.CreateOrganization(ctx, req) + require.NoError(t, err) + + if opts.IncludeProvisionerDaemon { + closer := NewExternalProvisionerDaemon(t, client, org.ID, map[string]string{}) + t.Cleanup(func() { + _ = closer.Close() + }) + } + + return org +} + // CreateTemplateVersion creates a template import provisioner job // with the responses provided. It uses the "echo" provisioner for compatibility // with testing. diff --git a/enterprise/cli/templatecreate_test.go b/enterprise/cli/templatecreate_test.go index 987cac0b93058..ddbbabe1ce769 100644 --- a/enterprise/cli/templatecreate_test.go +++ b/enterprise/cli/templatecreate_test.go @@ -134,4 +134,70 @@ func TestTemplateCreate(t *testing.T) { require.Error(t, err) require.Contains(t, err.Error(), "your license is not entitled to use enterprise access control, so you cannot set --require-active-version") }) + + // Create a template in a second organization via custom role + t.Run("SecondOrganization", func(t *testing.T) { + t.Parallel() + + dv := coderdtest.DeploymentValues(t) + dv.Experiments = []string{string(codersdk.ExperimentCustomRoles)} + ownerClient, _ := coderdenttest.New(t, &coderdenttest.Options{ + Options: &coderdtest.Options{ + DeploymentValues: dv, + // This only affects the first org. + IncludeProvisionerDaemon: false, + }, + LicenseOptions: &coderdenttest.LicenseOptions{ + Features: license.Features{ + codersdk.FeatureAccessControl: 1, + codersdk.FeatureCustomRoles: 1, + codersdk.FeatureExternalProvisionerDaemons: 1, + }, + }, + }) + + // Create the second organization + secondOrg := coderdtest.CreateOrganization(t, ownerClient, coderdtest.CreateOrganizationOptions{ + IncludeProvisionerDaemon: true, + }) + + ctx := testutil.Context(t, testutil.WaitMedium) + + orgTemplateAdminRole, err := ownerClient.PatchOrganizationRole(ctx, secondOrg.ID, codersdk.Role{ + Name: "org-template-admin", + OrganizationID: secondOrg.ID.String(), + OrganizationPermissions: codersdk.CreatePermissions(map[codersdk.RBACResource][]codersdk.RBACAction{ + codersdk.ResourceTemplate: codersdk.RBACResourceActions[codersdk.ResourceTemplate], + }), + }) + require.NoError(t, err, "create admin role") + + orgTemplateAdmin, _ := coderdtest.CreateAnotherUser(t, ownerClient, secondOrg.ID, rbac.RoleIdentifier{ + Name: orgTemplateAdminRole.Name, + OrganizationID: secondOrg.ID, + }) + + source := clitest.CreateTemplateVersionSource(t, &echo.Responses{ + Parse: echo.ParseComplete, + ProvisionApply: echo.ApplyComplete, + }) + + const templateName = "new-template" + inv, conf := newCLI(t, "templates", + "push", templateName, + "--directory", source, + "--test.provisioner", string(database.ProvisionerTypeEcho), + "-y", + ) + + clitest.SetupConfig(t, orgTemplateAdmin, conf) + + err = inv.Run() + require.NoError(t, err) + + ctx = testutil.Context(t, testutil.WaitMedium) + template, err := orgTemplateAdmin.TemplateByName(ctx, secondOrg.ID, templateName) + require.NoError(t, err) + require.Equal(t, template.OrganizationID, secondOrg.ID) + }) } diff --git a/enterprise/coderd/templates_test.go b/enterprise/coderd/templates_test.go index 7440ee743dca2..2eaddabe5442a 100644 --- a/enterprise/coderd/templates_test.go +++ b/enterprise/coderd/templates_test.go @@ -717,6 +717,56 @@ func TestTemplates(t *testing.T) { _, err = owner.Template(ctx, template.ID) require.NoError(t, err) }) + + // Create a template in a second organization via custom role + t.Run("SecondOrganization", func(t *testing.T) { + t.Parallel() + + dv := coderdtest.DeploymentValues(t) + dv.Experiments = []string{string(codersdk.ExperimentCustomRoles)} + ownerClient, _ := coderdenttest.New(t, &coderdenttest.Options{ + Options: &coderdtest.Options{ + DeploymentValues: dv, + IncludeProvisionerDaemon: false, + }, + LicenseOptions: &coderdenttest.LicenseOptions{ + Features: license.Features{ + codersdk.FeatureAccessControl: 1, + codersdk.FeatureCustomRoles: 1, + codersdk.FeatureExternalProvisionerDaemons: 1, + }, + }, + }) + + ctx := testutil.Context(t, testutil.WaitMedium) + secondOrg := coderdtest.CreateOrganization(t, ownerClient, coderdtest.CreateOrganizationOptions{ + IncludeProvisionerDaemon: true, + }) + + orgTemplateAdminRole, err := ownerClient.PatchOrganizationRole(ctx, secondOrg.ID, codersdk.Role{ + Name: "org-template-admin", + OrganizationID: secondOrg.ID.String(), + OrganizationPermissions: codersdk.CreatePermissions(map[codersdk.RBACResource][]codersdk.RBACAction{ + codersdk.ResourceTemplate: codersdk.RBACResourceActions[codersdk.ResourceTemplate], + }), + }) + require.NoError(t, err, "create admin role") + + orgTemplateAdmin, _ := coderdtest.CreateAnotherUser(t, ownerClient, secondOrg.ID, rbac.RoleIdentifier{ + Name: orgTemplateAdminRole.Name, + OrganizationID: secondOrg.ID, + }) + + version := coderdtest.CreateTemplateVersion(t, orgTemplateAdmin, secondOrg.ID, &echo.Responses{ + Parse: echo.ParseComplete, + ProvisionApply: echo.ApplyComplete, + ProvisionPlan: echo.PlanComplete, + }) + coderdtest.AwaitTemplateVersionJobCompleted(t, orgTemplateAdmin, version.ID) + + template := coderdtest.CreateTemplate(t, orgTemplateAdmin, secondOrg.ID, version.ID) + require.Equal(t, template.OrganizationID, secondOrg.ID) + }) } func TestTemplateACL(t *testing.T) { From 956b5a297b09a2be9aa79c6c753d93ea41c41972 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Fri, 21 Jun 2024 12:06:21 -0500 Subject: [PATCH 2/2] linting --- enterprise/cli/templatecreate_test.go | 1 + enterprise/coderd/templates_test.go | 1 + 2 files changed, 2 insertions(+) diff --git a/enterprise/cli/templatecreate_test.go b/enterprise/cli/templatecreate_test.go index ddbbabe1ce769..3f089a62622a6 100644 --- a/enterprise/cli/templatecreate_test.go +++ b/enterprise/cli/templatecreate_test.go @@ -163,6 +163,7 @@ func TestTemplateCreate(t *testing.T) { ctx := testutil.Context(t, testutil.WaitMedium) + //nolint:gocritic // owner required to make custom roles orgTemplateAdminRole, err := ownerClient.PatchOrganizationRole(ctx, secondOrg.ID, codersdk.Role{ Name: "org-template-admin", OrganizationID: secondOrg.ID.String(), diff --git a/enterprise/coderd/templates_test.go b/enterprise/coderd/templates_test.go index 2eaddabe5442a..80000f2eb22b4 100644 --- a/enterprise/coderd/templates_test.go +++ b/enterprise/coderd/templates_test.go @@ -743,6 +743,7 @@ func TestTemplates(t *testing.T) { IncludeProvisionerDaemon: true, }) + //nolint:gocritic // owner required to make custom roles orgTemplateAdminRole, err := ownerClient.PatchOrganizationRole(ctx, secondOrg.ID, codersdk.Role{ Name: "org-template-admin", OrganizationID: secondOrg.ID.String(),