Skip to content

Commit

Permalink
Merge pull request #23965 from r-vasquez/backport-pr-23951-v24.1.x
Browse files Browse the repository at this point in the history
[v24.1.x] [DEVEX-36] rpk: change expiry check for free_trial
  • Loading branch information
r-vasquez authored Oct 31, 2024
2 parents 3b0603c + a38bb1b commit 48822d6
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 17 deletions.
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
43 changes: 35 additions & 8 deletions src/go/rpk/pkg/adminapi/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import (
"time"

"github.com/hashicorp/go-multierror"
"github.com/kr/text"
mTerm "github.com/moby/term"
"github.com/redpanda-data/redpanda/src/go/rpk/pkg/config"
"github.com/redpanda-data/redpanda/src/go/rpk/pkg/net"
"github.com/redpanda-data/redpanda/src/go/rpk/pkg/oauth"
Expand Down Expand Up @@ -678,22 +680,31 @@ func licenseFeatureChecks(ctx context.Context, fs afero.Fs, cl *AdminAPI, p *con
// 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 @@ -709,5 +720,21 @@ func licenseFeatureChecks(ctx context.Context, fs afero.Fs, cl *AdminAPI, p *con
}
}
}
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 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
}
41 changes: 35 additions & 6 deletions src/go/rpk/pkg/adminapi/admin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,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 @@ -146,6 +146,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 @@ -358,16 +370,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/redpanda/src/go/rpk/pkg/adminapi"
Expand Down Expand Up @@ -132,17 +133,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")
}
}

0 comments on commit 48822d6

Please sign in to comment.