Skip to content

Commit

Permalink
feat: offline license validation
Browse files Browse the repository at this point in the history
  • Loading branch information
exu committed Nov 21, 2024
1 parent 5bb7dca commit bcdbfac
Show file tree
Hide file tree
Showing 19 changed files with 1,002 additions and 182 deletions.
139 changes: 139 additions & 0 deletions cmd/kubectl-testkube/commands/common/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package common

import (
"bufio"
"bytes"
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io"
Expand Down Expand Up @@ -628,6 +630,46 @@ func KubectlPrintEvents(namespace string) error {
return process.ExecuteAndStreamOutput(kubectl, args...)
}

func KubectlVersion() (client string, server string, err error) {
kubectl, err := lookupKubectlPath()

Check failure on line 634 in cmd/kubectl-testkube/commands/common/helper.go

View workflow job for this annotation

GitHub Actions / Lint Go

SA4023(related information): the lhs of the comparison gets its value from here and has a concrete type (staticcheck)
if err != nil {

Check failure on line 635 in cmd/kubectl-testkube/commands/common/helper.go

View workflow job for this annotation

GitHub Actions / Lint Go

SA4023: this comparison is always true (staticcheck)
return "", "", err
}

args := []string{
"version",
"-o", "json",
}

ui.ShellCommand(kubectl, args...)
ui.NL()

out, eerr := process.Execute(kubectl, args...)
if eerr != nil {
return "", "", eerr
}

type Version struct {
ClientVersion struct {
Version string `json:"gitVersion,omitempty"`
} `json:"clientVersion,omitempty"`
ServerVersion struct {
Version string `json:"gitVersion,omitempty"`
} `json:"serverVersion,omitempty"`
}

var v Version

out = bytes.Trim(out, "'")

err = json.Unmarshal(out, &v)
if err != nil {
return "", "", err
}

return strings.TrimLeft(v.ClientVersion.Version, "v"), strings.TrimLeft(v.ServerVersion.Version, "v"), nil
}

func KubectlDescribePods(namespace string) error {
kubectl, err := lookupKubectlPath()
if err != nil {
Expand Down Expand Up @@ -756,6 +798,56 @@ func KubectlDescribeIngresses(namespace string) error {
return process.ExecuteAndStreamOutput(kubectl, args...)
}

func KubectlGetPodEnvs(selector, namespace string) (map[string]string, error) {
kubectl, clierr := lookupKubectlPath()
if clierr != nil {
return nil, clierr.ActualError
}

args := []string{
"get",
"pod",
selector,
"-n", namespace,
"-o", `jsonpath='{range .items[*].spec.containers[*]}{"\nContainer: "}{.name}{"\n"}{range .env[*]}{.name}={.value}{"\n"}{end}{end}'`,
}

ui.ShellCommand(kubectl, args...)
ui.NL()

out, err := process.Execute(kubectl, args...)
if err != nil {
return nil, err
}

return convertEnvToMap(string(out)), nil
}

func KubectlGetSecret(selector, namespace string) (map[string]string, error) {
kubectl, clierr := lookupKubectlPath()
if clierr != nil {
return nil, clierr.ActualError
}

args := []string{
"get",
"secret",
selector,
"-n", namespace,
"-o", `jsonpath='{.data}'`,
}

ui.ShellCommand(kubectl, args...)
ui.NL()

out, err := process.Execute(kubectl, args...)
if err != nil {
return nil, err
}

return convertJSONStringToMap(string(out))
}

func lookupKubectlPath() (string, *CLIError) {
kubectlPath, err := exec.LookPath("kubectl")
if err != nil {
Expand Down Expand Up @@ -1004,3 +1096,50 @@ func GetLatestVersion() (string, error) {

return strings.TrimPrefix(metadata.TagName, "v"), nil
}

func convertEnvToMap(input string) map[string]string {
result := make(map[string]string)
scanner := bufio.NewScanner(strings.NewReader(input))

for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())

// Skip empty lines
if line == "" {
continue
}

// Split on first = only
parts := strings.SplitN(line, "=", 2)
if len(parts) != 2 {
continue // Skip invalid lines
}

key := strings.TrimSpace(parts[0])
value := strings.TrimSpace(parts[1])

// Store in map
result[key] = value
}

return result
}

func convertJSONStringToMap(in string) (map[string]string, error) {
res := map[string]string{}
in = strings.TrimLeft(in, "'")
in = strings.TrimRight(in, "'")
err := json.Unmarshal([]byte(in), &res)

if len(res) > 0 {
for k := range res {
decoded, err := base64.StdEncoding.DecodeString(res[k])
if err != nil {
return nil, err
}
res[k] = string(decoded)
}
}

return res, err
}
44 changes: 27 additions & 17 deletions cmd/kubectl-testkube/commands/diagnostics.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common"

"github.com/kubeshop/testkube/pkg/diagnostics"
"github.com/kubeshop/testkube/pkg/diagnostics/loader"
"github.com/kubeshop/testkube/pkg/diagnostics/validators/deps"
"github.com/kubeshop/testkube/pkg/diagnostics/validators/license"
"github.com/kubeshop/testkube/pkg/ui"
Expand All @@ -20,7 +21,7 @@ func NewDiagnosticsCmd() *cobra.Command {

cmd := &cobra.Command{
Use: "diagnostics",
Aliases: []string{"diag", "di"},
Aliases: []string{"diagnose", "diag", "di"},
Short: "Diagnoze testkube issues with ease",
Run: NewRunDiagnosticsCmdFunc(key, &validators, &groups),
}
Expand All @@ -31,8 +32,8 @@ func NewDiagnosticsCmd() *cobra.Command {
cmd.Flags().VarP(&validators, "commands", "s", "Comma-separated list of validators: "+allValidatorStr+", defaults to all")
cmd.Flags().VarP(&groups, "groups", "g", "Comma-separated list of groups, one of: "+allGroupsStr+", defaults to all")

cmd.Flags().StringVarP(&key, "key", "k", "", "License key")
cmd.Flags().StringVarP(&file, "file", "f", "", "License file")
cmd.Flags().StringVarP(&key, "key-override", "k", "", "Pass License key manually (we will not try to locate it automatically)")
cmd.Flags().StringVarP(&file, "file-override", "f", "", "Pass License file manually (we will not try to locate it automatically)")

return cmd
}
Expand All @@ -41,38 +42,47 @@ func NewRunDiagnosticsCmdFunc(key string, commands, groups *common.CommaList) fu
return func(cmd *cobra.Command, args []string) {

// Fetch current setup:
offlineActivation := true
key := cmd.Flag("key").Value.String()
file := cmd.Flag("file").Value.String()
namespace := cmd.Flag("namespace").Value.String()

keyOverride := cmd.Flag("key-override").Value.String()
fileOverride := cmd.Flag("file-override").Value.String()

l, err := loader.GetLicenseConfig(namespace)
ui.ExitOnError("loading license data", err)

if keyOverride != "" {
l.EnterpriseLicenseKey = keyOverride
}
if fileOverride != "" {
l.EnterpriseLicenseFile = fileOverride
}

// Compose diagnostics validators
d := diagnostics.New()

depsGroup := d.AddValidatorGroup("install.dependencies", file)
depsGroup := d.AddValidatorGroup("install.dependencies", nil)
depsGroup.AddValidator(deps.NewKubectlDependencyValidator())
depsGroup.AddValidator(deps.NewHelmDependencyValidator())

// License validator
licenseKeyGroup := d.AddValidatorGroup("license.key", key)
if offlineActivation {
if l.EnterpriseOfflineActivation {
licenseKeyGroup.AddValidator(license.NewOfflineLicenseKeyValidator())

// for offline license also add license file validator
licenseFileGroup := d.AddValidatorGroup("license.file", file)
licenseFileGroup := d.AddValidatorGroup("license.file", l.EnterpriseLicenseFile)
licenseFileGroup.AddValidator(license.NewFileValidator())

offlineLicenseGroup := d.AddValidatorGroup("license.offline.check", l.EnterpriseLicenseFile)
offlineLicenseGroup.AddValidator(license.NewOfflineLicenseValidator(l.EnterpriseLicenseKey, l.EnterpriseLicenseFile))
} else {
licenseKeyGroup.AddValidator(license.NewOnlineLicenseKeyValidator())
}

// common validator for both key types
licenseKeyGroup.AddValidator(license.NewKeygenShValidator())

// licenseKeyGroup.AddValidator(mock.AlwaysValidValidator{Name: "Key presence"})
// licenseKeyGroup.AddValidator(mock.AlwaysInvalidMultiValidator{Name: "multiple license key validation error"})

// licenseFileGroup.AddValidator(mock.AlwaysValidValidator{Name: "Date occurance"})
// licenseFileGroup.AddValidator(mock.AlwaysValidValidator{Name: "Date range"})
// licenseFileGroup.AddValidator(mock.AlwaysInvalidMultiValidator{Name: "multiple errors in validator"})
// licenseFileGroup.AddValidator(mock.AlwaysInvalidValidator{Name: "aaa2"})
// TODO allow to run partially

// Run single "diagnostic"

Expand All @@ -81,7 +91,7 @@ func NewRunDiagnosticsCmdFunc(key string, commands, groups *common.CommaList) fu
// Run predefined group

// Run all
err := d.Run()
err = d.Run()
ui.ExitOnError("Running validations", err)

ui.NL(2)
Expand Down
Loading

0 comments on commit bcdbfac

Please sign in to comment.