Skip to content

feat: implement premium vs enterprise licenses #13907

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Jul 24, 2024
Merged

Conversation

Emyrk
Copy link
Member

@Emyrk Emyrk commented Jul 16, 2024

This deprecates the AllFeatures boolean for a FeatureSet enum.

Refactored Entitlements to be more unit testable. Added more unit tests.

Closes #13934

@Emyrk Emyrk force-pushed the stevenmasley/premium_license branch from 0036916 to 26f7b66 Compare July 16, 2024 19:00
@Emyrk Emyrk marked this pull request as ready for review July 16, 2024 20:00
@Emyrk Emyrk requested a review from kylecarbs July 17, 2024 15:12
Comment on lines 179 to 252
// CompareFeatures compares two features and returns an integer representing
// if the first feature is greater than, equal to, or less than the second feature.
// "Greater than" means the first feature has more functionality than the
// second feature. It is assumed the features are for the same FeatureName.
//
// A feature is considered greater than another feature if:
// - Graceful & capable > Entitled & not capable
// - The entitlement is greater
// - The limit is greater
// - Enabled is greater than disabled
// - The actual is greater
func CompareFeatures(a, b Feature) int {
if !a.Capable() || !b.Capable() {
// If either is incapable, then it is possible a grace period
// feature can be "greater" than an entitled.
// If either is "NotEntitled" then we can defer to a strict entitlement
// check.
if entitlementWeight(a.Entitlement) >= 0 && entitlementWeight(b.Entitlement) >= 0 {
if a.Capable() && !b.Capable() {
return 1
}
if b.Capable() && !a.Capable() {
return -1
}
}
}

entitlement := CompareEntitlements(a.Entitlement, b.Entitlement)
if entitlement > 0 {
return 1
}
if entitlement < 0 {
return -1
}

// If the entitlement is the same, then we can compare the limits.
if a.Limit == nil && b.Limit != nil {
return -1
}
if a.Limit != nil && b.Limit == nil {
return 1
}
if a.Limit != nil && b.Limit != nil {
difference := *a.Limit - *b.Limit
if *a.Limit-*b.Limit != 0 {
return int(difference)
}
}

// Enabled is better than disabled.
if a.Enabled && !b.Enabled {
return 1
}
if !a.Enabled && b.Enabled {
return -1
}

// Higher actual is better
if a.Actual == nil && b.Actual != nil {
return -1
}
if a.Actual != nil && b.Actual == nil {
return 1
}
if a.Actual != nil && b.Actual != nil {
difference := *a.Actual - *b.Actual
if *a.Actual-*b.Actual != 0 {
return int(difference)
}
}

return 0
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the real meat and potatoes.

This function compares 2 features from 2 different licenses, and decides which one is to be kept. To change the behavior on how multiple licenses are handled, this is where to do it.

@@ -379,3 +382,164 @@ func TestExternalAuthYAMLConfig(t *testing.T) {
output := strings.Replace(out.String(), "value:", "externalAuthProviders:", 1)
require.Equal(t, inputYAML, output, "re-marshaled is the same as input")
}

func TestFeatureComparison(t *testing.T) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The refactor mainly leads to this unit test. Much easier to write than the previous tests that required starting a coderd to test license logic.

@@ -33,6 +33,21 @@ const (
EntitlementNotEntitled Entitlement = "not_entitled"
)

// entitlementWeight converts the enum types to a numerical value for easier
// comparisons. Easier than sets of if statements.
func entitlementWeight(e Entitlement) int {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be cleaner to add this as a func on Entitlement type itself. e.Weight()


func (set FeatureSet) Features() []FeatureName {
switch FeatureSet(strings.ToLower(string(set))) {
case FeatureSetEnterprise:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A while ago Ammar did work to make it so we don't have to list new features in a bunch of places.

Instead of this, could we do the inverse where Premium simply detracts from the list instead? Seems easier to mentally model.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand correctly, you are saying we have 1 list, All() (it's FeatureNames() or something atm).

And we define enterprise = All() - [multi-org], premium = All(). Rather than this explicit listing?

If we define it that way, it feels more likely to accidentally include a feature in "enterprise", and then we'd have to revoke it later. This current method might be a bit of a nuisance to deal with, but it errors on the side of restrictive.

Now our unit tests do not use feature sets, they manually define features. So this might reveal something lacking in our unit tests that only running a real server will pickup 🤔.

I'm still a bit split on the ideal way.

Copy link
Member Author

@Emyrk Emyrk Jul 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I switch it 👍. We should probably add some tests to assert we don't leak features into the wrong set, but not in this PR.

7436159 + 8e11ff9

@Emyrk Emyrk force-pushed the stevenmasley/premium_license branch from 8e11ff9 to 1c4a305 Compare July 24, 2024 16:22
@Emyrk Emyrk merged commit 15fda23 into main Jul 24, 2024
32 checks passed
@Emyrk Emyrk deleted the stevenmasley/premium_license branch July 24, 2024 17:08
@github-actions github-actions bot locked and limited conversation to collaborators Jul 24, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Coder license to support "enterprise" and "premium" feature sets
2 participants