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