Skip to content

Commit

Permalink
rpk: add rpk generate license
Browse files Browse the repository at this point in the history
It uses the public API to request a free trial
license for 30 days.
  • Loading branch information
r-vasquez committed Jan 14, 2025
1 parent e9de459 commit e358186
Show file tree
Hide file tree
Showing 9 changed files with 280 additions and 9 deletions.
2 changes: 2 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ use_repo(
"build_buf_gen_go_redpandadata_common_protocolbuffers_go",
"build_buf_gen_go_redpandadata_dataplane_connectrpc_go",
"build_buf_gen_go_redpandadata_dataplane_protocolbuffers_go",
"build_buf_gen_go_redpandadata_gatekeeper_connectrpc_go",
"build_buf_gen_go_redpandadata_gatekeeper_protocolbuffers_go",
"com_connectrpc_connect",
"com_github_actgardner_gogen_avro_v10",
"com_github_alecaivazis_survey_v2",
Expand Down
8 changes: 5 additions & 3 deletions src/go/rpk/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ require (
buf.build/gen/go/redpandadata/common/protocolbuffers/go v1.35.1-20240917150400-3f349e63f44a.1
buf.build/gen/go/redpandadata/dataplane/connectrpc/go v1.17.0-20241112225414-3759fedba3f3.1
buf.build/gen/go/redpandadata/dataplane/protocolbuffers/go v1.35.1-20241112225414-3759fedba3f3.1
buf.build/gen/go/redpandadata/gatekeeper/connectrpc/go v1.18.0-20241209180130-05cf059c71c1.1
buf.build/gen/go/redpandadata/gatekeeper/protocolbuffers/go v1.36.2-20241209180130-05cf059c71c1.1
cloud.google.com/go/compute/metadata v0.5.2
connectrpc.com/connect v1.17.0
connectrpc.com/connect v1.18.0
github.com/AlecAivazis/survey/v2 v2.3.7
github.com/avast/retry-go v3.0.0+incompatible
github.com/aws/aws-sdk-go v1.55.5
Expand Down Expand Up @@ -61,15 +63,15 @@ require (
golang.org/x/sync v0.8.0
golang.org/x/sys v0.28.0
golang.org/x/term v0.25.0
google.golang.org/protobuf v1.35.1
google.golang.org/protobuf v1.36.2
gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.31.2
k8s.io/apimachinery v0.31.2
k8s.io/client-go v0.31.2
)

require (
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.35.1-20240920164238-5a7b106cbb87.1 // indirect
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.2-20240920164238-5a7b106cbb87.1 // indirect
buf.build/gen/go/grpc-ecosystem/grpc-gateway/protocolbuffers/go v1.35.1-20240617172850-a48fcebcf8f1.1 // indirect
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect
Expand Down
16 changes: 10 additions & 6 deletions src/go/rpk/go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.35.1-20240920164238-5a7b106cbb87.1 h1:9wP6ZZYWnF2Z0TxmII7m3XNykxnP4/w8oXeth6ekcRI=
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.35.1-20240920164238-5a7b106cbb87.1/go.mod h1:Duw/9JoXkXIydyASnLYIiufkzySThoqavOsF+IihqvM=
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.2-20240920164238-5a7b106cbb87.1 h1:laCIQalEieFOxgzV19GyoOXwrdKjZhn7zFXt3YNkeAc=
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.2-20240920164238-5a7b106cbb87.1/go.mod h1:JnMVLi3qrNYPODVpEKG7UjHLl/d2zR221e66YCSmP2Q=
buf.build/gen/go/grpc-ecosystem/grpc-gateway/protocolbuffers/go v1.35.1-20240617172850-a48fcebcf8f1.1 h1:56K2aAfywpsJln2seD16Sfp2NJy7kYH8q3bxwbvR3J8=
buf.build/gen/go/grpc-ecosystem/grpc-gateway/protocolbuffers/go v1.35.1-20240617172850-a48fcebcf8f1.1/go.mod h1:Gob4yM1VtJ2LFWFjGhPVK32vpn1ftYpKEr72JEqRJDk=
buf.build/gen/go/redpandadata/cloud/connectrpc/go v1.17.0-20241024195046-353ea4645e3d.1 h1:8RGH8Fw8/mHvoAQSRwYPHI4JK5Xu3goqwplWTklP5RI=
Expand All @@ -12,10 +12,14 @@ buf.build/gen/go/redpandadata/dataplane/connectrpc/go v1.17.0-20241112225414-375
buf.build/gen/go/redpandadata/dataplane/connectrpc/go v1.17.0-20241112225414-3759fedba3f3.1/go.mod h1:lAVv5Nv6SZUV8+UFtUfFF2mMS4WlDp1CsOSPtNgrjPE=
buf.build/gen/go/redpandadata/dataplane/protocolbuffers/go v1.35.1-20241112225414-3759fedba3f3.1 h1:FoxR0Huu43isy8t/JcQkeORWN6KYb0SDoCKLrpU529E=
buf.build/gen/go/redpandadata/dataplane/protocolbuffers/go v1.35.1-20241112225414-3759fedba3f3.1/go.mod h1:+/pdQipFpdMztKw+xaZFHGUrwMfHLu1qyKOGpTsWFeA=
buf.build/gen/go/redpandadata/gatekeeper/connectrpc/go v1.18.0-20241209180130-05cf059c71c1.1 h1:XlPYQ+gKAnbp81oWIkaKl5g0bVDwp9/QH7EsT6wrr1U=
buf.build/gen/go/redpandadata/gatekeeper/connectrpc/go v1.18.0-20241209180130-05cf059c71c1.1/go.mod h1:KHqtiR23YDDMkNkjB50+ffEDpPMFfmvzfdgL6BH2QK0=
buf.build/gen/go/redpandadata/gatekeeper/protocolbuffers/go v1.36.2-20241209180130-05cf059c71c1.1 h1:MTirPdYgthT0qc9r2ftBQ4bHwyOJzrX780Cx702e6GA=
buf.build/gen/go/redpandadata/gatekeeper/protocolbuffers/go v1.36.2-20241209180130-05cf059c71c1.1/go.mod h1:JR47NAfo6qCtr01EWCX9DTgO5C6dsL1331MLz7+SnRg=
cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo=
cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k=
connectrpc.com/connect v1.17.0 h1:W0ZqMhtVzn9Zhn2yATuUokDLO5N+gIuBWMOnsQrfmZk=
connectrpc.com/connect v1.17.0/go.mod h1:0292hj1rnx8oFrStN7cB4jjVBeqs+Yx5yDIC2prWDO8=
connectrpc.com/connect v1.18.0 h1:7ZHAkx8fTaRO4YIyvV00XiS8bx4XjWp0grk9oh0PIQ0=
connectrpc.com/connect v1.18.0/go.mod h1:0292hj1rnx8oFrStN7cB4jjVBeqs+Yx5yDIC2prWDO8=
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
Expand Down Expand Up @@ -376,8 +380,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 h1:
google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=
google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/protobuf v1.36.2 h1:R8FeyR1/eLmkutZOM5CWghmo5itiG9z0ktFlTVLuTmU=
google.golang.org/protobuf v1.36.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
Expand Down
4 changes: 4 additions & 0 deletions src/go/rpk/pkg/cli/generate/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ go_library(
"autocomplete.go",
"generate.go",
"grafana.go",
"license.go",
"prometheus.go",
],
embedsrcs = [
Expand All @@ -26,6 +27,9 @@ go_library(
"//src/go/rpk/pkg/kafka",
"//src/go/rpk/pkg/os",
"//src/go/rpk/pkg/out",
"//src/go/rpk/pkg/publicapi",
"@build_buf_gen_go_redpandadata_gatekeeper_protocolbuffers_go//redpanda/api/gatekeeper/v1alpha1",
"@com_connectrpc_connect//:connect",
"@com_github_prometheus_client_model//go",
"@com_github_prometheus_common//expfmt",
"@com_github_spf13_afero//:afero",
Expand Down
1 change: 1 addition & 0 deletions src/go/rpk/pkg/cli/generate/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func NewCommand(fs afero.Fs, p *config.Params) *cobra.Command {
cmd.AddCommand(
newAppCmd(fs, p),
newGrafanaDashboardCmd(p),
newLicenseCommand(fs, p),
newPrometheusConfigCmd(fs, p),
newShellCompletionCommand(),
)
Expand Down
208 changes: 208 additions & 0 deletions src/go/rpk/pkg/cli/generate/license.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
// Copyright 2025 Redpanda Data, Inc.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.md
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0

package generate

import (
"errors"
"fmt"
"os"
"path/filepath"
"time"

gatekeeperv1alpha1 "buf.build/gen/go/redpandadata/gatekeeper/protocolbuffers/go/redpanda/api/gatekeeper/v1alpha1"
"connectrpc.com/connect"
"github.com/redpanda-data/redpanda/src/go/rpk/pkg/config"
rpkos "github.com/redpanda-data/redpanda/src/go/rpk/pkg/os"
"github.com/redpanda-data/redpanda/src/go/rpk/pkg/out"
"github.com/redpanda-data/redpanda/src/go/rpk/pkg/publicapi"
"github.com/spf13/afero"
"github.com/spf13/cobra"
)

type licenseRequest struct {
name string
lastname string
company string
email string
}

func newLicenseCommand(fs afero.Fs, p *config.Params) *cobra.Command {
var (
lr licenseRequest
path string
noConfirm bool
)
cmd := &cobra.Command{
Use: "license",
Short: "Generate a trial license",
Long: `Generate a trial license
This command allows you to sign up for a 30-day trial of Redpanda Enterprise.
If you require a permanent license, contact us: https://www.redpanda.com/contact
The license will be saved in your working directory or the specified path, based
on the --path flag.
`,
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, _ []string) {
cfg, err := p.Load(fs)
out.MaybeDie(err, "rpk unable to load config: %v", err)

if lr.isEmpty() {
err := lr.prompt()
out.MaybeDieErr(err)
}
err = lr.validate()
out.MaybeDieErr(err)

req := connect.NewRequest(
&gatekeeperv1alpha1.LicenseSignupRequest{
GivenName: lr.name,
FamilyName: lr.lastname,
CompanyName: lr.company,
Email: lr.email,
ClusterInfo: &gatekeeperv1alpha1.EnterpriseClusterInfo{
ClusterId: "rpk-generated",
Platform: gatekeeperv1alpha1.EnterpriseClusterInfo_PLATFORM_REDPANDA,
},
RequestOrigin: gatekeeperv1alpha1.LicenseSignupRequest_REQUEST_ORIGIN_CLI,
},
)

savePath, err := preparePath(fs, path, noConfirm)
out.MaybeDieErr(err)

cl := publicapi.NewEnterpriseClientSet(cfg.DevOverrides().PublicAPIURL)
signup, err := cl.Gatekeeper.LicenseSignup(cmd.Context(), req)
out.MaybeDie(err, "unable to request trial license: %v", err)

expirationDate := time.Now().Add(30 * 24 * time.Hour).Format(time.DateOnly)
err = rpkos.ReplaceFile(fs, savePath, []byte(signup.Msg.GetLicense().LicenseKey), 0o644)
if err != nil {
fmt.Printf(`
Successfully generated a license but we were unable to save it to a file: %v
License: %v
This license expires on %v
You may set this license by running:
rpk license set %[2]v
Or through Redpanda Console.
For more information, please visit: https://docs.redpanda.com/current/get-started/licensing/overview/#license-keys
`, err, signup.Msg.GetLicense().LicenseKey, expirationDate)
return
}

fmt.Printf(`
Successfully generated a license and it has been saved to %q.
This license expires on %v
You may set this license by running:
rpk license set --path %[1]v
Or through Redpanda Console.
For more information, please visit: https://docs.redpanda.com/current/get-started/licensing/overview/#license-keys
`, savePath, expirationDate)
},
}
cmd.Flags().StringVar(&path, "path", "", "File path for generating the license")
cmd.Flags().BoolVar(&noConfirm, "no-confirm", false, "Disable confirmation prompt for overwriting the generated license file")
// License request info.
cmd.Flags().StringVar(&lr.name, "name", "", "First name for trial license registration")
cmd.Flags().StringVar(&lr.lastname, "last-name", "", "Last name for register trial license registration")
cmd.Flags().StringVar(&lr.company, "company", "", "Company name for trial license registration")
cmd.Flags().StringVar(&lr.email, "email", "", "Company email for trial license registration")

cmd.MarkFlagsRequiredTogether("name", "last-name", "company", "email")
return cmd
}

func (l *licenseRequest) isEmpty() bool {
return l.name == "" && l.lastname == "" && l.company == "" && l.email == ""
}

func (l *licenseRequest) prompt() error {
name, err := out.Prompt("First Name:")
if err != nil {
return fmt.Errorf("unable to get the firt name: %v", err)
}
l.name = name
lastname, err := out.Prompt("Last Name:")
if err != nil {
return fmt.Errorf("unable to get the last name: %v", err)
}
l.lastname = lastname
company, err := out.Prompt("Company Name:")
if err != nil {
return fmt.Errorf("unable to get the company name: %v", err)
}
l.company = company
email, err := out.Prompt("Business Email:")
if err != nil {
return fmt.Errorf("unable to get the business email: %v", err)
}
l.email = email
return nil
}

func (l *licenseRequest) validate() error {
if l.name == "" {
return errors.New("name cannot be empty")
}
if l.lastname == "" {
return errors.New("lastname cannot be empty")
}
if l.email == "" {
return errors.New("company email cannot be empty")
}
if l.company == "" {
return errors.New("company name cannot be empty")
}
return nil
}

func preparePath(fs afero.Fs, path string, noConfirm bool) (string, error) {
if path == "" {
workingDir, err := os.Getwd()
if err != nil {
return "", err
}
path = filepath.Join(workingDir, "redpanda.license")
} else {
isDir, err := afero.IsDir(fs, path)
if err != nil {
return "", fmt.Errorf("unable to determine if path %q is a directory: %v", path, err)
}
if !isDir {
return path, nil
}
path = filepath.Join(path, "redpanda.license")
}
exists, err := afero.Exists(fs, path)
if err != nil {
return "", fmt.Errorf("unable to check if file %q exists: %v", path, err)
}
if exists && !noConfirm {
confirm, err := out.Confirm("%q already exists. Do you want to overwrite it?", path)
if err != nil {
return "", errors.New("cancelled; unable to confirm license file overwrite; you may select a new saving path using the '--path' flag")
}
if !confirm {
return "", fmt.Errorf("cancelled; overwrite not allowed on %q; you may select a new saving path using the '--path' flag", path)
}
}
return path, nil
}
2 changes: 2 additions & 0 deletions src/go/rpk/pkg/publicapi/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go_library(
srcs = [
"controlplane.go",
"dataplane.go",
"enterprise.go",
"publicapi.go",
"transform.go",
],
Expand All @@ -17,6 +18,7 @@ go_library(
"@build_buf_gen_go_redpandadata_common_protocolbuffers_go//redpanda/api/common/v1alpha1",
"@build_buf_gen_go_redpandadata_dataplane_connectrpc_go//redpanda/api/dataplane/v1alpha2/dataplanev1alpha2connect",
"@build_buf_gen_go_redpandadata_dataplane_protocolbuffers_go//redpanda/api/dataplane/v1alpha2",
"@build_buf_gen_go_redpandadata_gatekeeper_connectrpc_go//redpanda/api/gatekeeper/v1alpha1/gatekeeperv1alpha1connect",
"@com_connectrpc_connect//:connect",
"@org_uber_go_zap//:zap",
],
Expand Down
43 changes: 43 additions & 0 deletions src/go/rpk/pkg/publicapi/enterprise.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright 2025 Redpanda Data, Inc.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.md
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0

package publicapi

import (
"net/http"
"time"

"buf.build/gen/go/redpandadata/gatekeeper/connectrpc/go/redpanda/api/gatekeeper/v1alpha1/gatekeeperv1alpha1connect"
"connectrpc.com/connect"
)

// EnterpriseClientSet holds the respective service clients to interact with
// the enterprise endpoints of the Public API.
type EnterpriseClientSet struct {
Gatekeeper gatekeeperv1alpha1connect.EnterpriseServiceClient
}

// NewEnterpriseClientSet creates a Public API client set with the service
// clients of each resource available to interact with this package.
func NewEnterpriseClientSet(host string, opts ...connect.ClientOption) *EnterpriseClientSet {
if host == "" {
host = ControlPlaneProdURL
}
opts = append([]connect.ClientOption{
connect.WithInterceptors(
newLoggerInterceptor(), // Add logs to every request.
),
}, opts...)

httpCl := &http.Client{Timeout: 30 * time.Second}

return &EnterpriseClientSet{
Gatekeeper: gatekeeperv1alpha1connect.NewEnterpriseServiceClient(httpCl, host, opts...),
}
}
5 changes: 5 additions & 0 deletions tests/rptest/tests/rpk_debug_bundle_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ def test_debug_bundle(self):
continue
if re.match(r".* error querying .*\.ntp\..* i\/o timeout", l):
self.logger.error(f"Non-fatal transitory NTP error: {l}")
if re.match(r".*skipping\s+(startup_log collection|crash_reports collection):\s*(unable to find file|directory).*", l):
# this tests runs a development container, it will not have a
# startup_log and we don't expect a crash_reports dir to be
# in the data_directory as the container is new.
continue
else:
self.logger.error(f"Bad output line: {l}")
filtered_errors.append(l)
Expand Down

0 comments on commit e358186

Please sign in to comment.