Skip to content
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

[DEVEX-36] rpk: change expiry check for free_trial #23951

Merged
merged 3 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ use_repo(
"com_github_hashicorp_go_multierror",
"com_github_kballard_go_shellquote",
"com_github_kr_pretty",
"com_github_kr_text",
"com_github_lestrrat_go_jwx",
"com_github_linkedin_goavro_v2",
"com_github_lorenzosaino_go_sysctl",
Expand Down
1 change: 1 addition & 0 deletions src/go/rpk/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ require (
github.com/hamba/avro/v2 v2.25.2
github.com/hashicorp/go-multierror v1.1.1
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/kr/text v0.2.0
github.com/lestrrat-go/jwx v1.2.30
github.com/linkedin/goavro/v2 v2.13.0
github.com/lorenzosaino/go-sysctl v0.3.1
Expand Down
1 change: 1 addition & 0 deletions src/go/rpk/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
Expand Down
2 changes: 2 additions & 0 deletions src/go/rpk/pkg/adminapi/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ go_library(
"//src/go/rpk/pkg/config",
"//src/go/rpk/pkg/oauth",
"//src/go/rpk/pkg/oauth/providers/auth0",
"@com_github_kr_text//:text",
"@com_github_moby_term//:term",
"@com_github_redpanda_data_common_go_rpadmin//:rpadmin",
"@com_github_spf13_afero//:afero",
"@org_uber_go_zap//:zap",
Expand Down
45 changes: 37 additions & 8 deletions src/go/rpk/pkg/adminapi/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@ import (
"fmt"
"os"
"strconv"
"strings"
"time"

"github.com/kr/text"
mTerm "github.com/moby/term"

"go.uber.org/zap"

"github.com/redpanda-data/common-go/rpadmin"
Expand Down Expand Up @@ -137,22 +141,31 @@ func licenseFeatureChecks(ctx context.Context, fs afero.Fs, cl *rpadmin.AdminAPI
// violation. (we only save successful responses).
// 2. LicenseStatus was last checked more than 1 hour ago.
if p.LicenseCheck == nil || p.LicenseCheck != nil && time.Unix(p.LicenseCheck.LastUpdate, 0).Add(1*time.Hour).Before(time.Now()) {
resp, err := cl.GetEnterpriseFeatures(ctx)
featResp, err := cl.GetEnterpriseFeatures(ctx)
if err != nil {
zap.L().Sugar().Warnf("unable to check licensed enterprise features in the cluster: %v", err)
return ""
}
info, err := cl.GetLicenseInfo(ctx)
if err != nil {
zap.L().Sugar().Warnf("unable to check license information: %v", err)
return ""
}
// We don't write a profile if the config doesn't exist.
y, exists := p.ActualConfig()
var licenseCheck *config.LicenseStatusCache
if resp.Violation {
var features []string
for _, f := range resp.Features {
if f.Enabled {
features = append(features, f.Name)
}
var enabledFeatures []string
for _, f := range featResp.Features {
if f.Enabled {
enabledFeatures = append(enabledFeatures, f.Name)
}
msg = fmt.Sprintf("\nWARNING: The following Enterprise features are being used in your Redpanda cluster: %v. These features require a license. To get a license, contact us at https://www.redpanda.com/contact. For more information, see https://docs.redpanda.com/current/get-started/licenses/#redpanda-enterprise-edition\n", features)
}
isTrialCheck := isTrialAboutToExpire(info, enabledFeatures)

if featResp.Violation {
msg = fmt.Sprintf("\nWARNING: The following Enterprise features are being used in your Redpanda cluster: %v. These features require a license. To get a license, contact us at https://www.redpanda.com/contact. For more information, see https://docs.redpanda.com/current/get-started/licenses/#redpanda-enterprise-edition\n", enabledFeatures)
} else if isTrialCheck {
msg = fmt.Sprintf("\nWARNING: your TRIAL license is about to expire. The following Enterprise features are being used in your Redpanda cluster: %v. These features require a license. To get a license, contact us at https://www.redpanda.com/contact. For more information, see https://docs.redpanda.com/current/get-started/licenses/#redpanda-enterprise-edition\n", enabledFeatures)
} else {
licenseCheck = &config.LicenseStatusCache{
LastUpdate: time.Now().Unix(),
Expand All @@ -168,5 +181,21 @@ func licenseFeatureChecks(ctx context.Context, fs afero.Fs, cl *rpadmin.AdminAPI
}
}
}
if ws, err := mTerm.GetWinsize(0); err == nil {
// text.Wrap removes the newline from the text. We add it back.
msg = "\n" + text.Wrap(msg, int(ws.Width)) + "\n"
}
return msg
}

// isTrialAboutToExpire returns true if we have a loaded free_trial license that
// expires in less than 15 days, and we have enterprise features enabled.
func isTrialAboutToExpire(info rpadmin.License, enabledFeatures []string) bool {
if len(enabledFeatures) > 0 && info.Loaded && strings.EqualFold(info.Properties.Type, "free_trial") {
ut := time.Unix(info.Properties.Expires, 0)
daysLeft := int(time.Until(ut).Hours() / 24)

return daysLeft < 15 && !ut.Before(time.Now())
}
return false
}
42 changes: 36 additions & 6 deletions src/go/rpk/pkg/adminapi/admin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package adminapi

import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"testing"
Expand All @@ -16,7 +17,7 @@ func Test_licenseFeatureChecks(t *testing.T) {
tests := []struct {
name string
prof *config.RpkProfile
responseCase string // See the mapLicenseResponses below.
responseCase string // See the mapLicenseFeatureResponses below.
expContain string
withErr bool
checkCache func(t *testing.T, before int64, after int64)
Expand All @@ -27,6 +28,18 @@ func Test_licenseFeatureChecks(t *testing.T) {
responseCase: "ok",
expContain: "",
},
{
name: "free_trial about to expire, no features",
prof: &config.RpkProfile{},
responseCase: "ok-free",
expContain: "",
},
{
name: "free_trial about to expire, with features",
prof: &config.RpkProfile{},
responseCase: "ok-features",
expContain: "WARNING: your TRIAL license is about to expire",
},
{
name: "license ok, cache valid",
prof: &config.RpkProfile{LicenseCheck: &config.LicenseStatusCache{LastUpdate: time.Now().Add(20 * time.Minute).Unix()}},
Expand Down Expand Up @@ -144,16 +157,33 @@ type response struct {
body string
}

var mapLicenseResponses = map[string]response{
"ok": {http.StatusOK, `{"license_status": "valid", "violation": false}`},
var mapLicenseFeatureResponses = map[string]response{
"ok": {http.StatusOK, `{"license_status": "valid", "violation": false, "features": [{"name": "fips", "enabled": true},{"name": "partition_auto_balancing_continuous", "enabled": false}]}`},
"inViolation": {http.StatusOK, `{"license_status": "expired", "violation": true, "features": [{"name": "partition_auto_balancing_continuous", "enabled": true}]}`},
"failedRequest": {http.StatusBadRequest, ""},
"ok-free": {http.StatusOK, `{"license_status": "valid", "violation": false}`},
"ok-features": {http.StatusOK, `{"license_status": "valid", "violation": false, "features": [{"name": "partition_auto_balancing_continuous", "enabled": true}]}`},
}

var mapLicenseInfoResponses = map[string]response{
"ok": {http.StatusOK, fmt.Sprintf(`{"loaded": true, "license": {"type": "enterprise", "expires": %d}}`, time.Now().Add(60*24*time.Hour).Unix())},
"inViolation": {http.StatusOK, fmt.Sprintf(`{"loaded": true, "license": {"type": "enterprise", "expires": %d}}`, time.Now().Add(60*24*time.Hour).Unix())},
"failedRequest": {http.StatusBadRequest, ""},
"ok-free": {http.StatusOK, fmt.Sprintf(`{"loaded": true, "license": {"type": "free_trial", "expires": %d}}`, time.Now().Add(24*time.Hour).Unix())}, // expires in 1 day.
"ok-features": {http.StatusOK, fmt.Sprintf(`{"loaded": true, "license": {"type": "free_trial", "expires": %d}}`, time.Now().Add(24*time.Hour).Unix())},
}

func licenseHandler(respCase string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
resp := mapLicenseResponses[respCase]
w.WriteHeader(resp.status)
w.Write([]byte(resp.body))
fmt.Println(r.URL.Path)
if r.URL.Path == "/v1/features/enterprise" {
resp := mapLicenseFeatureResponses[respCase]
w.WriteHeader(resp.status)
w.Write([]byte(resp.body))
} else if r.URL.Path == "/v1/features/license" {
resp := mapLicenseInfoResponses[respCase]
w.WriteHeader(resp.status)
w.Write([]byte(resp.body))
}
}
}
11 changes: 8 additions & 3 deletions src/go/rpk/pkg/cli/cluster/license/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package license
import (
"fmt"
"os"
"strings"
"time"

"github.com/redpanda-data/common-go/rpadmin"
Expand Down Expand Up @@ -133,17 +134,21 @@ func printTextLicenseInfo(resp infoResponse) {
if *resp.Expired {
tw.Print("License expired:", *resp.Expired)
}
checkLicenseExpiry(resp.ExpiresUnix)
checkLicenseExpiry(resp.ExpiresUnix, resp.Type)
}
out.Section("LICENSE INFORMATION")
tw.Flush()
}

func checkLicenseExpiry(expiresUnix int64) {
func checkLicenseExpiry(expiresUnix int64, licenseType string) {
ut := time.Unix(expiresUnix, 0)
daysLeft := int(time.Until(ut).Hours() / 24)

if daysLeft < 30 && !ut.Before(time.Now()) {
dayThreshold := 30
if strings.EqualFold(licenseType, "free_trial") {
dayThreshold = 15
}
if daysLeft < dayThreshold && !ut.Before(time.Now()) {
fmt.Fprintf(os.Stderr, "WARNING: your license will expire soon.\n\n")
r-vasquez marked this conversation as resolved.
Show resolved Hide resolved
}
}