@@ -33,6 +33,23 @@ const (
33
33
EntitlementNotEntitled Entitlement = "not_entitled"
34
34
)
35
35
36
+ func CompareEntitlements (a , b Entitlement ) int {
37
+ return entitlementWeight (a ) - entitlementWeight (b )
38
+ }
39
+
40
+ func entitlementWeight (e Entitlement ) int {
41
+ switch e {
42
+ case EntitlementEntitled :
43
+ return 2
44
+ case EntitlementGracePeriod :
45
+ return 1
46
+ case EntitlementNotEntitled :
47
+ return 0
48
+ default :
49
+ return - 1
50
+ }
51
+ }
52
+
36
53
// FeatureName represents the internal name of a feature.
37
54
// To add a new feature, add it to this set of enums as well as the FeatureNames
38
55
// array below.
@@ -107,6 +124,51 @@ func (n FeatureName) AlwaysEnable() bool {
107
124
}[n ]
108
125
}
109
126
127
+ // FeatureSet represents a grouping of features. This is easier
128
+ // than manually assigning features al-la-carte when making a license.
129
+ // These sets are dynamic in the sense a feature can be added to an existing
130
+ // set to grant an additional feature to an existing license.
131
+ // If features were granted al-la-carte, we would need to reissue the license
132
+ // to include the new feature.
133
+ type FeatureSet string
134
+
135
+ const (
136
+ FeatureSetNone FeatureSet = ""
137
+ FeatureSetEnterprise FeatureSet = "enterprise"
138
+ FeatureSetPremium FeatureSet = "premium"
139
+ )
140
+
141
+ func (set FeatureSet ) Features () []FeatureName {
142
+ switch FeatureSet (strings .ToLower (string (set ))) {
143
+ case FeatureSetEnterprise :
144
+ // List all features that should be included in the Enterprise feature set.
145
+ return []FeatureName {
146
+ FeatureUserLimit ,
147
+ FeatureAuditLog ,
148
+ FeatureBrowserOnly ,
149
+ FeatureSCIM ,
150
+ FeatureTemplateRBAC ,
151
+ FeatureHighAvailability ,
152
+ FeatureMultipleExternalAuth ,
153
+ FeatureExternalProvisionerDaemons ,
154
+ FeatureAppearance ,
155
+ FeatureAdvancedTemplateScheduling ,
156
+ FeatureWorkspaceProxy ,
157
+ FeatureUserRoleManagement ,
158
+ FeatureExternalTokenEncryption ,
159
+ FeatureWorkspaceBatchActions ,
160
+ FeatureAccessControl ,
161
+ FeatureControlSharedPorts ,
162
+ FeatureCustomRoles ,
163
+ }
164
+ case FeatureSetPremium :
165
+ // FeatureSetPremium is a superset of Enterprise
166
+ return append (FeatureSetEnterprise .Features ())
167
+ }
168
+ // By default, return an empty set.
169
+ return []FeatureName {}
170
+ }
171
+
110
172
type Feature struct {
111
173
Entitlement Entitlement `json:"entitlement"`
112
174
Enabled bool `json:"enabled"`
@@ -124,6 +186,56 @@ type Entitlements struct {
124
186
RefreshedAt time.Time `json:"refreshed_at" format:"date-time"`
125
187
}
126
188
189
+ // AddFeature will add the feature to the entitlements iff it expands
190
+ // the set of features granted by the entitlements. If it does not, it will
191
+ // be ignored and the existing feature with the same name will remain.
192
+ //
193
+ // All features should be added as atomic items, and not merged in any way.
194
+ // Merging entitlements could lead to unexpected behavior, like a larger user
195
+ // limit in grace period merging with a smaller one in a grace period. This could
196
+ // lead to the larger limit being extended as "entitled", which is not correct.
197
+ func (e * Entitlements ) AddFeature (name FeatureName , add Feature ) {
198
+ existing , ok := e .Features [name ]
199
+ if ! ok {
200
+ e .Features [name ] = add
201
+ return
202
+ }
203
+
204
+ comparison := CompareEntitlements (add .Entitlement , existing .Entitlement )
205
+ // If the new entitlement is greater than the existing entitlement, replace it.
206
+ // The edge case is if the previous entitlement is in a grace period with a
207
+ // higher value.
208
+ // TODO: Address the edge case.
209
+ if comparison > 0 {
210
+ e .Features [name ] = add
211
+ return
212
+ }
213
+
214
+ // If they have the same entitlement, then we can compare the limits.
215
+ if comparison == 0 {
216
+ if add .Limit != nil {
217
+ if existing .Limit == nil || * add .Limit > * existing .Limit {
218
+ e .Features [name ] = add
219
+ return
220
+ }
221
+ }
222
+
223
+ // Enabled is better than disabled.
224
+ if add .Enabled && ! existing .Enabled {
225
+ e .Features [name ] = add
226
+ return
227
+ }
228
+
229
+ // If the actual value is greater than the existing actual value, replace it.
230
+ if add .Actual != nil {
231
+ if existing .Actual == nil || * add .Actual > * existing .Actual {
232
+ e .Features [name ] = add
233
+ return
234
+ }
235
+ }
236
+ }
237
+ }
238
+
127
239
func (c * Client ) Entitlements (ctx context.Context ) (Entitlements , error ) {
128
240
res , err := c .Request (ctx , http .MethodGet , "/api/v2/entitlements" , nil )
129
241
if err != nil {
0 commit comments