Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit15fda23

Browse files
authored
feat: implement premium vs enterprise licenses (#13907)
* feat: implement premium vs enterprise licensesImplement different sets of licensed features.
1 parent0d9615b commit15fda23

File tree

8 files changed

+971
-150
lines changed

8 files changed

+971
-150
lines changed

‎codersdk/deployment.go

Lines changed: 172 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"os"
1010
"path/filepath"
1111
"reflect"
12+
"slices"
1213
"strconv"
1314
"strings"
1415
"time"
@@ -34,6 +35,21 @@ const (
3435
EntitlementNotEntitledEntitlement="not_entitled"
3536
)
3637

38+
// Weight converts the enum types to a numerical value for easier
39+
// comparisons. Easier than sets of if statements.
40+
func (eEntitlement)Weight()int {
41+
switche {
42+
caseEntitlementEntitled:
43+
return2
44+
caseEntitlementGracePeriod:
45+
return1
46+
caseEntitlementNotEntitled:
47+
return-1
48+
default:
49+
return-2
50+
}
51+
}
52+
3753
// FeatureName represents the internal name of a feature.
3854
// To add a new feature, add it to this set of enums as well as the FeatureNames
3955
// array below.
@@ -95,8 +111,11 @@ func (n FeatureName) Humanize() string {
95111
}
96112

97113
// AlwaysEnable returns if the feature is always enabled if entitled.
98-
// Warning: We don't know if we need this functionality.
99-
// This method may disappear at any time.
114+
// This is required because some features are only enabled if they are entitled
115+
// and not required.
116+
// E.g: "multiple-organizations" is disabled by default in AGPL and enterprise
117+
// deployments. This feature should only be enabled for premium deployments
118+
// when it is entitled.
100119
func (nFeatureName)AlwaysEnable()bool {
101120
returnmap[FeatureName]bool{
102121
FeatureMultipleExternalAuth:true,
@@ -105,16 +124,144 @@ func (n FeatureName) AlwaysEnable() bool {
105124
FeatureWorkspaceBatchActions:true,
106125
FeatureHighAvailability:true,
107126
FeatureCustomRoles:true,
127+
FeatureMultipleOrganizations:true,
108128
}[n]
109129
}
110130

131+
// FeatureSet represents a grouping of features. Rather than manually
132+
// assigning features al-la-carte when making a license, a set can be specified.
133+
// Sets are dynamic in the sense a feature can be added to a set, granting the
134+
// feature to existing licenses out in the wild.
135+
// If features were granted al-la-carte, we would need to reissue the existing
136+
// old licenses to include the new feature.
137+
typeFeatureSetstring
138+
139+
const (
140+
FeatureSetNoneFeatureSet=""
141+
FeatureSetEnterpriseFeatureSet="enterprise"
142+
FeatureSetPremiumFeatureSet="premium"
143+
)
144+
145+
func (setFeatureSet)Features() []FeatureName {
146+
switchFeatureSet(strings.ToLower(string(set))) {
147+
caseFeatureSetEnterprise:
148+
// Enterprise is the set 'AllFeatures' minus some select features.
149+
150+
// Copy the list of all features
151+
enterpriseFeatures:=make([]FeatureName,len(FeatureNames))
152+
copy(enterpriseFeatures,FeatureNames)
153+
// Remove the selection
154+
enterpriseFeatures=slices.DeleteFunc(enterpriseFeatures,func(fFeatureName)bool {
155+
switchf {
156+
// Add all features that should be excluded in the Enterprise feature set.
157+
caseFeatureMultipleOrganizations:
158+
returntrue
159+
default:
160+
returnfalse
161+
}
162+
})
163+
164+
returnenterpriseFeatures
165+
caseFeatureSetPremium:
166+
premiumFeatures:=make([]FeatureName,len(FeatureNames))
167+
copy(premiumFeatures,FeatureNames)
168+
// FeatureSetPremium is just all features.
169+
returnpremiumFeatures
170+
}
171+
// By default, return an empty set.
172+
return []FeatureName{}
173+
}
174+
111175
typeFeaturestruct {
112176
EntitlementEntitlement`json:"entitlement"`
113177
Enabledbool`json:"enabled"`
114178
Limit*int64`json:"limit,omitempty"`
115179
Actual*int64`json:"actual,omitempty"`
116180
}
117181

182+
// Compare compares two features and returns an integer representing
183+
// if the first feature (f) is greater than, equal to, or less than the second
184+
// feature (b). "Greater than" means the first feature has more functionality
185+
// than the second feature. It is assumed the features are for the same FeatureName.
186+
//
187+
// A feature is considered greater than another feature if:
188+
// 1. Graceful & capable > Entitled & not capable
189+
// 2. The entitlement is greater
190+
// 3. The limit is greater
191+
// 4. Enabled is greater than disabled
192+
// 5. The actual is greater
193+
func (fFeature)Compare(bFeature)int {
194+
if!f.Capable()||!b.Capable() {
195+
// If either is incapable, then it is possible a grace period
196+
// feature can be "greater" than an entitled.
197+
// If either is "NotEntitled" then we can defer to a strict entitlement
198+
// check.
199+
iff.Entitlement.Weight()>=0&&b.Entitlement.Weight()>=0 {
200+
iff.Capable()&&!b.Capable() {
201+
return1
202+
}
203+
ifb.Capable()&&!f.Capable() {
204+
return-1
205+
}
206+
}
207+
}
208+
209+
// Strict entitlement check. Higher is better
210+
entitlementDifference:=f.Entitlement.Weight()-b.Entitlement.Weight()
211+
ifentitlementDifference!=0 {
212+
returnentitlementDifference
213+
}
214+
215+
// If the entitlement is the same, then we can compare the limits.
216+
iff.Limit==nil&&b.Limit!=nil {
217+
return-1
218+
}
219+
iff.Limit!=nil&&b.Limit==nil {
220+
return1
221+
}
222+
iff.Limit!=nil&&b.Limit!=nil {
223+
difference:=*f.Limit-*b.Limit
224+
ifdifference!=0 {
225+
returnint(difference)
226+
}
227+
}
228+
229+
// Enabled is better than disabled.
230+
iff.Enabled&&!b.Enabled {
231+
return1
232+
}
233+
if!f.Enabled&&b.Enabled {
234+
return-1
235+
}
236+
237+
// Higher actual is better
238+
iff.Actual==nil&&b.Actual!=nil {
239+
return-1
240+
}
241+
iff.Actual!=nil&&b.Actual==nil {
242+
return1
243+
}
244+
iff.Actual!=nil&&b.Actual!=nil {
245+
difference:=*f.Actual-*b.Actual
246+
ifdifference!=0 {
247+
returnint(difference)
248+
}
249+
}
250+
251+
return0
252+
}
253+
254+
// Capable is a helper function that returns if a given feature has a limit
255+
// that is greater than or equal to the actual.
256+
// If this condition is not true, then the feature is not capable of being used
257+
// since the limit is not high enough.
258+
func (fFeature)Capable()bool {
259+
iff.Limit!=nil&&f.Actual!=nil {
260+
return*f.Limit>=*f.Actual
261+
}
262+
returntrue
263+
}
264+
118265
typeEntitlementsstruct {
119266
Featuresmap[FeatureName]Feature`json:"features"`
120267
Warnings []string`json:"warnings"`
@@ -125,6 +272,29 @@ type Entitlements struct {
125272
RefreshedAt time.Time`json:"refreshed_at" format:"date-time"`
126273
}
127274

275+
// AddFeature will add the feature to the entitlements iff it expands
276+
// the set of features granted by the entitlements. If it does not, it will
277+
// be ignored and the existing feature with the same name will remain.
278+
//
279+
// All features should be added as atomic items, and not merged in any way.
280+
// Merging entitlements could lead to unexpected behavior, like a larger user
281+
// limit in grace period merging with a smaller one in an "entitled" state. This
282+
// could lead to the larger limit being extended as "entitled", which is not correct.
283+
func (e*Entitlements)AddFeature(nameFeatureName,addFeature) {
284+
existing,ok:=e.Features[name]
285+
if!ok {
286+
e.Features[name]=add
287+
return
288+
}
289+
290+
// Compare the features, keep the one that is "better"
291+
comparison:=add.Compare(existing)
292+
ifcomparison>0 {
293+
e.Features[name]=add
294+
return
295+
}
296+
}
297+
128298
func (c*Client)Entitlements(ctx context.Context) (Entitlements,error) {
129299
res,err:=c.Request(ctx,http.MethodGet,"/api/v2/entitlements",nil)
130300
iferr!=nil {

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp