-
Notifications
You must be signed in to change notification settings - Fork 597
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
It uses the public API to request a free trial license for 30 days.
- Loading branch information
Showing
9 changed files
with
280 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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...), | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters