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