mirror of
https://github.com/coder/coder.git
synced 2025-07-13 21:36:50 +00:00
feat: add license expiration warning (#7264)
* wip: add expiration warning * Use GraceAt * show expiration warning for trial accounts * fix test * only show license banner for users with deployment permission --------- Co-authored-by: Marcin Tojek <marcin@coder.com>
This commit is contained in:
@ -55,6 +55,7 @@ func TestEntitlements(t *testing.T) {
|
||||
codersdk.FeatureAdvancedTemplateScheduling: 1,
|
||||
codersdk.FeatureWorkspaceProxy: 1,
|
||||
},
|
||||
GraceAt: time.Now().Add(59 * 24 * time.Hour),
|
||||
})
|
||||
res, err := client.Entitlements(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"crypto/ed25519"
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
@ -70,6 +71,23 @@ func Entitlements(
|
||||
// LicenseExpires we must be in grace period.
|
||||
entitlement = codersdk.EntitlementGracePeriod
|
||||
}
|
||||
|
||||
// Add warning if license is expiring soon
|
||||
daysToExpire := int(math.Ceil(claims.LicenseExpires.Sub(now).Hours() / 24))
|
||||
isTrial := entitlements.Trial
|
||||
showWarningDays := 30
|
||||
if isTrial {
|
||||
showWarningDays = 7
|
||||
}
|
||||
isExpiringSoon := daysToExpire > 0 && daysToExpire < showWarningDays
|
||||
if isExpiringSoon {
|
||||
day := "day"
|
||||
if daysToExpire > 1 {
|
||||
day = "days"
|
||||
}
|
||||
entitlements.Warnings = append(entitlements.Warnings, fmt.Sprintf("Your license expires in %d %s.", daysToExpire, day))
|
||||
}
|
||||
|
||||
for featureName, featureValue := range claims.Features {
|
||||
// Can this be negative?
|
||||
if featureValue <= 0 {
|
||||
|
@ -102,6 +102,123 @@ func TestEntitlements(t *testing.T) {
|
||||
fmt.Sprintf("%s is enabled but your license for this feature is expired.", codersdk.FeatureAuditLog.Humanize()),
|
||||
)
|
||||
})
|
||||
t.Run("Expiration warning", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
db := dbfake.New()
|
||||
db.InsertLicense(context.Background(), database.InsertLicenseParams{
|
||||
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
|
||||
Features: license.Features{
|
||||
codersdk.FeatureUserLimit: 100,
|
||||
codersdk.FeatureAuditLog: 1,
|
||||
},
|
||||
|
||||
GraceAt: time.Now().AddDate(0, 0, 2),
|
||||
ExpiresAt: time.Now().AddDate(0, 0, 5),
|
||||
}),
|
||||
Exp: time.Now().AddDate(0, 0, 5),
|
||||
})
|
||||
|
||||
entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, 1, coderdenttest.Keys, all)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.True(t, entitlements.HasLicense)
|
||||
require.False(t, entitlements.Trial)
|
||||
|
||||
require.Equal(t, codersdk.EntitlementEntitled, entitlements.Features[codersdk.FeatureAuditLog].Entitlement)
|
||||
require.Contains(
|
||||
t, entitlements.Warnings,
|
||||
"Your license expires in 2 days.",
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("Expiration warning for license expiring in 1 day", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
db := dbfake.New()
|
||||
db.InsertLicense(context.Background(), database.InsertLicenseParams{
|
||||
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
|
||||
Features: license.Features{
|
||||
codersdk.FeatureUserLimit: 100,
|
||||
codersdk.FeatureAuditLog: 1,
|
||||
},
|
||||
|
||||
GraceAt: time.Now().AddDate(0, 0, 1),
|
||||
ExpiresAt: time.Now().AddDate(0, 0, 5),
|
||||
}),
|
||||
Exp: time.Now().AddDate(0, 0, 5),
|
||||
})
|
||||
|
||||
entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, 1, coderdenttest.Keys, all)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.True(t, entitlements.HasLicense)
|
||||
require.False(t, entitlements.Trial)
|
||||
|
||||
require.Equal(t, codersdk.EntitlementEntitled, entitlements.Features[codersdk.FeatureAuditLog].Entitlement)
|
||||
require.Contains(
|
||||
t, entitlements.Warnings,
|
||||
"Your license expires in 1 day.",
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("Expiration warning for trials", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
db := dbfake.New()
|
||||
db.InsertLicense(context.Background(), database.InsertLicenseParams{
|
||||
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
|
||||
Features: license.Features{
|
||||
codersdk.FeatureUserLimit: 100,
|
||||
codersdk.FeatureAuditLog: 1,
|
||||
},
|
||||
|
||||
Trial: true,
|
||||
GraceAt: time.Now().AddDate(0, 0, 8),
|
||||
ExpiresAt: time.Now().AddDate(0, 0, 5),
|
||||
}),
|
||||
Exp: time.Now().AddDate(0, 0, 5),
|
||||
})
|
||||
|
||||
entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, 1, coderdenttest.Keys, all)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.True(t, entitlements.HasLicense)
|
||||
require.True(t, entitlements.Trial)
|
||||
|
||||
require.Equal(t, codersdk.EntitlementEntitled, entitlements.Features[codersdk.FeatureAuditLog].Entitlement)
|
||||
require.NotContains( // it should not contain a warning since it is a trial license
|
||||
t, entitlements.Warnings,
|
||||
"Your license expires in 8 days.",
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("Expiration warning for non trials", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
db := dbfake.New()
|
||||
db.InsertLicense(context.Background(), database.InsertLicenseParams{
|
||||
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
|
||||
Features: license.Features{
|
||||
codersdk.FeatureUserLimit: 100,
|
||||
codersdk.FeatureAuditLog: 1,
|
||||
},
|
||||
|
||||
GraceAt: time.Now().AddDate(0, 0, 30),
|
||||
ExpiresAt: time.Now().AddDate(0, 0, 5),
|
||||
}),
|
||||
Exp: time.Now().AddDate(0, 0, 5),
|
||||
})
|
||||
|
||||
entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, 1, coderdenttest.Keys, all)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.True(t, entitlements.HasLicense)
|
||||
require.False(t, entitlements.Trial)
|
||||
|
||||
require.Equal(t, codersdk.EntitlementEntitled, entitlements.Features[codersdk.FeatureAuditLog].Entitlement)
|
||||
require.NotContains( // it should not contain a warning since it is a trial license
|
||||
t, entitlements.Warnings,
|
||||
"Your license expires in 30 days.",
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("SingleLicenseNotEntitled", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
db := dbfake.New()
|
||||
@ -164,16 +281,18 @@ func TestEntitlements(t *testing.T) {
|
||||
Features: license.Features{
|
||||
codersdk.FeatureUserLimit: 10,
|
||||
},
|
||||
GraceAt: time.Now().Add(59 * 24 * time.Hour),
|
||||
}),
|
||||
Exp: time.Now().Add(time.Hour),
|
||||
Exp: time.Now().Add(60 * 24 * time.Hour),
|
||||
})
|
||||
db.InsertLicense(context.Background(), database.InsertLicenseParams{
|
||||
JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{
|
||||
Features: license.Features{
|
||||
codersdk.FeatureUserLimit: 1,
|
||||
},
|
||||
GraceAt: time.Now().Add(59 * 24 * time.Hour),
|
||||
}),
|
||||
Exp: time.Now().Add(time.Hour),
|
||||
Exp: time.Now().Add(60 * 24 * time.Hour),
|
||||
})
|
||||
entitlements, err := license.Entitlements(context.Background(), db, slog.Logger{}, 1, 1, coderdenttest.Keys, empty)
|
||||
require.NoError(t, err)
|
||||
|
Reference in New Issue
Block a user