From 355f3c0cbebf0a9781aab65fd005dd1d5782811f Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Mon, 16 Jan 2023 21:25:18 +0000 Subject: [PATCH 01/13] refactor: deduplicate / type license feature code --- codersdk/features.go | 42 ++++++++++------- codersdk/licenses.go | 25 ++++++++++ enterprise/coderd/coderd.go | 4 +- enterprise/coderd/license/license.go | 7 ++- enterprise/coderd/license/license_test.go | 37 +++++++-------- enterprise/coderd/licenses_test.go | 57 ++++++++++++----------- 6 files changed, 103 insertions(+), 69 deletions(-) diff --git a/codersdk/features.go b/codersdk/features.go index 58fff94e0aace..866eac123cd58 100644 --- a/codersdk/features.go +++ b/codersdk/features.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "net/http" + "strings" ) type Entitlement string @@ -14,19 +15,28 @@ const ( EntitlementNotEntitled Entitlement = "not_entitled" ) +// To add a new feature, modify this set of enums as well as the FeatureNames +// array. +type FeatureName string + const ( - FeatureUserLimit = "user_limit" - FeatureAuditLog = "audit_log" - FeatureBrowserOnly = "browser_only" - FeatureSCIM = "scim" - FeatureTemplateRBAC = "template_rbac" - FeatureHighAvailability = "high_availability" - FeatureMultipleGitAuth = "multiple_git_auth" - FeatureExternalProvisionerDaemons = "external_provisioner_daemons" - FeatureAppearance = "appearance" + FeatureUserLimit FeatureName = "user_limit" + FeatureAuditLog FeatureName = "audit_log" + FeatureBrowserOnly FeatureName = "browser_only" + FeatureSCIM FeatureName = "scim" + FeatureTemplateRBAC FeatureName = "template_rbac" + FeatureHighAvailability FeatureName = "high_availability" + FeatureMultipleGitAuth FeatureName = "multiple_git_auth" + FeatureExternalProvisionerDaemons FeatureName = "external_provisioner_daemons" + FeatureAppearance FeatureName = "appearance" ) -var FeatureNames = []string{ +// Humanize returns the feature name in a human-readable format. +func (n FeatureName) Humanize() string { + return strings.Title(strings.ReplaceAll(string(n), "_", " ")) +} + +var FeatureNames = []FeatureName{ FeatureUserLimit, FeatureAuditLog, FeatureBrowserOnly, @@ -46,12 +56,12 @@ type Feature struct { } type Entitlements struct { - Features map[string]Feature `json:"features"` - Warnings []string `json:"warnings"` - Errors []string `json:"errors"` - HasLicense bool `json:"has_license"` - Experimental bool `json:"experimental"` - Trial bool `json:"trial"` + Features map[FeatureName]Feature `json:"features"` + Warnings []string `json:"warnings"` + Errors []string `json:"errors"` + HasLicense bool `json:"has_license"` + Experimental bool `json:"experimental"` + Trial bool `json:"trial"` } func (c *Client) Entitlements(ctx context.Context) (Entitlements, error) { diff --git a/codersdk/licenses.go b/codersdk/licenses.go index 3ede213e53251..59319a326e48e 100644 --- a/codersdk/licenses.go +++ b/codersdk/licenses.go @@ -8,6 +8,7 @@ import ( "time" "github.com/google/uuid" + "golang.org/x/xerrors" ) type AddLicenseRequest struct { @@ -25,6 +26,30 @@ type License struct { Claims map[string]interface{} `json:"claims"` } +// Features provides the feature claims in license. +func (l *License) Features() (map[FeatureName]int64, error) { + strMap, ok := l.Claims["features"].(map[string]interface{}) + if !ok { + return nil, xerrors.New("features key is unexpected type") + } + fMap := make(map[FeatureName]int64) + for k, v := range strMap { + jn, ok := v.(json.Number) + if !ok { + return nil, xerrors.Errorf("feature %q has unexpected type", k) + } + + n, err := jn.Int64() + if err != nil { + return nil, err + } + + fMap[FeatureName(k)] = n + } + + return fMap, nil +} + func (c *Client) AddLicense(ctx context.Context, r AddLicenseRequest) (License, error) { res, err := c.Request(ctx, http.MethodPost, "/api/v2/licenses", r) if err != nil { diff --git a/enterprise/coderd/coderd.go b/enterprise/coderd/coderd.go index 2ec7548f54a49..5ad7460b1dd26 100644 --- a/enterprise/coderd/coderd.go +++ b/enterprise/coderd/coderd.go @@ -238,7 +238,7 @@ func (api *API) updateEntitlements(ctx context.Context) error { api.entitlementsMu.Lock() defer api.entitlementsMu.Unlock() - entitlements, err := license.Entitlements(ctx, api.Database, api.Logger, len(api.replicaManager.All()), len(api.GitAuthConfigs), api.Keys, map[string]bool{ + entitlements, err := license.Entitlements(ctx, api.Database, api.Logger, len(api.replicaManager.All()), len(api.GitAuthConfigs), api.Keys, map[codersdk.FeatureName]bool{ codersdk.FeatureAuditLog: api.AuditLogging, codersdk.FeatureBrowserOnly: api.BrowserOnly, codersdk.FeatureSCIM: len(api.SCIMAPIKey) != 0, @@ -252,7 +252,7 @@ func (api *API) updateEntitlements(ctx context.Context) error { } entitlements.Experimental = api.DeploymentConfig.Experimental.Value - featureChanged := func(featureName string) (changed bool, enabled bool) { + featureChanged := func(featureName codersdk.FeatureName) (changed bool, enabled bool) { if api.entitlements.Features == nil { return true, entitlements.Features[featureName].Enabled } diff --git a/enterprise/coderd/license/license.go b/enterprise/coderd/license/license.go index 47c15d7b0d1cb..a6734aa3c0478 100644 --- a/enterprise/coderd/license/license.go +++ b/enterprise/coderd/license/license.go @@ -4,7 +4,6 @@ import ( "context" "crypto/ed25519" "fmt" - "strings" "time" "github.com/golang-jwt/jwt/v4" @@ -24,12 +23,12 @@ func Entitlements( replicaCount int, gitAuthCount int, keys map[string]ed25519.PublicKey, - enablements map[string]bool, + enablements map[codersdk.FeatureName]bool, ) (codersdk.Entitlements, error) { now := time.Now() // Default all entitlements to be disabled. entitlements := codersdk.Entitlements{ - Features: map[string]codersdk.Feature{}, + Features: map[codersdk.FeatureName]codersdk.Feature{}, Warnings: []string{}, Errors: []string{}, } @@ -171,7 +170,7 @@ func Entitlements( if !feature.Enabled { continue } - niceName := strings.Title(strings.ReplaceAll(featureName, "_", " ")) + niceName := featureName.Humanize() switch feature.Entitlement { case codersdk.EntitlementNotEntitled: entitlements.Warnings = append(entitlements.Warnings, diff --git a/enterprise/coderd/license/license_test.go b/enterprise/coderd/license/license_test.go index 1dde6062e78f4..ad20f3e6e6039 100644 --- a/enterprise/coderd/license/license_test.go +++ b/enterprise/coderd/license/license_test.go @@ -3,7 +3,6 @@ package license_test import ( "context" "fmt" - "strings" "testing" "time" @@ -19,17 +18,13 @@ import ( func TestEntitlements(t *testing.T) { t.Parallel() - all := map[string]bool{ - codersdk.FeatureAuditLog: true, - codersdk.FeatureBrowserOnly: true, - codersdk.FeatureSCIM: true, - codersdk.FeatureHighAvailability: true, - codersdk.FeatureTemplateRBAC: true, - codersdk.FeatureMultipleGitAuth: true, - codersdk.FeatureExternalProvisionerDaemons: true, - codersdk.FeatureAppearance: true, + all := make(map[codersdk.FeatureName]bool) + for _, n := range codersdk.FeatureNames { + all[n] = true } + empty := map[codersdk.FeatureName]bool{} + t.Run("Defaults", func(t *testing.T) { t.Parallel() db := databasefake.New() @@ -49,7 +44,7 @@ func TestEntitlements(t *testing.T) { JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{}), Exp: time.Now().Add(time.Hour), }) - entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, 1, coderdenttest.Keys, map[string]bool{}) + entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, 1, coderdenttest.Keys, empty) require.NoError(t, err) require.True(t, entitlements.HasLicense) require.False(t, entitlements.Trial) @@ -75,7 +70,7 @@ func TestEntitlements(t *testing.T) { }), Exp: time.Now().Add(time.Hour), }) - entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, 1, coderdenttest.Keys, map[string]bool{}) + entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, 1, coderdenttest.Keys, empty) require.NoError(t, err) require.True(t, entitlements.HasLicense) require.False(t, entitlements.Trial) @@ -115,7 +110,7 @@ func TestEntitlements(t *testing.T) { if featureName == codersdk.FeatureMultipleGitAuth { continue } - niceName := strings.Title(strings.ReplaceAll(featureName, "_", " ")) + niceName := featureName.Humanize() require.Equal(t, codersdk.EntitlementGracePeriod, entitlements.Features[featureName].Entitlement) require.Contains(t, entitlements.Warnings, fmt.Sprintf("%s is enabled but your license for this feature is expired.", niceName)) } @@ -141,7 +136,7 @@ func TestEntitlements(t *testing.T) { if featureName == codersdk.FeatureMultipleGitAuth { continue } - niceName := strings.Title(strings.ReplaceAll(featureName, "_", " ")) + niceName := featureName.Humanize() // Ensures features that are not entitled are properly disabled. require.False(t, entitlements.Features[featureName].Enabled) require.Equal(t, codersdk.EntitlementNotEntitled, entitlements.Features[featureName].Entitlement) @@ -163,7 +158,7 @@ func TestEntitlements(t *testing.T) { }), Exp: time.Now().Add(time.Hour), }) - entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, 1, coderdenttest.Keys, map[string]bool{}) + entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, 1, coderdenttest.Keys, empty) require.NoError(t, err) require.True(t, entitlements.HasLicense) require.Contains(t, entitlements.Warnings, "Your deployment has 2 active users but is only licensed for 1.") @@ -185,7 +180,7 @@ func TestEntitlements(t *testing.T) { }), Exp: time.Now().Add(time.Hour), }) - entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, 1, coderdenttest.Keys, map[string]bool{}) + entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, 1, coderdenttest.Keys, empty) require.NoError(t, err) require.True(t, entitlements.HasLicense) require.Empty(t, entitlements.Warnings) @@ -208,7 +203,7 @@ func TestEntitlements(t *testing.T) { }), }) - entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, 1, coderdenttest.Keys, map[string]bool{}) + entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, 1, coderdenttest.Keys, empty) require.NoError(t, err) require.True(t, entitlements.HasLicense) require.False(t, entitlements.Trial) @@ -255,7 +250,7 @@ func TestEntitlements(t *testing.T) { AuditLog: true, }), }) - entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 2, 1, coderdenttest.Keys, map[string]bool{ + entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 2, 1, coderdenttest.Keys, map[codersdk.FeatureName]bool{ codersdk.FeatureHighAvailability: true, }) require.NoError(t, err) @@ -275,7 +270,7 @@ func TestEntitlements(t *testing.T) { }), Exp: time.Now().Add(time.Hour), }) - entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 2, 1, coderdenttest.Keys, map[string]bool{ + entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 2, 1, coderdenttest.Keys, map[codersdk.FeatureName]bool{ codersdk.FeatureHighAvailability: true, }) require.NoError(t, err) @@ -303,7 +298,7 @@ func TestEntitlements(t *testing.T) { AuditLog: true, }), }) - entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, 2, coderdenttest.Keys, map[string]bool{ + entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, 2, coderdenttest.Keys, map[codersdk.FeatureName]bool{ codersdk.FeatureMultipleGitAuth: true, }) require.NoError(t, err) @@ -323,7 +318,7 @@ func TestEntitlements(t *testing.T) { }), Exp: time.Now().Add(time.Hour), }) - entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, 2, coderdenttest.Keys, map[string]bool{ + entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, 2, coderdenttest.Keys, map[codersdk.FeatureName]bool{ codersdk.FeatureMultipleGitAuth: true, }) require.NoError(t, err) diff --git a/enterprise/coderd/licenses_test.go b/enterprise/coderd/licenses_test.go index 34317773d539f..0648380b75820 100644 --- a/enterprise/coderd/licenses_test.go +++ b/enterprise/coderd/licenses_test.go @@ -2,7 +2,6 @@ package coderd_test import ( "context" - "encoding/json" "net/http" "testing" @@ -32,9 +31,9 @@ func TestPostLicense(t *testing.T) { assert.GreaterOrEqual(t, respLic.ID, int32(0)) // just a couple spot checks for sanity assert.Equal(t, "testing", respLic.Claims["account_id"]) - features, ok := respLic.Claims["features"].(map[string]interface{}) - require.True(t, ok) - assert.Equal(t, json.Number("1"), features[codersdk.FeatureAuditLog]) + features, err := respLic.Features() + require.NoError(t, err) + assert.EqualValues(t, 1, features[codersdk.FeatureAuditLog]) }) t.Run("Unauthorized", func(t *testing.T) { @@ -100,31 +99,37 @@ func TestGetLicense(t *testing.T) { require.Len(t, licenses, 2) assert.Equal(t, int32(1), licenses[0].ID) assert.Equal(t, "testing", licenses[0].Claims["account_id"]) - assert.Equal(t, map[string]interface{}{ - codersdk.FeatureUserLimit: json.Number("0"), - codersdk.FeatureAuditLog: json.Number("1"), - codersdk.FeatureSCIM: json.Number("1"), - codersdk.FeatureBrowserOnly: json.Number("1"), - codersdk.FeatureHighAvailability: json.Number("0"), - codersdk.FeatureTemplateRBAC: json.Number("1"), - codersdk.FeatureMultipleGitAuth: json.Number("0"), - codersdk.FeatureExternalProvisionerDaemons: json.Number("0"), - codersdk.FeatureAppearance: json.Number("0"), - }, licenses[0].Claims["features"]) + + features, err := licenses[0].Features() + require.NoError(t, err) + assert.Equal(t, map[codersdk.FeatureName]int64{ + codersdk.FeatureUserLimit: 0, + codersdk.FeatureAuditLog: 1, + codersdk.FeatureSCIM: 1, + codersdk.FeatureBrowserOnly: 1, + codersdk.FeatureHighAvailability: 0, + codersdk.FeatureTemplateRBAC: 1, + codersdk.FeatureMultipleGitAuth: 0, + codersdk.FeatureExternalProvisionerDaemons: 0, + codersdk.FeatureAppearance: 0, + }, features) assert.Equal(t, int32(2), licenses[1].ID) assert.Equal(t, "testing2", licenses[1].Claims["account_id"]) assert.Equal(t, true, licenses[1].Claims["trial"]) - assert.Equal(t, map[string]interface{}{ - codersdk.FeatureUserLimit: json.Number("200"), - codersdk.FeatureAuditLog: json.Number("1"), - codersdk.FeatureSCIM: json.Number("1"), - codersdk.FeatureBrowserOnly: json.Number("1"), - codersdk.FeatureHighAvailability: json.Number("0"), - codersdk.FeatureTemplateRBAC: json.Number("0"), - codersdk.FeatureMultipleGitAuth: json.Number("0"), - codersdk.FeatureExternalProvisionerDaemons: json.Number("0"), - codersdk.FeatureAppearance: json.Number("0"), - }, licenses[1].Claims["features"]) + + features, err = licenses[1].Features() + require.NoError(t, err) + assert.Equal(t, map[codersdk.FeatureName]int64{ + codersdk.FeatureUserLimit: 200, + codersdk.FeatureAuditLog: 1, + codersdk.FeatureSCIM: 1, + codersdk.FeatureBrowserOnly: 1, + codersdk.FeatureHighAvailability: 0, + codersdk.FeatureTemplateRBAC: 0, + codersdk.FeatureMultipleGitAuth: 0, + codersdk.FeatureExternalProvisionerDaemons: 0, + codersdk.FeatureAppearance: 0, + }, features) }) } From c2d044bf272753c4e5cf319fa94c52e27f1e0313 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Mon, 16 Jan 2023 21:39:40 +0000 Subject: [PATCH 02/13] fixup! refactor: deduplicate / type license feature code --- codersdk/features.go | 12 ++++ enterprise/coderd/license/license.go | 94 +++++++--------------------- 2 files changed, 36 insertions(+), 70 deletions(-) diff --git a/codersdk/features.go b/codersdk/features.go index 866eac123cd58..ce38abcccd905 100644 --- a/codersdk/features.go +++ b/codersdk/features.go @@ -36,6 +36,18 @@ func (n FeatureName) Humanize() string { return strings.Title(strings.ReplaceAll(string(n), "_", " ")) } +// AlwaysEnable returns if the feature is always enabled if entitled. +// Warning: We don't know if we need this functionality. +// This method may disappear at any time. +func (n FeatureName) AlwaysEnable() bool { + m := map[FeatureName]bool{ + FeatureMultipleGitAuth: true, + FeatureExternalProvisionerDaemons: true, + FeatureAppearance: true, + } + return m[n] +} + var FeatureNames = []FeatureName{ FeatureUserLimit, FeatureAuditLog, diff --git a/enterprise/coderd/license/license.go b/enterprise/coderd/license/license.go index a6734aa3c0478..7100b35c26d47 100644 --- a/enterprise/coderd/license/license.go +++ b/enterprise/coderd/license/license.go @@ -67,67 +67,31 @@ func Entitlements( // LicenseExpires we must be in grace period. entitlement = codersdk.EntitlementGracePeriod } - if claims.Features.UserLimit > 0 { - limit := claims.Features.UserLimit - priorLimit := entitlements.Features[codersdk.FeatureUserLimit] - if priorLimit.Limit != nil && *priorLimit.Limit > limit { - limit = *priorLimit.Limit - } - entitlements.Features[codersdk.FeatureUserLimit] = codersdk.Feature{ - Enabled: true, - Entitlement: entitlement, - Limit: &limit, - Actual: &activeUserCount, - } - } - if claims.Features.AuditLog > 0 { - entitlements.Features[codersdk.FeatureAuditLog] = codersdk.Feature{ - Entitlement: entitlement, - Enabled: enablements[codersdk.FeatureAuditLog], - } - } - if claims.Features.BrowserOnly > 0 { - entitlements.Features[codersdk.FeatureBrowserOnly] = codersdk.Feature{ - Entitlement: entitlement, - Enabled: enablements[codersdk.FeatureBrowserOnly], - } - } - if claims.Features.SCIM > 0 { - entitlements.Features[codersdk.FeatureSCIM] = codersdk.Feature{ - Entitlement: entitlement, - Enabled: enablements[codersdk.FeatureSCIM], - } - } - if claims.Features.HighAvailability > 0 { - entitlements.Features[codersdk.FeatureHighAvailability] = codersdk.Feature{ - Entitlement: entitlement, - Enabled: enablements[codersdk.FeatureHighAvailability], - } - } - if claims.Features.TemplateRBAC > 0 { - entitlements.Features[codersdk.FeatureTemplateRBAC] = codersdk.Feature{ - Entitlement: entitlement, - Enabled: enablements[codersdk.FeatureTemplateRBAC], - } - } - if claims.Features.MultipleGitAuth > 0 { - entitlements.Features[codersdk.FeatureMultipleGitAuth] = codersdk.Feature{ - Entitlement: entitlement, - Enabled: true, - } - } - if claims.Features.ExternalProvisionerDaemons > 0 { - entitlements.Features[codersdk.FeatureExternalProvisionerDaemons] = codersdk.Feature{ - Entitlement: entitlement, - Enabled: true, - } - } - if claims.Features.Appearance > 0 { - entitlements.Features[codersdk.FeatureAppearance] = codersdk.Feature{ - Entitlement: entitlement, - Enabled: true, + for featureName, featureValue := range claims.Features { + switch featureName { + // User limit has special treatment as our only non-boolean feature. + case codersdk.FeatureUserLimit: + limit := featureValue + priorLimit := entitlements.Features[codersdk.FeatureUserLimit] + if priorLimit.Limit != nil && *priorLimit.Limit > limit { + limit = *priorLimit.Limit + } + entitlements.Features[codersdk.FeatureUserLimit] = codersdk.Feature{ + Enabled: true, + Entitlement: entitlement, + Limit: &limit, + Actual: &activeUserCount, + } + default: + if featureValue > 0 { + entitlements.Features[featureName] = codersdk.Feature{ + Entitlement: entitlement, + Enabled: enablements[featureName] || featureName.AlwaysEnable(), + } + } } } + if claims.AllFeatures { allFeatures = true } @@ -248,17 +212,7 @@ var ( ErrMissingLicenseExpires = xerrors.New("license missing license_expires") ) -type Features struct { - UserLimit int64 `json:"user_limit"` - AuditLog int64 `json:"audit_log"` - BrowserOnly int64 `json:"browser_only"` - SCIM int64 `json:"scim"` - TemplateRBAC int64 `json:"template_rbac"` - HighAvailability int64 `json:"high_availability"` - MultipleGitAuth int64 `json:"multiple_git_auth"` - ExternalProvisionerDaemons int64 `json:"external_provisioner_daemons"` - Appearance int64 `json:"appearance"` -} +type Features map[codersdk.FeatureName]int64 type Claims struct { jwt.RegisteredClaims From 83cd8f4e21c9372e9ccb6f854b9d386e20ecd92c Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Mon, 16 Jan 2023 22:18:42 +0000 Subject: [PATCH 03/13] fixup! refactor: deduplicate / type license feature code --- codersdk/features.go | 27 +++--- .../coderd/coderdenttest/coderdenttest.go | 70 ++------------- enterprise/coderd/license/license.go | 13 +-- enterprise/coderd/license/license_test.go | 86 +++++++++---------- 4 files changed, 72 insertions(+), 124 deletions(-) diff --git a/codersdk/features.go b/codersdk/features.go index ce38abcccd905..439fb305d34ac 100644 --- a/codersdk/features.go +++ b/codersdk/features.go @@ -16,7 +16,7 @@ const ( ) // To add a new feature, modify this set of enums as well as the FeatureNames -// array. +// array below. type FeatureName string const ( @@ -31,6 +31,19 @@ const ( FeatureAppearance FeatureName = "appearance" ) +// FeatureNames must be kept in-sync with the Feature enum above. +var FeatureNames = []FeatureName{ + FeatureUserLimit, + FeatureAuditLog, + FeatureBrowserOnly, + FeatureSCIM, + FeatureTemplateRBAC, + FeatureHighAvailability, + FeatureMultipleGitAuth, + FeatureExternalProvisionerDaemons, + FeatureAppearance, +} + // Humanize returns the feature name in a human-readable format. func (n FeatureName) Humanize() string { return strings.Title(strings.ReplaceAll(string(n), "_", " ")) @@ -48,18 +61,6 @@ func (n FeatureName) AlwaysEnable() bool { return m[n] } -var FeatureNames = []FeatureName{ - FeatureUserLimit, - FeatureAuditLog, - FeatureBrowserOnly, - FeatureSCIM, - FeatureTemplateRBAC, - FeatureHighAvailability, - FeatureMultipleGitAuth, - FeatureExternalProvisionerDaemons, - FeatureAppearance, -} - type Feature struct { Entitlement Entitlement `json:"entitlement"` Enabled bool `json:"enabled"` diff --git a/enterprise/coderd/coderdenttest/coderdenttest.go b/enterprise/coderd/coderdenttest/coderdenttest.go index fa118a204d437..0e5c8e7a1ef93 100644 --- a/enterprise/coderd/coderdenttest/coderdenttest.go +++ b/enterprise/coderd/coderdenttest/coderdenttest.go @@ -99,21 +99,13 @@ func NewWithAPI(t *testing.T, options *Options) (*codersdk.Client, io.Closer, *c } type LicenseOptions struct { - AccountType string - AccountID string - Trial bool - AllFeatures bool - GraceAt time.Time - ExpiresAt time.Time - UserLimit int64 - AuditLog bool - BrowserOnly bool - SCIM bool - TemplateRBAC bool - HighAvailability bool - MultipleGitAuth bool - ExternalProvisionerDaemons bool - ServiceBanners bool + AccountType string + AccountID string + Trial bool + AllFeatures bool + GraceAt time.Time + ExpiresAt time.Time + Features license.Features } // AddLicense generates a new license with the options provided and inserts it. @@ -133,42 +125,6 @@ func GenerateLicense(t *testing.T, options LicenseOptions) string { if options.GraceAt.IsZero() { options.GraceAt = time.Now().Add(time.Hour) } - var auditLog int64 - if options.AuditLog { - auditLog = 1 - } - var browserOnly int64 - if options.BrowserOnly { - browserOnly = 1 - } - var scim int64 - if options.SCIM { - scim = 1 - } - highAvailability := int64(0) - if options.HighAvailability { - highAvailability = 1 - } - - rbacEnabled := int64(0) - if options.TemplateRBAC { - rbacEnabled = 1 - } - - multipleGitAuth := int64(0) - if options.MultipleGitAuth { - multipleGitAuth = 1 - } - - externalProvisionerDaemons := int64(0) - if options.ExternalProvisionerDaemons { - externalProvisionerDaemons = 1 - } - - serviceBanners := int64(0) - if options.ServiceBanners { - serviceBanners = 1 - } c := &license.Claims{ RegisteredClaims: jwt.RegisteredClaims{ @@ -183,17 +139,7 @@ func GenerateLicense(t *testing.T, options LicenseOptions) string { Trial: options.Trial, Version: license.CurrentVersion, AllFeatures: options.AllFeatures, - Features: license.Features{ - UserLimit: options.UserLimit, - AuditLog: auditLog, - BrowserOnly: browserOnly, - SCIM: scim, - HighAvailability: highAvailability, - TemplateRBAC: rbacEnabled, - MultipleGitAuth: multipleGitAuth, - ExternalProvisionerDaemons: externalProvisionerDaemons, - Appearance: serviceBanners, - }, + Features: options.Features, } tok := jwt.NewWithClaims(jwt.SigningMethodEdDSA, c) tok.Header[license.HeaderKeyID] = testKeyID diff --git a/enterprise/coderd/license/license.go b/enterprise/coderd/license/license.go index 7100b35c26d47..4e66ab578fad6 100644 --- a/enterprise/coderd/license/license.go +++ b/enterprise/coderd/license/license.go @@ -68,6 +68,11 @@ func Entitlements( entitlement = codersdk.EntitlementGracePeriod } for featureName, featureValue := range claims.Features { + // Can this be negative? + if featureValue <= 0 { + continue + } + switch featureName { // User limit has special treatment as our only non-boolean feature. case codersdk.FeatureUserLimit: @@ -83,11 +88,9 @@ func Entitlements( Actual: &activeUserCount, } default: - if featureValue > 0 { - entitlements.Features[featureName] = codersdk.Feature{ - Entitlement: entitlement, - Enabled: enablements[featureName] || featureName.AlwaysEnable(), - } + entitlements.Features[featureName] = codersdk.Feature{ + Entitlement: entitlement, + Enabled: enablements[featureName] || featureName.AlwaysEnable(), } } } diff --git a/enterprise/coderd/license/license_test.go b/enterprise/coderd/license/license_test.go index ad20f3e6e6039..0bbe815918444 100644 --- a/enterprise/coderd/license/license_test.go +++ b/enterprise/coderd/license/license_test.go @@ -58,15 +58,13 @@ func TestEntitlements(t *testing.T) { db := databasefake.New() db.InsertLicense(context.Background(), database.InsertLicenseParams{ JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{ - UserLimit: 100, - AuditLog: true, - BrowserOnly: true, - SCIM: true, - HighAvailability: true, - TemplateRBAC: true, - MultipleGitAuth: true, - ExternalProvisionerDaemons: true, - ServiceBanners: true, + Features: func() license.Features { + f := make(license.Features) + for _, name := range codersdk.FeatureNames { + f[name] = 1 + } + return f + }(), }), Exp: time.Now().Add(time.Hour), }) @@ -83,16 +81,12 @@ func TestEntitlements(t *testing.T) { db := databasefake.New() db.InsertLicense(context.Background(), database.InsertLicenseParams{ JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{ - UserLimit: 100, - AuditLog: true, - BrowserOnly: true, - SCIM: true, - HighAvailability: true, - TemplateRBAC: true, - ExternalProvisionerDaemons: true, - ServiceBanners: true, - GraceAt: time.Now().Add(-time.Hour), - ExpiresAt: time.Now().Add(time.Hour), + Features: license.Features{ + codersdk.FeatureUserLimit: 100, + }, + + GraceAt: time.Now().Add(-time.Hour), + ExpiresAt: time.Now().Add(time.Hour), }), Exp: time.Now().Add(time.Hour), }) @@ -100,20 +94,10 @@ func TestEntitlements(t *testing.T) { require.NoError(t, err) require.True(t, entitlements.HasLicense) require.False(t, entitlements.Trial) - for _, featureName := range codersdk.FeatureNames { - if featureName == codersdk.FeatureUserLimit { - continue - } - if featureName == codersdk.FeatureHighAvailability { - continue - } - if featureName == codersdk.FeatureMultipleGitAuth { - continue - } - niceName := featureName.Humanize() - require.Equal(t, codersdk.EntitlementGracePeriod, entitlements.Features[featureName].Entitlement) - require.Contains(t, entitlements.Warnings, fmt.Sprintf("%s is enabled but your license for this feature is expired.", niceName)) - } + + require.Equal(t, codersdk.EntitlementGracePeriod, entitlements.Features[codersdk.FeatureUserLimit].Entitlement) + require.Contains(t, entitlements.Warnings, fmt.Sprintf("%s is enabled but your license for this feature is expired.", codersdk.FeatureUserLimit.Humanize())) + }) t.Run("SingleLicenseNotEntitled", func(t *testing.T) { t.Parallel() @@ -154,7 +138,9 @@ func TestEntitlements(t *testing.T) { }) db.InsertLicense(context.Background(), database.InsertLicenseParams{ JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{ - UserLimit: 1, + Features: license.Features{ + codersdk.FeatureUserLimit: 1, + }, }), Exp: time.Now().Add(time.Hour), }) @@ -170,13 +156,17 @@ func TestEntitlements(t *testing.T) { db.InsertUser(context.Background(), database.InsertUserParams{}) db.InsertLicense(context.Background(), database.InsertLicenseParams{ JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{ - UserLimit: 10, + Features: license.Features{ + codersdk.FeatureUserLimit: 10, + }, }), Exp: time.Now().Add(time.Hour), }) db.InsertLicense(context.Background(), database.InsertLicenseParams{ JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{ - UserLimit: 1, + Features: license.Features{ + codersdk.FeatureUserLimit: 1, + }, }), Exp: time.Now().Add(time.Hour), }) @@ -247,7 +237,9 @@ func TestEntitlements(t *testing.T) { db.InsertLicense(context.Background(), database.InsertLicenseParams{ Exp: time.Now().Add(time.Hour), JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{ - AuditLog: true, + Features: license.Features{ + codersdk.FeatureAuditLog: 1, + }, }), }) entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 2, 1, coderdenttest.Keys, map[codersdk.FeatureName]bool{ @@ -264,9 +256,11 @@ func TestEntitlements(t *testing.T) { db := databasefake.New() db.InsertLicense(context.Background(), database.InsertLicenseParams{ JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{ - HighAvailability: true, - GraceAt: time.Now().Add(-time.Hour), - ExpiresAt: time.Now().Add(time.Hour), + Features: license.Features{ + codersdk.FeatureHighAvailability: 1, + }, + GraceAt: time.Now().Add(-time.Hour), + ExpiresAt: time.Now().Add(time.Hour), }), Exp: time.Now().Add(time.Hour), }) @@ -295,7 +289,9 @@ func TestEntitlements(t *testing.T) { db.InsertLicense(context.Background(), database.InsertLicenseParams{ Exp: time.Now().Add(time.Hour), JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{ - AuditLog: true, + Features: license.Features{ + codersdk.FeatureAuditLog: 1, + }, }), }) entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, 2, coderdenttest.Keys, map[codersdk.FeatureName]bool{ @@ -312,9 +308,11 @@ func TestEntitlements(t *testing.T) { db := databasefake.New() db.InsertLicense(context.Background(), database.InsertLicenseParams{ JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{ - MultipleGitAuth: true, - GraceAt: time.Now().Add(-time.Hour), - ExpiresAt: time.Now().Add(time.Hour), + GraceAt: time.Now().Add(-time.Hour), + ExpiresAt: time.Now().Add(time.Hour), + Features: license.Features{ + codersdk.FeatureMultipleGitAuth: 1, + }, }), Exp: time.Now().Add(time.Hour), }) From e51a0774cfa5391c19bb99654a379556ab09d669 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Mon, 16 Jan 2023 22:46:55 +0000 Subject: [PATCH 04/13] fixup! refactor: deduplicate / type license feature code --- codersdk/features.go | 9 +- enterprise/cli/features.go | 12 +- enterprise/cli/groupcreate_test.go | 6 +- enterprise/cli/groupdelete_test.go | 9 +- enterprise/cli/groupedit_test.go | 13 ++- enterprise/cli/grouplist_test.go | 9 +- enterprise/cli/licenses_test.go | 2 +- enterprise/coderd/appearance_test.go | 5 +- enterprise/coderd/authorize_test.go | 5 +- enterprise/coderd/coderd_test.go | 29 +++-- .../coderdenttest/coderdenttest_test.go | 11 +- enterprise/coderd/groups_test.go | 103 +++++++++++++----- enterprise/coderd/license/license_test.go | 9 +- enterprise/coderd/licenses_test.go | 67 ++++++------ enterprise/coderd/provisionerdaemons_test.go | 13 ++- enterprise/coderd/replicas_test.go | 9 +- enterprise/coderd/scim_test.go | 25 ++++- enterprise/coderd/templates_test.go | 79 ++++++++++---- enterprise/coderd/workspaceagents_test.go | 9 +- enterprise/coderd/workspacequota_test.go | 5 +- enterprise/coderd/workspaces_test.go | 5 +- 21 files changed, 304 insertions(+), 130 deletions(-) diff --git a/codersdk/features.go b/codersdk/features.go index 439fb305d34ac..4593e9d3fe9f0 100644 --- a/codersdk/features.go +++ b/codersdk/features.go @@ -46,7 +46,14 @@ var FeatureNames = []FeatureName{ // Humanize returns the feature name in a human-readable format. func (n FeatureName) Humanize() string { - return strings.Title(strings.ReplaceAll(string(n), "_", " ")) + switch n { + case FeatureTemplateRBAC: + return "Template RBAC" + case FeatureSCIM: + return "SCIM" + default: + return strings.Title(strings.ReplaceAll(string(n), "_", " ")) + } } // AlwaysEnable returns if the feature is always enabled if entitled. diff --git a/enterprise/cli/features.go b/enterprise/cli/features.go index b0ced1277caac..3c2abfb13fb37 100644 --- a/enterprise/cli/features.go +++ b/enterprise/cli/features.go @@ -88,17 +88,17 @@ func featuresList() *cobra.Command { } type featureRow struct { - Name string `table:"name"` - Entitlement string `table:"entitlement"` - Enabled bool `table:"enabled"` - Limit *int64 `table:"limit"` - Actual *int64 `table:"actual"` + Name codersdk.FeatureName `table:"name"` + Entitlement string `table:"entitlement"` + Enabled bool `table:"enabled"` + Limit *int64 `table:"limit"` + Actual *int64 `table:"actual"` } // displayFeatures will return a table displaying all features passed in. // filterColumns must be a subset of the feature fields and will determine which // columns to display -func displayFeatures(filterColumns []string, features map[string]codersdk.Feature) (string, error) { +func displayFeatures(filterColumns []string, features map[codersdk.FeatureName]codersdk.Feature) (string, error) { rows := make([]featureRow, 0, len(features)) for name, feat := range features { rows = append(rows, featureRow{ diff --git a/enterprise/cli/groupcreate_test.go b/enterprise/cli/groupcreate_test.go index 2a0e61c6c0687..166214901b908 100644 --- a/enterprise/cli/groupcreate_test.go +++ b/enterprise/cli/groupcreate_test.go @@ -9,8 +9,10 @@ import ( "github.com/coder/coder/cli/clitest" "github.com/coder/coder/cli/cliui" "github.com/coder/coder/coderd/coderdtest" + "github.com/coder/coder/codersdk" "github.com/coder/coder/enterprise/cli" "github.com/coder/coder/enterprise/coderd/coderdenttest" + "github.com/coder/coder/enterprise/coderd/license" "github.com/coder/coder/pty/ptytest" ) @@ -23,7 +25,9 @@ func TestCreateGroup(t *testing.T) { client := coderdenttest.New(t, nil) coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) var ( diff --git a/enterprise/cli/groupdelete_test.go b/enterprise/cli/groupdelete_test.go index e2319a43e2aaa..a997af603f5f4 100644 --- a/enterprise/cli/groupdelete_test.go +++ b/enterprise/cli/groupdelete_test.go @@ -12,6 +12,7 @@ import ( "github.com/coder/coder/codersdk" "github.com/coder/coder/enterprise/cli" "github.com/coder/coder/enterprise/coderd/coderdenttest" + "github.com/coder/coder/enterprise/coderd/license" "github.com/coder/coder/pty/ptytest" "github.com/coder/coder/testutil" ) @@ -26,7 +27,9 @@ func TestGroupDelete(t *testing.T) { admin := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) ctx, _ := testutil.Context(t) @@ -57,7 +60,9 @@ func TestGroupDelete(t *testing.T) { _ = coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) cmd, root := clitest.NewWithSubcommands(t, cli.EnterpriseSubcommands(), diff --git a/enterprise/cli/groupedit_test.go b/enterprise/cli/groupedit_test.go index 8c4c8f0f16e49..f8581e607f68f 100644 --- a/enterprise/cli/groupedit_test.go +++ b/enterprise/cli/groupedit_test.go @@ -12,6 +12,7 @@ import ( "github.com/coder/coder/codersdk" "github.com/coder/coder/enterprise/cli" "github.com/coder/coder/enterprise/coderd/coderdenttest" + "github.com/coder/coder/enterprise/coderd/license" "github.com/coder/coder/pty/ptytest" "github.com/coder/coder/testutil" ) @@ -26,7 +27,9 @@ func TestGroupEdit(t *testing.T) { admin := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) ctx, _ := testutil.Context(t) @@ -77,7 +80,9 @@ func TestGroupEdit(t *testing.T) { admin := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) ctx, _ := testutil.Context(t) @@ -106,7 +111,9 @@ func TestGroupEdit(t *testing.T) { _ = coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) cmd, root := clitest.NewWithSubcommands(t, cli.EnterpriseSubcommands(), "groups", "edit") diff --git a/enterprise/cli/grouplist_test.go b/enterprise/cli/grouplist_test.go index 8740829a017c9..e806e1e52517e 100644 --- a/enterprise/cli/grouplist_test.go +++ b/enterprise/cli/grouplist_test.go @@ -10,6 +10,7 @@ import ( "github.com/coder/coder/codersdk" "github.com/coder/coder/enterprise/cli" "github.com/coder/coder/enterprise/coderd/coderdenttest" + "github.com/coder/coder/enterprise/coderd/license" "github.com/coder/coder/pty/ptytest" "github.com/coder/coder/testutil" ) @@ -24,7 +25,9 @@ func TestGroupList(t *testing.T) { admin := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) ctx, _ := testutil.Context(t) @@ -81,7 +84,9 @@ func TestGroupList(t *testing.T) { _ = coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) cmd, root := clitest.NewWithSubcommands(t, cli.EnterpriseSubcommands(), "groups", "list") diff --git a/enterprise/cli/licenses_test.go b/enterprise/cli/licenses_test.go index 924590dee984f..47617d8f914c7 100644 --- a/enterprise/cli/licenses_test.go +++ b/enterprise/cli/licenses_test.go @@ -338,7 +338,7 @@ func (s *fakeLicenseAPI) deleteLicense(rw http.ResponseWriter, r *http.Request) } func (*fakeLicenseAPI) entitlements(rw http.ResponseWriter, r *http.Request) { - features := make(map[string]codersdk.Feature) + features := make(map[codersdk.FeatureName]codersdk.Feature) for _, f := range codersdk.FeatureNames { features[f] = codersdk.Feature{ Entitlement: codersdk.EntitlementEntitled, diff --git a/enterprise/coderd/appearance_test.go b/enterprise/coderd/appearance_test.go index c83f9fb62005b..6b1c000bec91b 100644 --- a/enterprise/coderd/appearance_test.go +++ b/enterprise/coderd/appearance_test.go @@ -11,6 +11,7 @@ import ( "github.com/coder/coder/coderd/coderdtest" "github.com/coder/coder/codersdk" "github.com/coder/coder/enterprise/coderd/coderdenttest" + "github.com/coder/coder/enterprise/coderd/license" "github.com/coder/coder/testutil" ) @@ -30,7 +31,9 @@ func TestServiceBanners(t *testing.T) { require.False(t, sb.ServiceBanner.Enabled) coderdenttest.AddLicense(t, adminClient, coderdenttest.LicenseOptions{ - ServiceBanners: true, + Features: license.Features{ + codersdk.FeatureAppearance: 1, + }, }) // Default state diff --git a/enterprise/coderd/authorize_test.go b/enterprise/coderd/authorize_test.go index 9195387632a67..ef181e5b985aa 100644 --- a/enterprise/coderd/authorize_test.go +++ b/enterprise/coderd/authorize_test.go @@ -11,6 +11,7 @@ import ( "github.com/coder/coder/coderd/rbac" "github.com/coder/coder/codersdk" "github.com/coder/coder/enterprise/coderd/coderdenttest" + "github.com/coder/coder/enterprise/coderd/license" "github.com/coder/coder/testutil" ) @@ -28,7 +29,9 @@ func TestCheckACLPermissions(t *testing.T) { // Create adminClient, member, and org adminClient adminUser := coderdtest.CreateFirstUser(t, adminClient) _ = coderdenttest.AddLicense(t, adminClient, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) memberClient := coderdtest.CreateAnotherUser(t, adminClient, adminUser.OrganizationID) diff --git a/enterprise/coderd/coderd_test.go b/enterprise/coderd/coderd_test.go index 6d8e90c9b185d..4d67d97029830 100644 --- a/enterprise/coderd/coderd_test.go +++ b/enterprise/coderd/coderd_test.go @@ -17,6 +17,7 @@ import ( "github.com/coder/coder/enterprise/audit" "github.com/coder/coder/enterprise/coderd" "github.com/coder/coder/enterprise/coderd/coderdenttest" + "github.com/coder/coder/enterprise/coderd/license" "github.com/coder/coder/testutil" ) @@ -41,10 +42,12 @@ func TestEntitlements(t *testing.T) { }) _ = coderdtest.CreateFirstUser(t, client) coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - UserLimit: 100, - AuditLog: true, - TemplateRBAC: true, - ExternalProvisionerDaemons: true, + Features: license.Features{ + codersdk.FeatureUserLimit: 100, + codersdk.FeatureAuditLog: 1, + codersdk.FeatureTemplateRBAC: 1, + codersdk.FeatureExternalProvisionerDaemons: 1, + }, }) res, err := client.Entitlements(context.Background()) require.NoError(t, err) @@ -68,8 +71,10 @@ func TestEntitlements(t *testing.T) { }) _ = coderdtest.CreateFirstUser(t, client) license := coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - UserLimit: 100, - AuditLog: true, + Features: license.Features{ + codersdk.FeatureUserLimit: 100, + codersdk.FeatureAuditLog: 1, + }, }) res, err := client.Entitlements(context.Background()) require.NoError(t, err) @@ -99,7 +104,9 @@ func TestEntitlements(t *testing.T) { UploadedAt: database.Now(), Exp: database.Now().AddDate(1, 0, 0), JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{ - AuditLog: true, + Features: license.Features{ + codersdk.FeatureAuditLog: 1, + }, }), }) require.NoError(t, err) @@ -125,7 +132,9 @@ func TestEntitlements(t *testing.T) { UploadedAt: database.Now(), Exp: database.Now().AddDate(1, 0, 0), JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{ - AuditLog: true, + Features: license.Features{ + codersdk.FeatureAuditLog: 1, + }, }), }) require.NoError(t, err) @@ -165,7 +174,9 @@ func TestAuditLogging(t *testing.T) { }) coderdtest.CreateFirstUser(t, client) coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - AuditLog: true, + Features: license.Features{ + codersdk.FeatureAuditLog: 1, + }, }) auditor := *api.AGPL.Auditor.Load() ea := audit.NewAuditor(audit.DefaultFilter) diff --git a/enterprise/coderd/coderdenttest/coderdenttest_test.go b/enterprise/coderd/coderdenttest/coderdenttest_test.go index b6fb038d06acf..bfd5ee64152f0 100644 --- a/enterprise/coderd/coderdenttest/coderdenttest_test.go +++ b/enterprise/coderd/coderdenttest/coderdenttest_test.go @@ -12,6 +12,7 @@ import ( "github.com/coder/coder/coderd/rbac" "github.com/coder/coder/codersdk" "github.com/coder/coder/enterprise/coderd/coderdenttest" + "github.com/coder/coder/enterprise/coderd/license" "github.com/coder/coder/testutil" ) @@ -32,9 +33,11 @@ func TestAuthorizeAllEndpoints(t *testing.T) { }) ctx, _ := testutil.Context(t) admin := coderdtest.CreateFirstUser(t, client) - license := coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, - ExternalProvisionerDaemons: true, + lic := coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + codersdk.FeatureExternalProvisionerDaemons: 1, + }, }) group, err := client.CreateGroup(ctx, admin.OrganizationID, codersdk.CreateGroupRequest{ Name: "testgroup", @@ -43,7 +46,7 @@ func TestAuthorizeAllEndpoints(t *testing.T) { groupObj := rbac.ResourceGroup.InOrg(admin.OrganizationID) a := coderdtest.NewAuthTester(ctx, t, client, api.AGPL, admin) - a.URLParams["licenses/{id}"] = fmt.Sprintf("licenses/%d", license.ID) + a.URLParams["licenses/{id}"] = fmt.Sprintf("licenses/%d", lic.ID) a.URLParams["groups/{group}"] = fmt.Sprintf("groups/%s", group.ID.String()) a.URLParams["{groupName}"] = group.Name diff --git a/enterprise/coderd/groups_test.go b/enterprise/coderd/groups_test.go index 333368cb3b86a..b57f15cf3e434 100644 --- a/enterprise/coderd/groups_test.go +++ b/enterprise/coderd/groups_test.go @@ -13,6 +13,7 @@ import ( "github.com/coder/coder/coderd/database" "github.com/coder/coder/codersdk" "github.com/coder/coder/enterprise/coderd/coderdenttest" + "github.com/coder/coder/enterprise/coderd/license" "github.com/coder/coder/testutil" ) @@ -26,7 +27,9 @@ func TestCreateGroup(t *testing.T) { user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) ctx, _ := testutil.Context(t) group, err := client.CreateGroup(ctx, user.OrganizationID, codersdk.CreateGroupRequest{ @@ -54,8 +57,10 @@ func TestCreateGroup(t *testing.T) { user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, - AuditLog: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + codersdk.FeatureAuditLog: 1, + }, }) ctx, _ := testutil.Context(t) @@ -78,7 +83,9 @@ func TestCreateGroup(t *testing.T) { user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) ctx, _ := testutil.Context(t) _, err := client.CreateGroup(ctx, user.OrganizationID, codersdk.CreateGroupRequest{ @@ -102,7 +109,9 @@ func TestCreateGroup(t *testing.T) { user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) ctx, _ := testutil.Context(t) _, err := client.CreateGroup(ctx, user.OrganizationID, codersdk.CreateGroupRequest{ @@ -125,7 +134,9 @@ func TestPatchGroup(t *testing.T) { user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) ctx, _ := testutil.Context(t) group, err := client.CreateGroup(ctx, user.OrganizationID, codersdk.CreateGroupRequest{ @@ -157,7 +168,9 @@ func TestPatchGroup(t *testing.T) { user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) ctx, _ := testutil.Context(t) group, err := client.CreateGroup(ctx, user.OrganizationID, codersdk.CreateGroupRequest{ @@ -179,7 +192,9 @@ func TestPatchGroup(t *testing.T) { user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) _, user2 := coderdtest.CreateAnotherUserWithUser(t, client, user.OrganizationID) _, user3 := coderdtest.CreateAnotherUserWithUser(t, client, user.OrganizationID) @@ -205,7 +220,9 @@ func TestPatchGroup(t *testing.T) { user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) _, user2 := coderdtest.CreateAnotherUserWithUser(t, client, user.OrganizationID) _, user3 := coderdtest.CreateAnotherUserWithUser(t, client, user.OrganizationID) @@ -248,8 +265,10 @@ func TestPatchGroup(t *testing.T) { user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, - AuditLog: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + codersdk.FeatureAuditLog: 1, + }, }) ctx, _ := testutil.Context(t) @@ -277,7 +296,9 @@ func TestPatchGroup(t *testing.T) { user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) ctx, _ := testutil.Context(t) group1, err := client.CreateGroup(ctx, user.OrganizationID, codersdk.CreateGroupRequest{ @@ -308,7 +329,9 @@ func TestPatchGroup(t *testing.T) { user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) ctx, _ := testutil.Context(t) group, err := client.CreateGroup(ctx, user.OrganizationID, codersdk.CreateGroupRequest{ @@ -332,7 +355,9 @@ func TestPatchGroup(t *testing.T) { user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) ctx, _ := testutil.Context(t) group, err := client.CreateGroup(ctx, user.OrganizationID, codersdk.CreateGroupRequest{ @@ -356,7 +381,9 @@ func TestPatchGroup(t *testing.T) { user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) _, user2 := coderdtest.CreateAnotherUserWithUser(t, client, user.OrganizationID) ctx, _ := testutil.Context(t) @@ -382,7 +409,9 @@ func TestPatchGroup(t *testing.T) { user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) ctx, _ := testutil.Context(t) group, err := client.CreateGroup(ctx, user.OrganizationID, codersdk.CreateGroupRequest{ @@ -411,7 +440,9 @@ func TestGroup(t *testing.T) { user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) ctx, _ := testutil.Context(t) group, err := client.CreateGroup(ctx, user.OrganizationID, codersdk.CreateGroupRequest{ @@ -431,7 +462,9 @@ func TestGroup(t *testing.T) { user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) ctx, _ := testutil.Context(t) group, err := client.CreateGroup(ctx, user.OrganizationID, codersdk.CreateGroupRequest{ @@ -451,7 +484,9 @@ func TestGroup(t *testing.T) { user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) _, user2 := coderdtest.CreateAnotherUserWithUser(t, client, user.OrganizationID) _, user3 := coderdtest.CreateAnotherUserWithUser(t, client, user.OrganizationID) @@ -481,7 +516,9 @@ func TestGroup(t *testing.T) { user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) client1, _ := coderdtest.CreateAnotherUserWithUser(t, client, user.OrganizationID) @@ -502,7 +539,9 @@ func TestGroup(t *testing.T) { client := coderdenttest.New(t, nil) user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) _, user1 := coderdtest.CreateAnotherUserWithUser(t, client, user.OrganizationID) @@ -535,7 +574,9 @@ func TestGroup(t *testing.T) { client := coderdenttest.New(t, nil) user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) _, user1 := coderdtest.CreateAnotherUserWithUser(t, client, user.OrganizationID) @@ -576,7 +617,9 @@ func TestGroups(t *testing.T) { client := coderdenttest.New(t, nil) user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) _, user2 := coderdtest.CreateAnotherUserWithUser(t, client, user.OrganizationID) _, user3 := coderdtest.CreateAnotherUserWithUser(t, client, user.OrganizationID) @@ -622,7 +665,9 @@ func TestDeleteGroup(t *testing.T) { user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) ctx, _ := testutil.Context(t) group1, err := client.CreateGroup(ctx, user.OrganizationID, codersdk.CreateGroupRequest{ @@ -654,8 +699,10 @@ func TestDeleteGroup(t *testing.T) { user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, - AuditLog: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + codersdk.FeatureAuditLog: 1, + }, }) ctx, _ := testutil.Context(t) @@ -681,7 +728,9 @@ func TestDeleteGroup(t *testing.T) { user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) ctx, _ := testutil.Context(t) err := client.DeleteGroup(ctx, user.OrganizationID) diff --git a/enterprise/coderd/license/license_test.go b/enterprise/coderd/license/license_test.go index 0bbe815918444..1befc39d371cc 100644 --- a/enterprise/coderd/license/license_test.go +++ b/enterprise/coderd/license/license_test.go @@ -83,6 +83,7 @@ func TestEntitlements(t *testing.T) { JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{ Features: license.Features{ codersdk.FeatureUserLimit: 100, + codersdk.FeatureAuditLog: 1, }, GraceAt: time.Now().Add(-time.Hour), @@ -95,9 +96,11 @@ func TestEntitlements(t *testing.T) { require.True(t, entitlements.HasLicense) require.False(t, entitlements.Trial) - require.Equal(t, codersdk.EntitlementGracePeriod, entitlements.Features[codersdk.FeatureUserLimit].Entitlement) - require.Contains(t, entitlements.Warnings, fmt.Sprintf("%s is enabled but your license for this feature is expired.", codersdk.FeatureUserLimit.Humanize())) - + require.Equal(t, codersdk.EntitlementGracePeriod, entitlements.Features[codersdk.FeatureAuditLog].Entitlement) + require.Contains( + t, entitlements.Warnings, + fmt.Sprintf("%s is enabled but your license for this feature is expired.", codersdk.FeatureAuditLog.Humanize()), + ) }) t.Run("SingleLicenseNotEntitled", func(t *testing.T) { t.Parallel() diff --git a/enterprise/coderd/licenses_test.go b/enterprise/coderd/licenses_test.go index 0648380b75820..4c0595cc12fa8 100644 --- a/enterprise/coderd/licenses_test.go +++ b/enterprise/coderd/licenses_test.go @@ -26,7 +26,9 @@ func TestPostLicense(t *testing.T) { respLic := coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ AccountType: license.AccountTypeSalesforce, AccountID: "testing", - AuditLog: true, + Features: license.Features{ + codersdk.FeatureAuditLog: 1, + }, }) assert.GreaterOrEqual(t, respLic.ID, int32(0)) // just a couple spot checks for sanity @@ -77,21 +79,24 @@ func TestGetLicense(t *testing.T) { defer cancel() coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - AccountID: "testing", - AuditLog: true, - SCIM: true, - BrowserOnly: true, - TemplateRBAC: true, + AccountID: "testing", + Features: license.Features{ + codersdk.FeatureAuditLog: 1, + codersdk.FeatureSCIM: 1, + codersdk.FeatureBrowserOnly: 1, + codersdk.FeatureTemplateRBAC: 1, + }, }) coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - AccountID: "testing2", - AuditLog: true, - SCIM: true, - BrowserOnly: true, - Trial: true, - UserLimit: 200, - TemplateRBAC: false, + AccountID: "testing2", + Features: license.Features{ + codersdk.FeatureAuditLog: 1, + codersdk.FeatureSCIM: 1, + codersdk.FeatureBrowserOnly: 1, + codersdk.FeatureUserLimit: 200, + }, + Trial: true, }) licenses, err := client.Licenses(ctx) @@ -103,15 +108,10 @@ func TestGetLicense(t *testing.T) { features, err := licenses[0].Features() require.NoError(t, err) assert.Equal(t, map[codersdk.FeatureName]int64{ - codersdk.FeatureUserLimit: 0, - codersdk.FeatureAuditLog: 1, - codersdk.FeatureSCIM: 1, - codersdk.FeatureBrowserOnly: 1, - codersdk.FeatureHighAvailability: 0, - codersdk.FeatureTemplateRBAC: 1, - codersdk.FeatureMultipleGitAuth: 0, - codersdk.FeatureExternalProvisionerDaemons: 0, - codersdk.FeatureAppearance: 0, + codersdk.FeatureAuditLog: 1, + codersdk.FeatureSCIM: 1, + codersdk.FeatureBrowserOnly: 1, + codersdk.FeatureTemplateRBAC: 1, }, features) assert.Equal(t, int32(2), licenses[1].ID) assert.Equal(t, "testing2", licenses[1].Claims["account_id"]) @@ -120,15 +120,10 @@ func TestGetLicense(t *testing.T) { features, err = licenses[1].Features() require.NoError(t, err) assert.Equal(t, map[codersdk.FeatureName]int64{ - codersdk.FeatureUserLimit: 200, - codersdk.FeatureAuditLog: 1, - codersdk.FeatureSCIM: 1, - codersdk.FeatureBrowserOnly: 1, - codersdk.FeatureHighAvailability: 0, - codersdk.FeatureTemplateRBAC: 0, - codersdk.FeatureMultipleGitAuth: 0, - codersdk.FeatureExternalProvisionerDaemons: 0, - codersdk.FeatureAppearance: 0, + codersdk.FeatureUserLimit: 200, + codersdk.FeatureAuditLog: 1, + codersdk.FeatureSCIM: 1, + codersdk.FeatureBrowserOnly: 1, }, features) }) } @@ -173,12 +168,16 @@ func TestDeleteLicense(t *testing.T) { coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ AccountID: "testing", - AuditLog: true, + Features: license.Features{ + codersdk.FeatureAuditLog: 1, + }, }) coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ AccountID: "testing2", - AuditLog: true, - UserLimit: 200, + Features: license.Features{ + codersdk.FeatureAuditLog: 1, + codersdk.FeatureUserLimit: 200, + }, }) licenses, err := client.Licenses(ctx) diff --git a/enterprise/coderd/provisionerdaemons_test.go b/enterprise/coderd/provisionerdaemons_test.go index f603f3569e807..65c5eaebb26ca 100644 --- a/enterprise/coderd/provisionerdaemons_test.go +++ b/enterprise/coderd/provisionerdaemons_test.go @@ -12,6 +12,7 @@ import ( "github.com/coder/coder/coderd/provisionerdserver" "github.com/coder/coder/codersdk" "github.com/coder/coder/enterprise/coderd/coderdenttest" + "github.com/coder/coder/enterprise/coderd/license" "github.com/coder/coder/provisioner/echo" "github.com/coder/coder/provisionersdk/proto" ) @@ -36,7 +37,9 @@ func TestProvisionerDaemonServe(t *testing.T) { client := coderdenttest.New(t, nil) user := coderdtest.CreateFirstUser(t, client) coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - ExternalProvisionerDaemons: true, + Features: license.Features{ + codersdk.FeatureExternalProvisionerDaemons: 1, + }, }) srv, err := client.ServeProvisionerDaemon(context.Background(), user.OrganizationID, []codersdk.ProvisionerType{ codersdk.ProvisionerTypeEcho, @@ -50,7 +53,9 @@ func TestProvisionerDaemonServe(t *testing.T) { client := coderdenttest.New(t, nil) user := coderdtest.CreateFirstUser(t, client) coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - ExternalProvisionerDaemons: true, + Features: license.Features{ + codersdk.FeatureExternalProvisionerDaemons: 1, + }, }) another := coderdtest.CreateAnotherUser(t, client, user.OrganizationID) _, err := another.ServeProvisionerDaemon(context.Background(), user.OrganizationID, []codersdk.ProvisionerType{ @@ -69,7 +74,9 @@ func TestProvisionerDaemonServe(t *testing.T) { client := coderdenttest.New(t, nil) user := coderdtest.CreateFirstUser(t, client) coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - ExternalProvisionerDaemons: true, + Features: license.Features{ + codersdk.FeatureExternalProvisionerDaemons: 1, + }, }) closer := coderdtest.NewExternalProvisionerDaemon(t, client, user.OrganizationID, map[string]string{ provisionerdserver.TagScope: provisionerdserver.ScopeUser, diff --git a/enterprise/coderd/replicas_test.go b/enterprise/coderd/replicas_test.go index 4713c276adaf1..4e910ac84a56c 100644 --- a/enterprise/coderd/replicas_test.go +++ b/enterprise/coderd/replicas_test.go @@ -14,6 +14,7 @@ import ( "github.com/coder/coder/coderd/database/dbtestutil" "github.com/coder/coder/codersdk" "github.com/coder/coder/enterprise/coderd/coderdenttest" + "github.com/coder/coder/enterprise/coderd/license" "github.com/coder/coder/testutil" ) @@ -58,7 +59,9 @@ func TestReplicas(t *testing.T) { }) firstUser := coderdtest.CreateFirstUser(t, firstClient) coderdenttest.AddLicense(t, firstClient, coderdenttest.LicenseOptions{ - HighAvailability: true, + Features: license.Features{ + codersdk.FeatureHighAvailability: 1, + }, }) secondClient := coderdenttest.New(t, &coderdenttest.Options{ @@ -100,7 +103,9 @@ func TestReplicas(t *testing.T) { }) firstUser := coderdtest.CreateFirstUser(t, firstClient) coderdenttest.AddLicense(t, firstClient, coderdenttest.LicenseOptions{ - HighAvailability: true, + Features: license.Features{ + codersdk.FeatureHighAvailability: 1, + }, }) secondClient := coderdenttest.New(t, &coderdenttest.Options{ diff --git a/enterprise/coderd/scim_test.go b/enterprise/coderd/scim_test.go index c0ebf1356d120..491b4f8180aa8 100644 --- a/enterprise/coderd/scim_test.go +++ b/enterprise/coderd/scim_test.go @@ -15,6 +15,7 @@ import ( "github.com/coder/coder/cryptorand" "github.com/coder/coder/enterprise/coderd" "github.com/coder/coder/enterprise/coderd/coderdenttest" + "github.com/coder/coder/enterprise/coderd/license" "github.com/coder/coder/testutil" ) @@ -66,7 +67,9 @@ func TestScim(t *testing.T) { _ = coderdtest.CreateFirstUser(t, client) coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ AccountID: "coolin", - SCIM: false, + Features: license.Features{ + codersdk.FeatureSCIM: 0, + }, }) res, err := client.Request(ctx, "POST", "/scim/v2/Users", struct{}{}) @@ -85,7 +88,9 @@ func TestScim(t *testing.T) { _ = coderdtest.CreateFirstUser(t, client) coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ AccountID: "coolin", - SCIM: true, + Features: license.Features{ + codersdk.FeatureSCIM: 1, + }, }) res, err := client.Request(ctx, "POST", "/scim/v2/Users", struct{}{}) @@ -105,7 +110,9 @@ func TestScim(t *testing.T) { _ = coderdtest.CreateFirstUser(t, client) coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ AccountID: "coolin", - SCIM: true, + Features: license.Features{ + codersdk.FeatureSCIM: 1, + }, }) sUser := makeScimUser(t) @@ -136,7 +143,9 @@ func TestScim(t *testing.T) { _ = coderdtest.CreateFirstUser(t, client) coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ AccountID: "coolin", - SCIM: false, + Features: license.Features{ + codersdk.FeatureSCIM: 0, + }, }) res, err := client.Request(ctx, "PATCH", "/scim/v2/Users/bob", struct{}{}) @@ -155,7 +164,9 @@ func TestScim(t *testing.T) { _ = coderdtest.CreateFirstUser(t, client) coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ AccountID: "coolin", - SCIM: true, + Features: license.Features{ + codersdk.FeatureSCIM: 1, + }, }) res, err := client.Request(ctx, "PATCH", "/scim/v2/Users/bob", struct{}{}) @@ -175,7 +186,9 @@ func TestScim(t *testing.T) { _ = coderdtest.CreateFirstUser(t, client) coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ AccountID: "coolin", - SCIM: true, + Features: license.Features{ + codersdk.FeatureSCIM: 1, + }, }) sUser := makeScimUser(t) diff --git a/enterprise/coderd/templates_test.go b/enterprise/coderd/templates_test.go index f257766b0bddb..725c6cc5eb621 100644 --- a/enterprise/coderd/templates_test.go +++ b/enterprise/coderd/templates_test.go @@ -15,6 +15,7 @@ import ( "github.com/coder/coder/codersdk" "github.com/coder/coder/cryptorand" "github.com/coder/coder/enterprise/coderd/coderdenttest" + "github.com/coder/coder/enterprise/coderd/license" "github.com/coder/coder/provisioner/echo" "github.com/coder/coder/testutil" ) @@ -27,7 +28,9 @@ func TestTemplateACL(t *testing.T) { client := coderdenttest.New(t, nil) user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) _, user2 := coderdtest.CreateAnotherUserWithUser(t, client, user.OrganizationID) @@ -68,7 +71,9 @@ func TestTemplateACL(t *testing.T) { client := coderdenttest.New(t, nil) user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) _, user1 := coderdtest.CreateAnotherUserWithUser(t, client, user.OrganizationID) @@ -92,7 +97,9 @@ func TestTemplateACL(t *testing.T) { client := coderdenttest.New(t, nil) user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) client1, _ := coderdtest.CreateAnotherUserWithUser(t, client, user.OrganizationID) @@ -142,7 +149,9 @@ func TestTemplateACL(t *testing.T) { client := coderdenttest.New(t, nil) user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) _, user1 := coderdtest.CreateAnotherUserWithUser(t, client, user.OrganizationID) @@ -180,7 +189,9 @@ func TestTemplateACL(t *testing.T) { client := coderdenttest.New(t, nil) user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) _, user1 := coderdtest.CreateAnotherUserWithUser(t, client, user.OrganizationID) @@ -218,7 +229,9 @@ func TestTemplateACL(t *testing.T) { client := coderdenttest.New(t, nil) user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) @@ -266,7 +279,9 @@ func TestTemplateACL(t *testing.T) { client := coderdenttest.New(t, nil) user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) client1, user1 := coderdtest.CreateAnotherUserWithUser(t, client, user.OrganizationID) @@ -322,7 +337,9 @@ func TestUpdateTemplateACL(t *testing.T) { client := coderdenttest.New(t, nil) user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) _, user2 := coderdtest.CreateAnotherUserWithUser(t, client, user.OrganizationID) @@ -374,8 +391,10 @@ func TestUpdateTemplateACL(t *testing.T) { user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, - AuditLog: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + codersdk.FeatureAuditLog: 1, + }, }) version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) @@ -405,7 +424,9 @@ func TestUpdateTemplateACL(t *testing.T) { client := coderdenttest.New(t, nil) user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) _, user2 := coderdtest.CreateAnotherUserWithUser(t, client, user.OrganizationID) @@ -466,7 +487,9 @@ func TestUpdateTemplateACL(t *testing.T) { client := coderdenttest.New(t, nil) user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) @@ -491,7 +514,9 @@ func TestUpdateTemplateACL(t *testing.T) { client := coderdenttest.New(t, nil) user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) @@ -516,7 +541,9 @@ func TestUpdateTemplateACL(t *testing.T) { client := coderdenttest.New(t, nil) user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) _, user2 := coderdtest.CreateAnotherUserWithUser(t, client, user.OrganizationID) @@ -542,7 +569,9 @@ func TestUpdateTemplateACL(t *testing.T) { client := coderdenttest.New(t, nil) user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) client2, user2 := coderdtest.CreateAnotherUserWithUser(t, client, user.OrganizationID) @@ -577,7 +606,9 @@ func TestUpdateTemplateACL(t *testing.T) { client := coderdenttest.New(t, nil) user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) client2, user2 := coderdtest.CreateAnotherUserWithUser(t, client, user.OrganizationID) @@ -619,7 +650,9 @@ func TestUpdateTemplateACL(t *testing.T) { client := coderdenttest.New(t, nil) user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) @@ -641,7 +674,9 @@ func TestUpdateTemplateACL(t *testing.T) { client := coderdenttest.New(t, nil) user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) client1, user1 := coderdtest.CreateAnotherUserWithUser(t, client, user.OrganizationID) @@ -706,7 +741,9 @@ func TestUpdateTemplateACL(t *testing.T) { client := coderdenttest.New(t, nil) user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) client1, _ := coderdtest.CreateAnotherUserWithUser(t, client, user.OrganizationID) @@ -764,7 +801,9 @@ func TestTemplateAccess(t *testing.T) { ownerClient := coderdenttest.New(t, nil) owner := coderdtest.CreateFirstUser(t, ownerClient) _ = coderdenttest.AddLicense(t, ownerClient, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) type coderUser struct { diff --git a/enterprise/coderd/workspaceagents_test.go b/enterprise/coderd/workspaceagents_test.go index b103e8e2e4e41..aaef3c28f999a 100644 --- a/enterprise/coderd/workspaceagents_test.go +++ b/enterprise/coderd/workspaceagents_test.go @@ -15,6 +15,7 @@ import ( "github.com/coder/coder/coderd/coderdtest" "github.com/coder/coder/codersdk" "github.com/coder/coder/enterprise/coderd/coderdenttest" + "github.com/coder/coder/enterprise/coderd/license" "github.com/coder/coder/provisioner/echo" "github.com/coder/coder/provisionersdk/proto" "github.com/coder/coder/testutil" @@ -39,7 +40,9 @@ func TestBlockNonBrowser(t *testing.T) { }) user := coderdtest.CreateFirstUser(t, client) coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - BrowserOnly: true, + Features: license.Features{ + codersdk.FeatureBrowserOnly: 1, + }, }) _, agent := setupWorkspaceAgent(t, client, user, 0) _, err := client.DialWorkspaceAgent(context.Background(), agent.ID, nil) @@ -56,7 +59,9 @@ func TestBlockNonBrowser(t *testing.T) { }) user := coderdtest.CreateFirstUser(t, client) coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - BrowserOnly: false, + Features: license.Features{ + codersdk.FeatureBrowserOnly: 0, + }, }) _, agent := setupWorkspaceAgent(t, client, user, 0) conn, err := client.DialWorkspaceAgent(context.Background(), agent.ID, nil) diff --git a/enterprise/coderd/workspacequota_test.go b/enterprise/coderd/workspacequota_test.go index 98118f310aa7f..ed4b5448fe0a4 100644 --- a/enterprise/coderd/workspacequota_test.go +++ b/enterprise/coderd/workspacequota_test.go @@ -10,6 +10,7 @@ import ( "github.com/coder/coder/coderd/coderdtest" "github.com/coder/coder/codersdk" "github.com/coder/coder/enterprise/coderd/coderdenttest" + "github.com/coder/coder/enterprise/coderd/license" "github.com/coder/coder/provisioner/echo" "github.com/coder/coder/provisionersdk/proto" "github.com/coder/coder/testutil" @@ -45,7 +46,9 @@ func TestWorkspaceQuota(t *testing.T) { user := coderdtest.CreateFirstUser(t, client) coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) verifyQuota(ctx, t, client, 0, 0) diff --git a/enterprise/coderd/workspaces_test.go b/enterprise/coderd/workspaces_test.go index 824b3febb191c..ef14d8a6be628 100644 --- a/enterprise/coderd/workspaces_test.go +++ b/enterprise/coderd/workspaces_test.go @@ -12,6 +12,7 @@ import ( "github.com/coder/coder/coderd/util/ptr" "github.com/coder/coder/codersdk" "github.com/coder/coder/enterprise/coderd/coderdenttest" + "github.com/coder/coder/enterprise/coderd/license" "github.com/coder/coder/testutil" ) @@ -26,7 +27,9 @@ func TestCreateWorkspace(t *testing.T) { client := coderdenttest.New(t, nil) user := coderdtest.CreateFirstUser(t, client) _ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{ - TemplateRBAC: true, + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, }) version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) From 445640b177c1555dda7ced8a7fe02daec2342b23 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Mon, 16 Jan 2023 22:48:02 +0000 Subject: [PATCH 05/13] fixup! refactor: deduplicate / type license feature code --- site/src/api/typesGenerated.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 7570a7d9289de..e7933bfb2e929 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -331,7 +331,7 @@ export interface DeploymentConfigField { // From codersdk/features.go export interface Entitlements { - readonly features: Record + readonly features: Record readonly warnings: string[] readonly errors: string[] readonly has_license: boolean @@ -1030,6 +1030,18 @@ export type BuildReason = "autostart" | "autostop" | "initiator" // From codersdk/features.go export type Entitlement = "entitled" | "grace_period" | "not_entitled" +// From codersdk/features.go +export type FeatureName = + | "appearance" + | "audit_log" + | "browser_only" + | "external_provisioner_daemons" + | "high_availability" + | "multiple_git_auth" + | "scim" + | "template_rbac" + | "user_limit" + // From codersdk/agentconn.go export type ListeningPortNetwork = "tcp" From 32d89dc1d82f6aa941c9ee3555d9267e4507028b Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Mon, 16 Jan 2023 23:58:25 +0000 Subject: [PATCH 06/13] Update frontend types --- codersdk/features.go | 5 +- scripts/apitypings/main.go | 14 +- site/src/AppRouter.tsx | 3 +- site/src/api/api.ts | 7 +- site/src/api/types.ts | 11 -- site/src/api/typesGenerated.ts | 138 ++++++++++++++++-- .../entitlements/entitlementsSelectors.ts | 4 +- 7 files changed, 144 insertions(+), 38 deletions(-) diff --git a/codersdk/features.go b/codersdk/features.go index 4593e9d3fe9f0..916df4db6110d 100644 --- a/codersdk/features.go +++ b/codersdk/features.go @@ -60,12 +60,11 @@ func (n FeatureName) Humanize() string { // Warning: We don't know if we need this functionality. // This method may disappear at any time. func (n FeatureName) AlwaysEnable() bool { - m := map[FeatureName]bool{ + return map[FeatureName]bool{ FeatureMultipleGitAuth: true, FeatureExternalProvisionerDaemons: true, FeatureAppearance: true, - } - return m[n] + }[n] } type Feature struct { diff --git a/scripts/apitypings/main.go b/scripts/apitypings/main.go index 5351084a8c041..c128cd8ec29a8 100644 --- a/scripts/apitypings/main.go +++ b/scripts/apitypings/main.go @@ -223,6 +223,18 @@ func (g *Generator) generateAll() (*TypescriptTypes, error) { name, strings.Join(values, " | "), )) + var pluralName string + if strings.HasSuffix(name, "s") { + pluralName = name + "es" + } else { + pluralName = name + "s" + } + + // Generate array used for enumerating all possible values. + _, _ = s.WriteString(fmt.Sprintf("export const %s: %s[] = [%s]\n", + pluralName, name, strings.Join(values, ", "), + )) + enumCodeBlocks[name] = s.String() } @@ -645,7 +657,7 @@ func (g *Generator) typescriptType(ty types.Type) (TypescriptType, error) { } aboveTypeLine = aboveTypeLine + valueType.AboveTypeLine return TypescriptType{ - ValueType: fmt.Sprintf("Record<%s, %s>", keyType.ValueType, valueType.ValueType), + ValueType: fmt.Sprintf("Partial>", keyType.ValueType, valueType.ValueType), AboveTypeLine: aboveTypeLine, }, nil case *types.Slice, *types.Array: diff --git a/site/src/AppRouter.tsx b/site/src/AppRouter.tsx index 3c7ce3c828d00..7342994685944 100644 --- a/site/src/AppRouter.tsx +++ b/site/src/AppRouter.tsx @@ -1,5 +1,4 @@ import { useSelector } from "@xstate/react" -import { FeatureNames } from "api/types" import { FullScreenLoader } from "components/Loader/FullScreenLoader" import { RequirePermission } from "components/RequirePermission/RequirePermission" import { TemplateLayout } from "components/TemplateLayout/TemplateLayout" @@ -196,7 +195,7 @@ export const AppRouter: FC = () => { element={ diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 5b32f38b572b5..5882a2cfef054 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -19,12 +19,13 @@ export const hardCodedCSRFCookie = (): string => { // defaultEntitlements has a default set of disabled functionality. export const defaultEntitlements = (): TypesGen.Entitlements => { const features: TypesGen.Entitlements["features"] = {} - for (const feature in Types.FeatureNames) { - features[feature] = { + for (const feature in TypesGen.FeatureNames) { + features[feature as TypesGen.FeatureName] = { enabled: false, entitlement: "not_entitled", } } + return { features: features, has_license: false, @@ -35,7 +36,7 @@ export const defaultEntitlements = (): TypesGen.Entitlements => { } } -// Always attach CSRF token to all requests. +// Always attach CSRF token to all re = "not_entitled"quests. // In puppeteer the document is undefined. In those cases, just // do nothing. const token = diff --git a/site/src/api/types.ts b/site/src/api/types.ts index cc8103a080e6b..daf4e451ac5e8 100644 --- a/site/src/api/types.ts +++ b/site/src/api/types.ts @@ -14,14 +14,3 @@ export interface ReconnectingPTYRequest { export type WorkspaceBuildTransition = "start" | "stop" | "delete" export type Message = { message: string } - -// Keep up to date with coder/codersdk/features.go -export enum FeatureNames { - AuditLog = "audit_log", - UserLimit = "user_limit", - BrowserOnly = "browser_only", - SCIM = "scim", - TemplateRBAC = "template_rbac", - HighAvailability = "high_availability", - Appearance = "appearance", -} diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index e7933bfb2e929..715e5f1ea09a6 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -43,7 +43,7 @@ export interface AssignableRoles extends Role { } // From codersdk/audit.go -export type AuditDiff = Record +export type AuditDiff = Partial> // From codersdk/audit.go export interface AuditDiffField { @@ -112,11 +112,11 @@ export interface AuthorizationObject { // From codersdk/authorization.go export interface AuthorizationRequest { - readonly checks: Record + readonly checks: Partial> } // From codersdk/authorization.go -export type AuthorizationResponse = Record +export type AuthorizationResponse = Partial> // From codersdk/workspaceagents.go export interface AzureInstanceIdentityToken { @@ -198,7 +198,7 @@ export interface CreateTemplateVersionRequest { readonly file_id?: string readonly example_id?: string readonly provisioner: ProvisionerType - readonly tags: Record + readonly tags: Partial> readonly parameter_values?: CreateParameterRequest[] } @@ -331,7 +331,7 @@ export interface DeploymentConfigField { // From codersdk/features.go export interface Entitlements { - readonly features: Record + readonly features: Partial> readonly warnings: string[] readonly errors: string[] readonly has_license: boolean @@ -407,7 +407,7 @@ export interface License { readonly uuid: string readonly uploaded_at: string // eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO explain why this is needed - readonly claims: Record + readonly claims: Partial> } // From codersdk/agentconn.go @@ -561,7 +561,7 @@ export interface ProvisionerDaemon { readonly updated_at?: string readonly name: string readonly provisioners: ProvisionerType[] - readonly tags: Record + readonly tags: Partial> } // From codersdk/provisionerdaemons.go @@ -575,7 +575,7 @@ export interface ProvisionerJob { readonly status: ProvisionerJobStatus readonly worker_id?: string readonly file_id: string - readonly tags: Record + readonly tags: Partial> } // From codersdk/provisionerdaemons.go @@ -691,9 +691,8 @@ export interface TemplateACL { } // From codersdk/templates.go -export type TemplateBuildTimeStats = Record< - WorkspaceTransition, - TransitionStats +export type TemplateBuildTimeStats = Partial< + Record > // From codersdk/templates.go @@ -777,8 +776,8 @@ export interface UpdateRoles { // From codersdk/templates.go export interface UpdateTemplateACL { - readonly user_perms?: Record - readonly group_perms?: Record + readonly user_perms?: Partial> + readonly group_perms?: Partial> } // From codersdk/templates.go @@ -838,7 +837,7 @@ export interface User { // From codersdk/users.go export interface UserRoles { readonly roles: string[] - readonly organization_roles: Record + readonly organization_roles: Partial> } // From codersdk/users.go @@ -885,13 +884,13 @@ export interface WorkspaceAgent { readonly resource_id: string readonly instance_id?: string readonly architecture: string - readonly environment_variables: Record + readonly environment_variables: Partial> readonly operating_system: string readonly startup_script?: string readonly directory?: string readonly version: string readonly apps: WorkspaceApp[] - readonly latency?: Record + readonly latency?: Partial> readonly connection_timeout_seconds: number readonly troubleshooting_url: string } @@ -1020,15 +1019,33 @@ export interface WorkspacesResponse { // From codersdk/apikey.go export type APIKeyScope = "all" | "application_connect" +export const APIKeyScopes: APIKeyScope[] = ["all", "application_connect"] // From codersdk/audit.go export type AuditAction = "create" | "delete" | "start" | "stop" | "write" +export const AuditActions: AuditAction[] = [ + "create", + "delete", + "start", + "stop", + "write", +] // From codersdk/workspacebuilds.go export type BuildReason = "autostart" | "autostop" | "initiator" +export const BuildReasons: BuildReason[] = [ + "autostart", + "autostop", + "initiator", +] // From codersdk/features.go export type Entitlement = "entitled" | "grace_period" | "not_entitled" +export const Entitlements: Entitlement[] = [ + "entitled", + "grace_period", + "not_entitled", +] // From codersdk/features.go export type FeatureName = @@ -1041,33 +1058,60 @@ export type FeatureName = | "scim" | "template_rbac" | "user_limit" +export const FeatureNames: FeatureName[] = [ + "appearance", + "audit_log", + "browser_only", + "external_provisioner_daemons", + "high_availability", + "multiple_git_auth", + "scim", + "template_rbac", + "user_limit", +] // From codersdk/agentconn.go export type ListeningPortNetwork = "tcp" +export const ListeningPortNetworks: ListeningPortNetwork[] = ["tcp"] // From codersdk/provisionerdaemons.go export type LogLevel = "debug" | "error" | "info" | "trace" | "warn" +export const LogLevels: LogLevel[] = ["debug", "error", "info", "trace", "warn"] // From codersdk/provisionerdaemons.go export type LogSource = "provisioner" | "provisioner_daemon" +export const LogSources: LogSource[] = ["provisioner", "provisioner_daemon"] // From codersdk/apikey.go export type LoginType = "github" | "oidc" | "password" | "token" +export const LoginTypes: LoginType[] = ["github", "oidc", "password", "token"] // From codersdk/parameters.go export type ParameterDestinationScheme = | "environment_variable" | "none" | "provisioner_variable" +export const ParameterDestinationSchemes: ParameterDestinationScheme[] = [ + "environment_variable", + "none", + "provisioner_variable", +] // From codersdk/parameters.go export type ParameterScope = "import_job" | "template" | "workspace" +export const ParameterScopes: ParameterScope[] = [ + "import_job", + "template", + "workspace", +] // From codersdk/parameters.go export type ParameterSourceScheme = "data" | "none" +export const ParameterSourceSchemes: ParameterSourceScheme[] = ["data", "none"] // From codersdk/parameters.go export type ParameterTypeSystem = "hcl" | "none" +export const ParameterTypeSystems: ParameterTypeSystem[] = ["hcl", "none"] // From codersdk/provisionerdaemons.go export type ProvisionerJobStatus = @@ -1077,12 +1121,22 @@ export type ProvisionerJobStatus = | "pending" | "running" | "succeeded" +export const ProvisionerJobStatuses: ProvisionerJobStatus[] = [ + "canceled", + "canceling", + "failed", + "pending", + "running", + "succeeded", +] // From codersdk/organizations.go export type ProvisionerStorageMethod = "file" +export const ProvisionerStorageMethods: ProvisionerStorageMethod[] = ["file"] // From codersdk/organizations.go export type ProvisionerType = "echo" | "terraform" +export const ProvisionerTypes: ProvisionerType[] = ["echo", "terraform"] // From codersdk/audit.go export type ResourceType = @@ -1095,15 +1149,33 @@ export type ResourceType = | "user" | "workspace" | "workspace_build" +export const ResourceTypes: ResourceType[] = [ + "api_key", + "git_ssh_key", + "group", + "organization", + "template", + "template_version", + "user", + "workspace", + "workspace_build", +] // From codersdk/sse.go export type ServerSentEventType = "data" | "error" | "ping" +export const ServerSentEventTypes: ServerSentEventType[] = [ + "data", + "error", + "ping", +] // From codersdk/templates.go export type TemplateRole = "" | "admin" | "use" +export const TemplateRoles: TemplateRole[] = ["", "admin", "use"] // From codersdk/users.go export type UserStatus = "active" | "suspended" +export const UserStatuses: UserStatus[] = ["active", "suspended"] // From codersdk/workspaceagents.go export type WorkspaceAgentStatus = @@ -1111,6 +1183,12 @@ export type WorkspaceAgentStatus = | "connecting" | "disconnected" | "timeout" +export const WorkspaceAgentStatuses: WorkspaceAgentStatus[] = [ + "connected", + "connecting", + "disconnected", + "timeout", +] // From codersdk/workspaceapps.go export type WorkspaceAppHealth = @@ -1118,9 +1196,20 @@ export type WorkspaceAppHealth = | "healthy" | "initializing" | "unhealthy" +export const WorkspaceAppHealths: WorkspaceAppHealth[] = [ + "disabled", + "healthy", + "initializing", + "unhealthy", +] // From codersdk/workspaceapps.go export type WorkspaceAppSharingLevel = "authenticated" | "owner" | "public" +export const WorkspaceAppSharingLevels: WorkspaceAppSharingLevel[] = [ + "authenticated", + "owner", + "public", +] // From codersdk/workspacebuilds.go export type WorkspaceStatus = @@ -1134,9 +1223,26 @@ export type WorkspaceStatus = | "starting" | "stopped" | "stopping" +export const WorkspaceStatuses: WorkspaceStatus[] = [ + "canceled", + "canceling", + "deleted", + "deleting", + "failed", + "pending", + "running", + "starting", + "stopped", + "stopping", +] // From codersdk/workspacebuilds.go export type WorkspaceTransition = "delete" | "start" | "stop" +export const WorkspaceTransitions: WorkspaceTransition[] = [ + "delete", + "start", + "stop", +] // From codersdk/deploymentconfig.go export type Flaggable = string | number | boolean | string[] | GitAuthConfig[] diff --git a/site/src/xServices/entitlements/entitlementsSelectors.ts b/site/src/xServices/entitlements/entitlementsSelectors.ts index 780eb47c0a191..b634a2a25ed0c 100644 --- a/site/src/xServices/entitlements/entitlementsSelectors.ts +++ b/site/src/xServices/entitlements/entitlementsSelectors.ts @@ -1,4 +1,4 @@ -import { Feature } from "api/typesGenerated" +import { Feature, FeatureName } from "api/typesGenerated" import { State } from "xstate" import { EntitlementsContext, EntitlementsEvent } from "./entitlementsXService" @@ -28,7 +28,7 @@ export const getFeatureVisibility = ( export const selectFeatureVisibility = ( state: EntitlementState, -): Record => { +): Record => { return getFeatureVisibility( state.context.entitlements.has_license, state.context.entitlements.features, From f1d95e477adc9dd6a9c4e5fe2113b645649aadfe Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Tue, 17 Jan 2023 00:19:20 +0000 Subject: [PATCH 07/13] fixup! Update frontend types --- scripts/apitypings/main.go | 25 ++++++++++++++++++++-- site/src/api/typesGenerated.ts | 39 +++++++++++++++++++++------------- 2 files changed, 47 insertions(+), 17 deletions(-) diff --git a/scripts/apitypings/main.go b/scripts/apitypings/main.go index c128cd8ec29a8..4f3f88c3163d1 100644 --- a/scripts/apitypings/main.go +++ b/scripts/apitypings/main.go @@ -63,7 +63,20 @@ type TypescriptTypes struct { // String just combines all the codeblocks. func (t TypescriptTypes) String() string { var s strings.Builder - _, _ = s.WriteString("// Code generated by 'make site/src/api/typesGenerated.ts'. DO NOT EDIT.\n\n") + const prelude = ` +// Code generated by 'make site/src/api/typesGenerated.ts'. DO NOT EDIT. + + +// PartialRecord is useful when a union string literal is a record key type but +// we do not want to fill all keys on every record. +// +// eslint-disable-next-line @typescript-eslint/no-explicit-any -- necessary for abstract record support +type PartialRecord = { + [P in K]?: T; +}; + +` + _, _ = s.WriteString(prelude) sortedTypes := make([]string, 0, len(t.Types)) sortedEnums := make([]string, 0, len(t.Enums)) @@ -656,8 +669,16 @@ func (g *Generator) typescriptType(ty types.Type) (TypescriptType, error) { aboveTypeLine = aboveTypeLine + "\n" } aboveTypeLine = aboveTypeLine + valueType.AboveTypeLine + + var tsValueType string + if keyType.ValueType == "string" { + tsValueType = fmt.Sprintf("Record<%s, %s>", keyType.ValueType, valueType.ValueType) + } else { + tsValueType = fmt.Sprintf("PartialRecord<%s, %s>", keyType.ValueType, valueType.ValueType) + } + return TypescriptType{ - ValueType: fmt.Sprintf("Partial>", keyType.ValueType, valueType.ValueType), + ValueType: tsValueType, AboveTypeLine: aboveTypeLine, }, nil case *types.Slice, *types.Array: diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 715e5f1ea09a6..46995e905c458 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -1,5 +1,13 @@ // Code generated by 'make site/src/api/typesGenerated.ts'. DO NOT EDIT. +// PartialRecord is useful when a union string literal is a record key type but +// we do not want to fill all keys on every record. +// +// eslint-disable-next-line @typescript-eslint/no-explicit-any -- necessary for abstract record support +type PartialRecord = { + [P in K]?: T +} + // From codersdk/apikey.go export interface APIKey { readonly id: string @@ -43,7 +51,7 @@ export interface AssignableRoles extends Role { } // From codersdk/audit.go -export type AuditDiff = Partial> +export type AuditDiff = Record // From codersdk/audit.go export interface AuditDiffField { @@ -112,11 +120,11 @@ export interface AuthorizationObject { // From codersdk/authorization.go export interface AuthorizationRequest { - readonly checks: Partial> + readonly checks: Record } // From codersdk/authorization.go -export type AuthorizationResponse = Partial> +export type AuthorizationResponse = Record // From codersdk/workspaceagents.go export interface AzureInstanceIdentityToken { @@ -198,7 +206,7 @@ export interface CreateTemplateVersionRequest { readonly file_id?: string readonly example_id?: string readonly provisioner: ProvisionerType - readonly tags: Partial> + readonly tags: Record readonly parameter_values?: CreateParameterRequest[] } @@ -331,7 +339,7 @@ export interface DeploymentConfigField { // From codersdk/features.go export interface Entitlements { - readonly features: Partial> + readonly features: PartialRecord readonly warnings: string[] readonly errors: string[] readonly has_license: boolean @@ -407,7 +415,7 @@ export interface License { readonly uuid: string readonly uploaded_at: string // eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO explain why this is needed - readonly claims: Partial> + readonly claims: Record } // From codersdk/agentconn.go @@ -561,7 +569,7 @@ export interface ProvisionerDaemon { readonly updated_at?: string readonly name: string readonly provisioners: ProvisionerType[] - readonly tags: Partial> + readonly tags: Record } // From codersdk/provisionerdaemons.go @@ -575,7 +583,7 @@ export interface ProvisionerJob { readonly status: ProvisionerJobStatus readonly worker_id?: string readonly file_id: string - readonly tags: Partial> + readonly tags: Record } // From codersdk/provisionerdaemons.go @@ -691,8 +699,9 @@ export interface TemplateACL { } // From codersdk/templates.go -export type TemplateBuildTimeStats = Partial< - Record +export type TemplateBuildTimeStats = PartialRecord< + WorkspaceTransition, + TransitionStats > // From codersdk/templates.go @@ -776,8 +785,8 @@ export interface UpdateRoles { // From codersdk/templates.go export interface UpdateTemplateACL { - readonly user_perms?: Partial> - readonly group_perms?: Partial> + readonly user_perms?: Record + readonly group_perms?: Record } // From codersdk/templates.go @@ -837,7 +846,7 @@ export interface User { // From codersdk/users.go export interface UserRoles { readonly roles: string[] - readonly organization_roles: Partial> + readonly organization_roles: Record } // From codersdk/users.go @@ -884,13 +893,13 @@ export interface WorkspaceAgent { readonly resource_id: string readonly instance_id?: string readonly architecture: string - readonly environment_variables: Partial> + readonly environment_variables: Record readonly operating_system: string readonly startup_script?: string readonly directory?: string readonly version: string readonly apps: WorkspaceApp[] - readonly latency?: Partial> + readonly latency?: Record readonly connection_timeout_seconds: number readonly troubleshooting_url: string } From 48cda29da3bb32f03ef20a164539d9f21b58b7ae Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Tue, 17 Jan 2023 00:48:50 +0000 Subject: [PATCH 08/13] fixup! Update frontend types --- scripts/apitypings/testdata/enums/enums.ts | 10 ++++++++++ scripts/apitypings/testdata/generics/generics.ts | 9 +++++++++ 2 files changed, 19 insertions(+) diff --git a/scripts/apitypings/testdata/enums/enums.ts b/scripts/apitypings/testdata/enums/enums.ts index 0aca42a2feebd..c7f44b20ecf25 100644 --- a/scripts/apitypings/testdata/enums/enums.ts +++ b/scripts/apitypings/testdata/enums/enums.ts @@ -1,4 +1,14 @@ // Code generated by 'make site/src/api/typesGenerated.ts'. DO NOT EDIT. + +// PartialRecord is useful when a union string literal is a record key type but +// we do not want to fill all keys on every record. +// +// eslint-disable-next-line @typescript-eslint/no-explicit-any -- necessary for abstract record support +type PartialRecord = { + [P in K]?: T; +}; + // From codersdk/enums.go export type Enum = "bar" | "baz" | "foo" | "qux" +export const Enums: Enum[] = ["bar", "baz", "foo", "qux"] diff --git a/scripts/apitypings/testdata/generics/generics.ts b/scripts/apitypings/testdata/generics/generics.ts index ce851f0cc6ff5..083044ea912d5 100644 --- a/scripts/apitypings/testdata/generics/generics.ts +++ b/scripts/apitypings/testdata/generics/generics.ts @@ -1,5 +1,14 @@ // Code generated by 'make site/src/api/typesGenerated.ts'. DO NOT EDIT. + +// PartialRecord is useful when a union string literal is a record key type but +// we do not want to fill all keys on every record. +// +// eslint-disable-next-line @typescript-eslint/no-explicit-any -- necessary for abstract record support +type PartialRecord = { + [P in K]?: T; +}; + // From codersdk/generics.go export interface ComplexGeneric { readonly dynamic: GenericFields From 277ef448a19045c856fe5dd4cf2c4b30a434960e Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Tue, 17 Jan 2023 00:50:03 +0000 Subject: [PATCH 09/13] fixup! Update frontend types --- site/src/api/api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 5882a2cfef054..6dfa1fc2a0d9e 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -36,7 +36,7 @@ export const defaultEntitlements = (): TypesGen.Entitlements => { } } -// Always attach CSRF token to all re = "not_entitled"quests. +// Always attach CSRF token to all requests. // In puppeteer the document is undefined. In those cases, just // do nothing. const token = From f165432ddfe725c4cd59eee399ef8cc38d3e3bc9 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Tue, 17 Jan 2023 00:51:55 +0000 Subject: [PATCH 10/13] fixup! Update frontend types --- site/src/components/Navbar/Navbar.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/site/src/components/Navbar/Navbar.tsx b/site/src/components/Navbar/Navbar.tsx index 811410e2b21cb..7941a95b1b92a 100644 --- a/site/src/components/Navbar/Navbar.tsx +++ b/site/src/components/Navbar/Navbar.tsx @@ -1,5 +1,4 @@ import { shallowEqual, useActor, useSelector } from "@xstate/react" -import { FeatureNames } from "api/types" import { useContext, FC } from "react" import { selectFeatureVisibility } from "xServices/entitlements/entitlementsSelectors" import { XServiceContext } from "../../xServices/StateContext" @@ -17,8 +16,7 @@ export const Navbar: FC = () => { shallowEqual, ) const canViewAuditLog = - featureVisibility[FeatureNames.AuditLog] && - Boolean(permissions?.viewAuditLog) + featureVisibility["audit_log"] && Boolean(permissions?.viewAuditLog) const canViewDeployment = Boolean(permissions?.viewDeploymentConfig) const onSignOut = () => authSend("SIGN_OUT") From 4bdd3c01acb1c649d0428cc3341a8c8a02eb0613 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Tue, 17 Jan 2023 00:58:35 +0000 Subject: [PATCH 11/13] fixup! Update frontend types --- scripts/apitypings/main.go | 19 +------------------ scripts/apitypings/testdata/enums/enums.ts | 9 --------- .../apitypings/testdata/generics/generics.ts | 9 --------- site/src/api/api.ts | 4 ++-- site/src/api/typesGenerated.ts | 13 ++----------- site/src/hooks/useFeatureVisibility.ts | 4 ++-- .../AppearanceSettingsPage.tsx | 3 +-- .../SecuritySettingsPage.tsx | 9 +++------ .../WorkspacePage/WorkspaceReadyPage.tsx | 5 ++--- 9 files changed, 13 insertions(+), 62 deletions(-) diff --git a/scripts/apitypings/main.go b/scripts/apitypings/main.go index 4f3f88c3163d1..3506b2df0143c 100644 --- a/scripts/apitypings/main.go +++ b/scripts/apitypings/main.go @@ -65,16 +65,6 @@ func (t TypescriptTypes) String() string { var s strings.Builder const prelude = ` // Code generated by 'make site/src/api/typesGenerated.ts'. DO NOT EDIT. - - -// PartialRecord is useful when a union string literal is a record key type but -// we do not want to fill all keys on every record. -// -// eslint-disable-next-line @typescript-eslint/no-explicit-any -- necessary for abstract record support -type PartialRecord = { - [P in K]?: T; -}; - ` _, _ = s.WriteString(prelude) @@ -670,15 +660,8 @@ func (g *Generator) typescriptType(ty types.Type) (TypescriptType, error) { } aboveTypeLine = aboveTypeLine + valueType.AboveTypeLine - var tsValueType string - if keyType.ValueType == "string" { - tsValueType = fmt.Sprintf("Record<%s, %s>", keyType.ValueType, valueType.ValueType) - } else { - tsValueType = fmt.Sprintf("PartialRecord<%s, %s>", keyType.ValueType, valueType.ValueType) - } - return TypescriptType{ - ValueType: tsValueType, + ValueType: fmt.Sprintf("Record<%s, %s>", keyType.ValueType, valueType.ValueType), AboveTypeLine: aboveTypeLine, }, nil case *types.Slice, *types.Array: diff --git a/scripts/apitypings/testdata/enums/enums.ts b/scripts/apitypings/testdata/enums/enums.ts index c7f44b20ecf25..21b6f1934e499 100644 --- a/scripts/apitypings/testdata/enums/enums.ts +++ b/scripts/apitypings/testdata/enums/enums.ts @@ -1,14 +1,5 @@ // Code generated by 'make site/src/api/typesGenerated.ts'. DO NOT EDIT. - -// PartialRecord is useful when a union string literal is a record key type but -// we do not want to fill all keys on every record. -// -// eslint-disable-next-line @typescript-eslint/no-explicit-any -- necessary for abstract record support -type PartialRecord = { - [P in K]?: T; -}; - // From codersdk/enums.go export type Enum = "bar" | "baz" | "foo" | "qux" export const Enums: Enum[] = ["bar", "baz", "foo", "qux"] diff --git a/scripts/apitypings/testdata/generics/generics.ts b/scripts/apitypings/testdata/generics/generics.ts index 083044ea912d5..ce851f0cc6ff5 100644 --- a/scripts/apitypings/testdata/generics/generics.ts +++ b/scripts/apitypings/testdata/generics/generics.ts @@ -1,14 +1,5 @@ // Code generated by 'make site/src/api/typesGenerated.ts'. DO NOT EDIT. - -// PartialRecord is useful when a union string literal is a record key type but -// we do not want to fill all keys on every record. -// -// eslint-disable-next-line @typescript-eslint/no-explicit-any -- necessary for abstract record support -type PartialRecord = { - [P in K]?: T; -}; - // From codersdk/generics.go export interface ComplexGeneric { readonly dynamic: GenericFields diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 6dfa1fc2a0d9e..c917ba67ce283 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -18,7 +18,7 @@ export const hardCodedCSRFCookie = (): string => { // defaultEntitlements has a default set of disabled functionality. export const defaultEntitlements = (): TypesGen.Entitlements => { - const features: TypesGen.Entitlements["features"] = {} + const features: Partial = {} for (const feature in TypesGen.FeatureNames) { features[feature as TypesGen.FeatureName] = { enabled: false, @@ -27,7 +27,7 @@ export const defaultEntitlements = (): TypesGen.Entitlements => { } return { - features: features, + features: features as TypesGen.Entitlements["features"], has_license: false, errors: [], warnings: [], diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 46995e905c458..299c202ace986 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -1,13 +1,4 @@ // Code generated by 'make site/src/api/typesGenerated.ts'. DO NOT EDIT. - -// PartialRecord is useful when a union string literal is a record key type but -// we do not want to fill all keys on every record. -// -// eslint-disable-next-line @typescript-eslint/no-explicit-any -- necessary for abstract record support -type PartialRecord = { - [P in K]?: T -} - // From codersdk/apikey.go export interface APIKey { readonly id: string @@ -339,7 +330,7 @@ export interface DeploymentConfigField { // From codersdk/features.go export interface Entitlements { - readonly features: PartialRecord + readonly features: Record readonly warnings: string[] readonly errors: string[] readonly has_license: boolean @@ -699,7 +690,7 @@ export interface TemplateACL { } // From codersdk/templates.go -export type TemplateBuildTimeStats = PartialRecord< +export type TemplateBuildTimeStats = Record< WorkspaceTransition, TransitionStats > diff --git a/site/src/hooks/useFeatureVisibility.ts b/site/src/hooks/useFeatureVisibility.ts index 715dbe76948e2..7e0a860972c58 100644 --- a/site/src/hooks/useFeatureVisibility.ts +++ b/site/src/hooks/useFeatureVisibility.ts @@ -1,10 +1,10 @@ import { useSelector } from "@xstate/react" -import { FeatureNames } from "api/types" +import { FeatureName } from "api/typesGenerated" import { useContext } from "react" import { selectFeatureVisibility } from "xServices/entitlements/entitlementsSelectors" import { XServiceContext } from "xServices/StateContext" -export const useFeatureVisibility = (): Record => { +export const useFeatureVisibility = (): Record => { const xServices = useContext(XServiceContext) return useSelector(xServices.entitlementsXService, selectFeatureVisibility) } diff --git a/site/src/pages/DeploySettingsPage/AppearanceSettingsPage/AppearanceSettingsPage.tsx b/site/src/pages/DeploySettingsPage/AppearanceSettingsPage/AppearanceSettingsPage.tsx index ee1e1809d9a2c..6c0b16cddf33b 100644 --- a/site/src/pages/DeploySettingsPage/AppearanceSettingsPage/AppearanceSettingsPage.tsx +++ b/site/src/pages/DeploySettingsPage/AppearanceSettingsPage/AppearanceSettingsPage.tsx @@ -1,5 +1,4 @@ import { useActor } from "@xstate/react" -import { FeatureNames } from "api/types" import { AppearanceConfig } from "api/typesGenerated" import { useContext, FC } from "react" import { Helmet } from "react-helmet-async" @@ -20,7 +19,7 @@ const AppearanceSettingsPage: FC = () => { const appearance = appearanceXService.context.appearance const isEntitled = - entitlementsState.context.entitlements.features[FeatureNames.Appearance] + entitlementsState.context.entitlements.features["appearance"] .entitlement !== "not_entitled" const updateAppearance = ( diff --git a/site/src/pages/DeploySettingsPage/SecuritySettingsPage/SecuritySettingsPage.tsx b/site/src/pages/DeploySettingsPage/SecuritySettingsPage/SecuritySettingsPage.tsx index 82f0b0c1cbedb..77300d16fac72 100644 --- a/site/src/pages/DeploySettingsPage/SecuritySettingsPage/SecuritySettingsPage.tsx +++ b/site/src/pages/DeploySettingsPage/SecuritySettingsPage/SecuritySettingsPage.tsx @@ -1,5 +1,4 @@ import { useActor } from "@xstate/react" -import { FeatureNames } from "api/types" import { useDeploySettings } from "components/DeploySettingsLayout/DeploySettingsLayout" import { useContext, FC } from "react" import { Helmet } from "react-helmet-async" @@ -21,13 +20,11 @@ const SecuritySettingsPage: FC = () => { diff --git a/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx b/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx index 7f801b17e8df8..3c8f6324ac35b 100644 --- a/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx @@ -1,5 +1,4 @@ import { useActor, useSelector } from "@xstate/react" -import { FeatureNames } from "api/types" import dayjs from "dayjs" import { useContext, useEffect } from "react" import { Helmet } from "react-helmet-async" @@ -123,9 +122,9 @@ export const WorkspaceReadyPage = ({ resources={workspace.latest_build.resources} builds={builds} canUpdateWorkspace={canUpdateWorkspace} - hideSSHButton={featureVisibility[FeatureNames.BrowserOnly]} + hideSSHButton={featureVisibility["browser_only"]} hideVSCodeDesktopButton={ - !experimental || featureVisibility[FeatureNames.BrowserOnly] + !experimental || featureVisibility["browser_only"] } workspaceErrors={{ [WorkspaceErrors.GET_RESOURCES_ERROR]: refreshWorkspaceWarning, From 138055aa24ac5425c931f95e118f4723324cd78a Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Tue, 17 Jan 2023 01:00:20 +0000 Subject: [PATCH 12/13] fixup! Update frontend types --- scripts/apitypings/main.go | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/apitypings/main.go b/scripts/apitypings/main.go index 3506b2df0143c..1d068795c0eb4 100644 --- a/scripts/apitypings/main.go +++ b/scripts/apitypings/main.go @@ -65,6 +65,7 @@ func (t TypescriptTypes) String() string { var s strings.Builder const prelude = ` // Code generated by 'make site/src/api/typesGenerated.ts'. DO NOT EDIT. + ` _, _ = s.WriteString(prelude) From 15dc805b603328577fd128f72e7f728167437d3f Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Tue, 17 Jan 2023 01:22:27 +0000 Subject: [PATCH 13/13] fixup! Update frontend types --- site/src/api/api.ts | 22 ++++++++++++++----- site/src/api/typesGenerated.ts | 1 + site/src/testHelpers/entities.ts | 11 +++++----- .../entitlements/entitlementsXService.ts | 3 ++- 4 files changed, 25 insertions(+), 12 deletions(-) diff --git a/site/src/api/api.ts b/site/src/api/api.ts index c917ba67ce283..fb0cac686b734 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -16,18 +16,28 @@ export const hardCodedCSRFCookie = (): string => { return csrfToken } -// defaultEntitlements has a default set of disabled functionality. -export const defaultEntitlements = (): TypesGen.Entitlements => { - const features: Partial = {} - for (const feature in TypesGen.FeatureNames) { - features[feature as TypesGen.FeatureName] = { +// withDefaultFeatures sets all unspecified features to not_entitled and disabled. +export const withDefaultFeatures = ( + fs: Partial, +): TypesGen.Entitlements["features"] => { + for (const k in TypesGen.FeatureNames) { + const feature = k as TypesGen.FeatureName + // Skip fields that are already filled. + if (fs[feature] !== undefined) { + continue + } + fs[feature] = { enabled: false, entitlement: "not_entitled", } } + return fs as TypesGen.Entitlements["features"] +} +// defaultEntitlements has a default set of disabled functionality. +export const defaultEntitlements = (): TypesGen.Entitlements => { return { - features: features as TypesGen.Entitlements["features"], + features: withDefaultFeatures({}), has_license: false, errors: [], warnings: [], diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 299c202ace986..665e6517dacde 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -1,4 +1,5 @@ // Code generated by 'make site/src/api/typesGenerated.ts'. DO NOT EDIT. + // From codersdk/apikey.go export interface APIKey { readonly id: string diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index ffcd3ea8b8b35..18bfbb0ccba5e 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -1,3 +1,4 @@ +import { withDefaultFeatures } from "./../api/api" import { FieldError } from "api/errors" import { everyOneGroup } from "util/groups" import * as Types from "../api/types" @@ -938,7 +939,7 @@ export const MockEntitlements: TypesGen.Entitlements = { errors: [], warnings: [], has_license: false, - features: {}, + features: withDefaultFeatures({}), experimental: false, trial: false, } @@ -949,7 +950,7 @@ export const MockEntitlementsWithWarnings: TypesGen.Entitlements = { has_license: true, experimental: false, trial: false, - features: { + features: withDefaultFeatures({ user_limit: { enabled: true, entitlement: "grace_period", @@ -964,7 +965,7 @@ export const MockEntitlementsWithWarnings: TypesGen.Entitlements = { enabled: true, entitlement: "entitled", }, - }, + }), } export const MockEntitlementsWithAuditLog: TypesGen.Entitlements = { @@ -973,12 +974,12 @@ export const MockEntitlementsWithAuditLog: TypesGen.Entitlements = { has_license: true, experimental: false, trial: false, - features: { + features: withDefaultFeatures({ audit_log: { enabled: true, entitlement: "entitled", }, - }, + }), } export const MockAuditLog: TypesGen.AuditLog = { diff --git a/site/src/xServices/entitlements/entitlementsXService.ts b/site/src/xServices/entitlements/entitlementsXService.ts index a1e8bb0d9b895..e7e9b78ddd3fc 100644 --- a/site/src/xServices/entitlements/entitlementsXService.ts +++ b/site/src/xServices/entitlements/entitlementsXService.ts @@ -1,3 +1,4 @@ +import { withDefaultFeatures } from "./../../api/api" import { MockEntitlementsWithWarnings } from "testHelpers/entities" import { assign, createMachine } from "xstate" import * as API from "../../api/api" @@ -22,7 +23,7 @@ export type EntitlementsEvent = const emptyEntitlements = { errors: [], warnings: [], - features: {}, + features: withDefaultFeatures({}), has_license: false, experimental: false, trial: false,