From ed13f76e32bf0735b744358c36377b023bd967f7 Mon Sep 17 00:00:00 2001 From: Asher Date: Mon, 19 Aug 2024 14:52:36 -0800 Subject: [PATCH 1/7] fix: Report multiple organizations as a premium feature --- codersdk/deployment.go | 19 ++++++++++++------- enterprise/coderd/templates.go | 6 +++++- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/codersdk/deployment.go b/codersdk/deployment.go index ef5eefa2344ce..c796a18389ed5 100644 --- a/codersdk/deployment.go +++ b/codersdk/deployment.go @@ -128,6 +128,17 @@ func (n FeatureName) AlwaysEnable() bool { }[n] } +// Premium returns true if the feature is a premium feature. +func (n FeatureName) Premium() bool { + switch n { + // Add all features that should be excluded in the Enterprise feature set. + case FeatureMultipleOrganizations, FeatureCustomRoles: + return true + default: + return false + } +} + // FeatureSet represents a grouping of features. Rather than manually // assigning features al-la-carte when making a license, a set can be specified. // Sets are dynamic in the sense a feature can be added to a set, granting the @@ -152,13 +163,7 @@ func (set FeatureSet) Features() []FeatureName { copy(enterpriseFeatures, FeatureNames) // Remove the selection enterpriseFeatures = slices.DeleteFunc(enterpriseFeatures, func(f FeatureName) bool { - switch f { - // Add all features that should be excluded in the Enterprise feature set. - case FeatureMultipleOrganizations, FeatureCustomRoles: - return true - default: - return false - } + return f.Premium() }) return enterpriseFeatures diff --git a/enterprise/coderd/templates.go b/enterprise/coderd/templates.go index d2f136b821e82..054fae3f729cc 100644 --- a/enterprise/coderd/templates.go +++ b/enterprise/coderd/templates.go @@ -364,8 +364,12 @@ func (api *API) RequireFeatureMW(feat codersdk.FeatureName) func(http.Handler) h enabled := api.entitlements.Features[feat].Enabled api.entitlementsMu.RUnlock() if !enabled { + var licenseType = "an Enterprise" + if feat.Premium() { + licenseType = "a Premium" + } httpapi.Write(r.Context(), rw, http.StatusForbidden, codersdk.Response{ - Message: fmt.Sprintf("%s is an Enterprise feature. Contact sales!", feat.Humanize()), + Message: fmt.Sprintf("%s is %s feature. Contact sales!", licenseType, feat.Humanize()), }) return } From 1685f4e137d8a7b07f16cafdd292fa0ed1b4b501 Mon Sep 17 00:00:00 2001 From: Asher Date: Mon, 19 Aug 2024 15:06:58 -0800 Subject: [PATCH 2/7] fixup! fix: Report multiple organizations as a premium feature --- enterprise/coderd/templates.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enterprise/coderd/templates.go b/enterprise/coderd/templates.go index 054fae3f729cc..dee824e545851 100644 --- a/enterprise/coderd/templates.go +++ b/enterprise/coderd/templates.go @@ -364,7 +364,7 @@ func (api *API) RequireFeatureMW(feat codersdk.FeatureName) func(http.Handler) h enabled := api.entitlements.Features[feat].Enabled api.entitlementsMu.RUnlock() if !enabled { - var licenseType = "an Enterprise" + licenseType := "an Enterprise" if feat.Premium() { licenseType = "a Premium" } From 425adcb2bbe9b68d9cfbe8eb7b67e40781e61754 Mon Sep 17 00:00:00 2001 From: Asher Date: Mon, 19 Aug 2024 15:09:58 -0800 Subject: [PATCH 3/7] fixup! fix: Report multiple organizations as a premium feature --- enterprise/coderd/templates.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enterprise/coderd/templates.go b/enterprise/coderd/templates.go index dee824e545851..2d11293b59c9b 100644 --- a/enterprise/coderd/templates.go +++ b/enterprise/coderd/templates.go @@ -369,7 +369,7 @@ func (api *API) RequireFeatureMW(feat codersdk.FeatureName) func(http.Handler) h licenseType = "a Premium" } httpapi.Write(r.Context(), rw, http.StatusForbidden, codersdk.Response{ - Message: fmt.Sprintf("%s is %s feature. Contact sales!", licenseType, feat.Humanize()), + Message: fmt.Sprintf("%s is %s feature. Contact sales!", feat.Humanize(), licenseType), }) return } From 01d267336d6721f7fd387f258096acc14ddc9ee3 Mon Sep 17 00:00:00 2001 From: Asher Date: Mon, 19 Aug 2024 15:11:20 -0800 Subject: [PATCH 4/7] Flip license check --- codersdk/deployment.go | 10 +++++----- enterprise/coderd/templates.go | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/codersdk/deployment.go b/codersdk/deployment.go index c796a18389ed5..be99ab5388e54 100644 --- a/codersdk/deployment.go +++ b/codersdk/deployment.go @@ -128,14 +128,14 @@ func (n FeatureName) AlwaysEnable() bool { }[n] } -// Premium returns true if the feature is a premium feature. -func (n FeatureName) Premium() bool { +// Enterprise returns true if the feature is an enterprise feature. +func (n FeatureName) Enterprise() bool { switch n { // Add all features that should be excluded in the Enterprise feature set. case FeatureMultipleOrganizations, FeatureCustomRoles: - return true - default: return false + default: + return true } } @@ -163,7 +163,7 @@ func (set FeatureSet) Features() []FeatureName { copy(enterpriseFeatures, FeatureNames) // Remove the selection enterpriseFeatures = slices.DeleteFunc(enterpriseFeatures, func(f FeatureName) bool { - return f.Premium() + return !f.Enterprise() }) return enterpriseFeatures diff --git a/enterprise/coderd/templates.go b/enterprise/coderd/templates.go index 2d11293b59c9b..cf7a34530a048 100644 --- a/enterprise/coderd/templates.go +++ b/enterprise/coderd/templates.go @@ -364,9 +364,9 @@ func (api *API) RequireFeatureMW(feat codersdk.FeatureName) func(http.Handler) h enabled := api.entitlements.Features[feat].Enabled api.entitlementsMu.RUnlock() if !enabled { - licenseType := "an Enterprise" - if feat.Premium() { - licenseType = "a Premium" + licenseType := "a Premium" + if feat.Enterprise() { + licenseType = "an Enterprise" } httpapi.Write(r.Context(), rw, http.StatusForbidden, codersdk.Response{ Message: fmt.Sprintf("%s is %s feature. Contact sales!", feat.Humanize(), licenseType), From 732fefc84b621cab5ffffa2cdac9083efebfdeb1 Mon Sep 17 00:00:00 2001 From: Asher Date: Mon, 19 Aug 2024 15:17:34 -0800 Subject: [PATCH 5/7] Update custom roles test --- enterprise/coderd/roles_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enterprise/coderd/roles_test.go b/enterprise/coderd/roles_test.go index 8c58427a76f0e..8864178211739 100644 --- a/enterprise/coderd/roles_test.go +++ b/enterprise/coderd/roles_test.go @@ -125,7 +125,7 @@ func TestCustomOrganizationRole(t *testing.T) { // Verify functionality is lost _, err = owner.UpdateOrganizationRole(ctx, templateAdminCustom(first.OrganizationID)) - require.ErrorContains(t, err, "Custom Roles is an Enterprise feature") + require.ErrorContains(t, err, "Custom Roles is a Premium feature") // Assign the custom template admin role tmplAdmin, _ := coderdtest.CreateAnotherUser(t, owner, first.OrganizationID, rbac.RoleIdentifier{Name: role.Name, OrganizationID: first.OrganizationID}) From dc1164f39fdecb1b6bc5592298529b6fd7cf842b Mon Sep 17 00:00:00 2001 From: Asher Date: Mon, 19 Aug 2024 15:22:25 -0800 Subject: [PATCH 6/7] Add org patch test for revoked license --- enterprise/coderd/organizations_test.go | 41 +++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/enterprise/coderd/organizations_test.go b/enterprise/coderd/organizations_test.go index 8c4e9daa0d801..f18cef77c0a30 100644 --- a/enterprise/coderd/organizations_test.go +++ b/enterprise/coderd/organizations_test.go @@ -488,6 +488,47 @@ func TestPatchOrganizationsByUser(t *testing.T) { require.Equal(t, displayName, o.DisplayName) // didn't change require.Equal(t, icon, o.Icon) }) + + t.Run("RevokedLicense", func(t *testing.T) { + t.Parallel() + dv := coderdtest.DeploymentValues(t) + dv.Experiments = []string{string(codersdk.ExperimentMultiOrganization)} + client, _ := coderdenttest.New(t, &coderdenttest.Options{ + Options: &coderdtest.Options{ + DeploymentValues: dv, + }, + LicenseOptions: &coderdenttest.LicenseOptions{ + Features: license.Features{ + codersdk.FeatureMultipleOrganizations: 1, + }, + }, + }) + ctx := testutil.Context(t, testutil.WaitMedium) + + const displayName = "New Organization" + var err error + o := coderdenttest.CreateOrganization(t, client, coderdenttest.CreateOrganizationOptions{}, func(request *codersdk.CreateOrganizationRequest) { + request.DisplayName = displayName + request.Icon = "/emojis/random.png" + request.Name = "new-org" + }) + + // Remove the license to block enterprise functionality. + licenses, err := client.Licenses(ctx) + require.NoError(t, err, "get licenses") + for _, license := range licenses { + // Should be only 1... + err := client.DeleteLicense(ctx, license.ID) + require.NoError(t, err, "delete license") + } + + // Verify functionality is lost. + const icon = "/emojis/1f48f-1f3ff.png" + o, err = client.UpdateOrganization(ctx, o.Name, codersdk.UpdateOrganizationRequest{ + Icon: ptr.Ref(icon), + }) + require.ErrorContains(t, err, "Multiple Organizations is a Premium feature") + }) } func TestPostOrganizationsByUser(t *testing.T) { From bfcad583c5ef96d0e2ea00b2ce582df867a309ea Mon Sep 17 00:00:00 2001 From: Asher Date: Mon, 19 Aug 2024 15:32:42 -0800 Subject: [PATCH 7/7] Delete extra var and say premium in comments --- enterprise/coderd/organizations_test.go | 3 +-- enterprise/coderd/roles_test.go | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/enterprise/coderd/organizations_test.go b/enterprise/coderd/organizations_test.go index f18cef77c0a30..512ca9bac2847 100644 --- a/enterprise/coderd/organizations_test.go +++ b/enterprise/coderd/organizations_test.go @@ -506,14 +506,13 @@ func TestPatchOrganizationsByUser(t *testing.T) { ctx := testutil.Context(t, testutil.WaitMedium) const displayName = "New Organization" - var err error o := coderdenttest.CreateOrganization(t, client, coderdenttest.CreateOrganizationOptions{}, func(request *codersdk.CreateOrganizationRequest) { request.DisplayName = displayName request.Icon = "/emojis/random.png" request.Name = "new-org" }) - // Remove the license to block enterprise functionality. + // Remove the license to block premium functionality. licenses, err := client.Licenses(ctx) require.NoError(t, err, "get licenses") for _, license := range licenses { diff --git a/enterprise/coderd/roles_test.go b/enterprise/coderd/roles_test.go index 8864178211739..c919ecf00f780 100644 --- a/enterprise/coderd/roles_test.go +++ b/enterprise/coderd/roles_test.go @@ -114,7 +114,7 @@ func TestCustomOrganizationRole(t *testing.T) { role, err := owner.CreateOrganizationRole(ctx, templateAdminCustom(first.OrganizationID)) require.NoError(t, err, "upsert role") - // Remove the license to block enterprise functionality + // Remove the license to block premium functionality licenses, err := owner.Licenses(ctx) require.NoError(t, err, "get licenses") for _, license := range licenses {