Skip to content

Commit f2c0983

Browse files
committed
Add banner for too many configurations
1 parent 4e4f0ba commit f2c0983

File tree

6 files changed

+115
-30
lines changed

6 files changed

+115
-30
lines changed

codersdk/features.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const (
2222
FeatureWorkspaceQuota = "workspace_quota"
2323
FeatureTemplateRBAC = "template_rbac"
2424
FeatureHighAvailability = "high_availability"
25+
FeatureMultipleGitAuth = "multiple_git_auth"
2526
)
2627

2728
var FeatureNames = []string{
@@ -32,6 +33,7 @@ var FeatureNames = []string{
3233
FeatureWorkspaceQuota,
3334
FeatureTemplateRBAC,
3435
FeatureHighAvailability,
36+
FeatureMultipleGitAuth,
3537
}
3638

3739
type Feature struct {

enterprise/cli/features_test.go

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -57,22 +57,10 @@ func TestFeaturesList(t *testing.T) {
5757
var entitlements codersdk.Entitlements
5858
err := json.Unmarshal(buf.Bytes(), &entitlements)
5959
require.NoError(t, err, "unmarshal JSON output")
60-
assert.Len(t, entitlements.Features, 7)
6160
assert.Empty(t, entitlements.Warnings)
62-
assert.Equal(t, codersdk.EntitlementNotEntitled,
63-
entitlements.Features[codersdk.FeatureUserLimit].Entitlement)
64-
assert.Equal(t, codersdk.EntitlementNotEntitled,
65-
entitlements.Features[codersdk.FeatureAuditLog].Entitlement)
66-
assert.Equal(t, codersdk.EntitlementNotEntitled,
67-
entitlements.Features[codersdk.FeatureBrowserOnly].Entitlement)
68-
assert.Equal(t, codersdk.EntitlementNotEntitled,
69-
entitlements.Features[codersdk.FeatureWorkspaceQuota].Entitlement)
70-
assert.Equal(t, codersdk.EntitlementNotEntitled,
71-
entitlements.Features[codersdk.FeatureTemplateRBAC].Entitlement)
72-
assert.Equal(t, codersdk.EntitlementNotEntitled,
73-
entitlements.Features[codersdk.FeatureSCIM].Entitlement)
74-
assert.Equal(t, codersdk.EntitlementNotEntitled,
75-
entitlements.Features[codersdk.FeatureHighAvailability].Entitlement)
61+
for _, featureName := range codersdk.FeatureNames {
62+
assert.Equal(t, codersdk.EntitlementNotEntitled, entitlements.Features[featureName].Entitlement)
63+
}
7664
assert.False(t, entitlements.HasLicense)
7765
assert.False(t, entitlements.Experimental)
7866
})

enterprise/coderd/coderd.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,12 +211,13 @@ func (api *API) updateEntitlements(ctx context.Context) error {
211211
api.entitlementsMu.Lock()
212212
defer api.entitlementsMu.Unlock()
213213

214-
entitlements, err := license.Entitlements(ctx, api.Database, api.Logger, len(api.replicaManager.All()), api.Keys, map[string]bool{
214+
entitlements, err := license.Entitlements(ctx, api.Database, api.Logger, len(api.replicaManager.All()), len(api.GitAuthConfigs), api.Keys, map[string]bool{
215215
codersdk.FeatureAuditLog: api.AuditLogging,
216216
codersdk.FeatureBrowserOnly: api.BrowserOnly,
217217
codersdk.FeatureSCIM: len(api.SCIMAPIKey) != 0,
218218
codersdk.FeatureWorkspaceQuota: api.UserWorkspaceQuota != 0,
219219
codersdk.FeatureHighAvailability: api.DERPServerRelayAddress != "",
220+
codersdk.FeatureMultipleGitAuth: len(api.GitAuthConfigs) > 1,
220221
codersdk.FeatureTemplateRBAC: api.RBAC,
221222
})
222223
if err != nil {

enterprise/coderd/coderdenttest/coderdenttest.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ type LicenseOptions struct {
113113
WorkspaceQuota bool
114114
TemplateRBAC bool
115115
HighAvailability bool
116+
MultipleGitAuth bool
116117
}
117118

118119
// AddLicense generates a new license with the options provided and inserts it.
@@ -158,6 +159,11 @@ func GenerateLicense(t *testing.T, options LicenseOptions) string {
158159
rbacEnabled = 1
159160
}
160161

162+
multipleGitAuth := int64(0)
163+
if options.MultipleGitAuth {
164+
multipleGitAuth = 1
165+
}
166+
161167
c := &license.Claims{
162168
RegisteredClaims: jwt.RegisteredClaims{
163169
Issuer: "test@testing.test",
@@ -179,6 +185,7 @@ func GenerateLicense(t *testing.T, options LicenseOptions) string {
179185
WorkspaceQuota: workspaceQuota,
180186
HighAvailability: highAvailability,
181187
TemplateRBAC: rbacEnabled,
188+
MultipleGitAuth: multipleGitAuth,
182189
},
183190
}
184191
tok := jwt.NewWithClaims(jwt.SigningMethodEdDSA, c)

enterprise/coderd/license/license.go

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ func Entitlements(
2222
db database.Store,
2323
logger slog.Logger,
2424
replicaCount int,
25+
gitAuthCount int,
2526
keys map[string]ed25519.PublicKey,
2627
enablements map[string]bool,
2728
) (codersdk.Entitlements, error) {
@@ -116,6 +117,12 @@ func Entitlements(
116117
Enabled: enablements[codersdk.FeatureTemplateRBAC],
117118
}
118119
}
120+
if claims.Features.MultipleGitAuth > 0 {
121+
entitlements.Features[codersdk.FeatureMultipleGitAuth] = codersdk.Feature{
122+
Entitlement: entitlement,
123+
Enabled: true,
124+
}
125+
}
119126
if claims.AllFeatures {
120127
allFeatures = true
121128
}
@@ -150,6 +157,10 @@ func Entitlements(
150157
if featureName == codersdk.FeatureHighAvailability {
151158
continue
152159
}
160+
// Multiple Git auth has it's own warnings based on the number configured!
161+
if featureName == codersdk.FeatureMultipleGitAuth {
162+
continue
163+
}
153164
feature := entitlements.Features[featureName]
154165
if !feature.Enabled {
155166
continue
@@ -173,10 +184,10 @@ func Entitlements(
173184
switch feature.Entitlement {
174185
case codersdk.EntitlementNotEntitled:
175186
if entitlements.HasLicense {
176-
entitlements.Errors = append(entitlements.Warnings,
187+
entitlements.Errors = append(entitlements.Errors,
177188
"You have multiple replicas but your license is not entitled to high availability. You will be unable to connect to workspaces.")
178189
} else {
179-
entitlements.Errors = append(entitlements.Warnings,
190+
entitlements.Errors = append(entitlements.Errors,
180191
"You have multiple replicas but high availability is an Enterprise feature. You will be unable to connect to workspaces.")
181192
}
182193
case codersdk.EntitlementGracePeriod:
@@ -185,6 +196,27 @@ func Entitlements(
185196
}
186197
}
187198

199+
if gitAuthCount > 1 {
200+
feature := entitlements.Features[codersdk.FeatureMultipleGitAuth]
201+
202+
switch feature.Entitlement {
203+
case codersdk.EntitlementNotEntitled:
204+
if entitlements.HasLicense {
205+
entitlements.Errors = append(entitlements.Errors,
206+
"You have multiple Git authorizations configured but your license is limited at one.",
207+
)
208+
} else {
209+
entitlements.Errors = append(entitlements.Errors,
210+
"You have multiple Git authorizations configured but this is an Enterprise feature. Reduce to one.",
211+
)
212+
}
213+
case codersdk.EntitlementGracePeriod:
214+
entitlements.Warnings = append(entitlements.Warnings,
215+
"You have multiple Git authorizations configured but your license is expired. Reduce to one.",
216+
)
217+
}
218+
}
219+
188220
for _, featureName := range codersdk.FeatureNames {
189221
feature := entitlements.Features[featureName]
190222
if feature.Entitlement == codersdk.EntitlementNotEntitled {
@@ -219,6 +251,7 @@ type Features struct {
219251
WorkspaceQuota int64 `json:"workspace_quota"`
220252
TemplateRBAC int64 `json:"template_rbac"`
221253
HighAvailability int64 `json:"high_availability"`
254+
MultipleGitAuth int64 `json:"multiple_git_auth"`
222255
}
223256

224257
type Claims struct {

enterprise/coderd/license/license_test.go

Lines changed: 66 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func TestEntitlements(t *testing.T) {
3131
t.Run("Defaults", func(t *testing.T) {
3232
t.Parallel()
3333
db := databasefake.New()
34-
entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, coderdenttest.Keys, all)
34+
entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, 1, coderdenttest.Keys, all)
3535
require.NoError(t, err)
3636
require.False(t, entitlements.HasLicense)
3737
require.False(t, entitlements.Trial)
@@ -47,7 +47,7 @@ func TestEntitlements(t *testing.T) {
4747
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{}),
4848
Exp: time.Now().Add(time.Hour),
4949
})
50-
entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, coderdenttest.Keys, map[string]bool{})
50+
entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, 1, coderdenttest.Keys, map[string]bool{})
5151
require.NoError(t, err)
5252
require.True(t, entitlements.HasLicense)
5353
require.False(t, entitlements.Trial)
@@ -71,7 +71,7 @@ func TestEntitlements(t *testing.T) {
7171
}),
7272
Exp: time.Now().Add(time.Hour),
7373
})
74-
entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, coderdenttest.Keys, map[string]bool{})
74+
entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, 1, coderdenttest.Keys, map[string]bool{})
7575
require.NoError(t, err)
7676
require.True(t, entitlements.HasLicense)
7777
require.False(t, entitlements.Trial)
@@ -96,7 +96,7 @@ func TestEntitlements(t *testing.T) {
9696
}),
9797
Exp: time.Now().Add(time.Hour),
9898
})
99-
entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, coderdenttest.Keys, all)
99+
entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, 1, coderdenttest.Keys, all)
100100
require.NoError(t, err)
101101
require.True(t, entitlements.HasLicense)
102102
require.False(t, entitlements.Trial)
@@ -107,6 +107,9 @@ func TestEntitlements(t *testing.T) {
107107
if featureName == codersdk.FeatureHighAvailability {
108108
continue
109109
}
110+
if featureName == codersdk.FeatureMultipleGitAuth {
111+
continue
112+
}
110113
niceName := strings.Title(strings.ReplaceAll(featureName, "_", " "))
111114
require.Equal(t, codersdk.EntitlementGracePeriod, entitlements.Features[featureName].Entitlement)
112115
require.Contains(t, entitlements.Warnings, fmt.Sprintf("%s is enabled but your license for this feature is expired.", niceName))
@@ -119,7 +122,7 @@ func TestEntitlements(t *testing.T) {
119122
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{}),
120123
Exp: time.Now().Add(time.Hour),
121124
})
122-
entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, coderdenttest.Keys, all)
125+
entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, 1, coderdenttest.Keys, all)
123126
require.NoError(t, err)
124127
require.True(t, entitlements.HasLicense)
125128
require.False(t, entitlements.Trial)
@@ -130,6 +133,9 @@ func TestEntitlements(t *testing.T) {
130133
if featureName == codersdk.FeatureHighAvailability {
131134
continue
132135
}
136+
if featureName == codersdk.FeatureMultipleGitAuth {
137+
continue
138+
}
133139
niceName := strings.Title(strings.ReplaceAll(featureName, "_", " "))
134140
// Ensures features that are not entitled are properly disabled.
135141
require.False(t, entitlements.Features[featureName].Enabled)
@@ -152,7 +158,7 @@ func TestEntitlements(t *testing.T) {
152158
}),
153159
Exp: time.Now().Add(time.Hour),
154160
})
155-
entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, coderdenttest.Keys, map[string]bool{})
161+
entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, 1, coderdenttest.Keys, map[string]bool{})
156162
require.NoError(t, err)
157163
require.True(t, entitlements.HasLicense)
158164
require.Contains(t, entitlements.Warnings, "Your deployment has 2 active users but is only licensed for 1.")
@@ -174,7 +180,7 @@ func TestEntitlements(t *testing.T) {
174180
}),
175181
Exp: time.Now().Add(time.Hour),
176182
})
177-
entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, coderdenttest.Keys, map[string]bool{})
183+
entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, 1, coderdenttest.Keys, map[string]bool{})
178184
require.NoError(t, err)
179185
require.True(t, entitlements.HasLicense)
180186
require.Empty(t, entitlements.Warnings)
@@ -197,7 +203,7 @@ func TestEntitlements(t *testing.T) {
197203
}),
198204
})
199205

200-
entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, coderdenttest.Keys, map[string]bool{})
206+
entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, 1, coderdenttest.Keys, map[string]bool{})
201207
require.NoError(t, err)
202208
require.True(t, entitlements.HasLicense)
203209
require.False(t, entitlements.Trial)
@@ -212,7 +218,7 @@ func TestEntitlements(t *testing.T) {
212218
AllFeatures: true,
213219
}),
214220
})
215-
entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, coderdenttest.Keys, all)
221+
entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, 1, coderdenttest.Keys, all)
216222
require.NoError(t, err)
217223
require.True(t, entitlements.HasLicense)
218224
require.False(t, entitlements.Trial)
@@ -228,7 +234,7 @@ func TestEntitlements(t *testing.T) {
228234
t.Run("MultipleReplicasNoLicense", func(t *testing.T) {
229235
t.Parallel()
230236
db := databasefake.New()
231-
entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 2, coderdenttest.Keys, all)
237+
entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 2, 1, coderdenttest.Keys, all)
232238
require.NoError(t, err)
233239
require.False(t, entitlements.HasLicense)
234240
require.Len(t, entitlements.Errors, 1)
@@ -244,7 +250,7 @@ func TestEntitlements(t *testing.T) {
244250
AuditLog: true,
245251
}),
246252
})
247-
entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 2, coderdenttest.Keys, map[string]bool{
253+
entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 2, 1, coderdenttest.Keys, map[string]bool{
248254
codersdk.FeatureHighAvailability: true,
249255
})
250256
require.NoError(t, err)
@@ -264,12 +270,60 @@ func TestEntitlements(t *testing.T) {
264270
}),
265271
Exp: time.Now().Add(time.Hour),
266272
})
267-
entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 2, coderdenttest.Keys, map[string]bool{
273+
entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 2, 1, coderdenttest.Keys, map[string]bool{
268274
codersdk.FeatureHighAvailability: true,
269275
})
270276
require.NoError(t, err)
271277
require.True(t, entitlements.HasLicense)
272278
require.Len(t, entitlements.Warnings, 1)
273279
require.Equal(t, "You have multiple replicas but your license for high availability is expired. Reduce to one replica or workspace connections will stop working.", entitlements.Warnings[0])
274280
})
281+
282+
t.Run("MultipleGitAuthNoLicense", func(t *testing.T) {
283+
t.Parallel()
284+
db := databasefake.New()
285+
entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, 2, coderdenttest.Keys, all)
286+
require.NoError(t, err)
287+
require.False(t, entitlements.HasLicense)
288+
require.Len(t, entitlements.Errors, 1)
289+
require.Equal(t, "You have multiple Git authorizations configured but this is an Enterprise feature. Reduce to one.", entitlements.Errors[0])
290+
})
291+
292+
t.Run("MultipleGitAuthNotEntitled", func(t *testing.T) {
293+
t.Parallel()
294+
db := databasefake.New()
295+
db.InsertLicense(context.Background(), database.InsertLicenseParams{
296+
Exp: time.Now().Add(time.Hour),
297+
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
298+
AuditLog: true,
299+
}),
300+
})
301+
entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, 2, coderdenttest.Keys, map[string]bool{
302+
codersdk.FeatureMultipleGitAuth: true,
303+
})
304+
require.NoError(t, err)
305+
require.True(t, entitlements.HasLicense)
306+
require.Len(t, entitlements.Errors, 1)
307+
require.Equal(t, "You have multiple Git authorizations configured but your license is limited at one.", entitlements.Errors[0])
308+
})
309+
310+
t.Run("MultipleGitAuthGrace", func(t *testing.T) {
311+
t.Parallel()
312+
db := databasefake.New()
313+
db.InsertLicense(context.Background(), database.InsertLicenseParams{
314+
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
315+
MultipleGitAuth: true,
316+
GraceAt: time.Now().Add(-time.Hour),
317+
ExpiresAt: time.Now().Add(time.Hour),
318+
}),
319+
Exp: time.Now().Add(time.Hour),
320+
})
321+
entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, 2, coderdenttest.Keys, map[string]bool{
322+
codersdk.FeatureMultipleGitAuth: true,
323+
})
324+
require.NoError(t, err)
325+
require.True(t, entitlements.HasLicense)
326+
require.Len(t, entitlements.Warnings, 1)
327+
require.Equal(t, "You have multiple Git authorizations configured but your license is expired. Reduce to one.", entitlements.Warnings[0])
328+
})
275329
}

0 commit comments

Comments
 (0)