Skip to content

Commit

Permalink
feat: CLI license Diagnostics (#6040)
Browse files Browse the repository at this point in the history
* feat: diagnostics

* feat: diagnostics

* chore: changed results

* chore: removed mess

* feat: CLI renderer updates

* feat: CLI renderer updates -k adahsjdhsajh

* fix: golang ci fixes

* feat: added deps

* feat: offline license validation

* feat: added separated commands

* validators update

* feat: ui polishing

* fix: go mod tidy

* fix: golang ci fixes and moving public key to be set through build process
  • Loading branch information
exu authored Nov 22, 2024
1 parent de1ea6e commit c805f6b
Show file tree
Hide file tree
Showing 40 changed files with 1,747 additions and 133 deletions.
1 change: 1 addition & 0 deletions .builds-windows.goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@ builds:
- -X main.builtBy=goreleaser
- -X github.com/kubeshop/testkube/pkg/telemetry.TestkubeMeasurementID={{.Env.ANALYTICS_TRACKING_ID}}
- -X github.com/kubeshop/testkube/pkg/telemetry.TestkubeMeasurementSecret={{.Env.ANALYTICS_API_KEY}}
- -X github.com/kubeshop/testkube/pkg/diagnostics/validators/license.KeygenOfflinePublicKey={{.Env.KEYGEN_PUBLIC_KEY}}
archives:
- format: binary
2 changes: 1 addition & 1 deletion cmd/kubectl-testkube/commands/common/cloudcontext.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func UiPrintContext(cfg config.Data) {
}

// add agent information only when need to change agent data, it's usually not needed in usual workflow
if ui.Verbose {
if ui.IsVerbose() {
contextData["Agent Key"] = text.Obfuscate(cfg.CloudContext.AgentKey)
contextData["Agent URI"] = cfg.CloudContext.AgentUri
}
Expand Down
199 changes: 189 additions & 10 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,51 @@ func KubectlPrintEvents(namespace string) error {
return process.ExecuteAndStreamOutput(kubectl, args...)
}

func KubectlVersion() (client string, server string, err error) {
kubectl, err := exec.LookPath("kubectl")
if err != nil {
return "", "", err
}

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

if ui.IsVerbose() {
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, err = extractJSONObject(out)
if err != nil {
return "", "", err
}

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 All @@ -640,8 +687,10 @@ func KubectlDescribePods(namespace string) error {
"-n", namespace,
}

ui.ShellCommand(kubectl, args...)
ui.NL()
if ui.IsVerbose() {
ui.ShellCommand(kubectl, args...)
ui.NL()
}

return process.ExecuteAndStreamOutput(kubectl, args...)
}
Expand All @@ -659,8 +708,10 @@ func KubectlPrintPods(namespace string) error {
"--show-labels",
}

ui.ShellCommand(kubectl, args...)
ui.NL()
if ui.IsVerbose() {
ui.ShellCommand(kubectl, args...)
ui.NL()
}

return process.ExecuteAndStreamOutput(kubectl, args...)
}
Expand All @@ -676,8 +727,10 @@ func KubectlGetStorageClass(namespace string) error {
"storageclass",
}

ui.ShellCommand(kubectl, args...)
ui.NL()
if ui.IsVerbose() {
ui.ShellCommand(kubectl, args...)
ui.NL()
}

return process.ExecuteAndStreamOutput(kubectl, args...)
}
Expand All @@ -694,8 +747,10 @@ func KubectlGetServices(namespace string) error {
"-n", namespace,
}

ui.ShellCommand(kubectl, args...)
ui.NL()
if ui.IsVerbose() {
ui.ShellCommand(kubectl, args...)
ui.NL()
}

return process.ExecuteAndStreamOutput(kubectl, args...)
}
Expand All @@ -713,8 +768,10 @@ func KubectlDescribeServices(namespace string) error {
"-o", "yaml",
}

ui.ShellCommand(kubectl, args...)
ui.NL()
if ui.IsVerbose() {
ui.ShellCommand(kubectl, args...)
ui.NL()
}

return process.ExecuteAndStreamOutput(kubectl, args...)
}
Expand Down Expand Up @@ -756,6 +813,60 @@ 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}'`,
}

if ui.IsVerbose() {
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}'`,
}

if ui.IsVerbose() {
ui.ShellCommand(kubectl, args...)
ui.NL()
}

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

return secretsJSONToMap(string(out))
}

func lookupKubectlPath() (string, *CLIError) {
kubectlPath, err := exec.LookPath("kubectl")
if err != nil {
Expand Down Expand Up @@ -1004,3 +1115,71 @@ 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 secretsJSONToMap(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
}

// extractJSONObject extracts JSON from any string
func extractJSONObject(input []byte) ([]byte, error) {
// Find the first '{' and last '}' to extract JSON object
start := bytes.Index(input, []byte("{"))
end := bytes.LastIndex(input, []byte("}"))

if start == -1 || end == -1 || start > end {
return []byte(""), fmt.Errorf("invalid JSON format")
}

jsonStr := input[start : end+1]

// Validate JSON
var prettyJSON bytes.Buffer
if err := json.Indent(&prettyJSON, []byte(jsonStr), "", " "); err != nil {
return []byte(""), err
}

return prettyJSON.Bytes(), nil
}
2 changes: 1 addition & 1 deletion cmd/kubectl-testkube/commands/common/render/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ func PrintLogs(client client.Client, info testkube.ServerInfo, execution testkub
lastSource = log.Source
}

if ui.Verbose {
if ui.IsVerbose() {
ui.Print(log.Time.Format("2006-01-02 15:04:05") + " " + log.Content)
} else {
ui.Print(log.Content)
Expand Down
40 changes: 40 additions & 0 deletions cmd/kubectl-testkube/commands/diagnostics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package commands

import (
"github.com/spf13/cobra"

commands "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/diagnostics"
"github.com/kubeshop/testkube/pkg/diagnostics"
"github.com/kubeshop/testkube/pkg/ui"
)

// NewDebugCmd creates the 'testkube debug' command
func NewDiagnosticsCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "diagnostics",
Aliases: []string{"diagnose", "diag", "di"},
Short: "Diagnoze testkube issues with ease",
Run: NewRunDiagnosticsCmdFunc(),
}

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

cmd.AddCommand(commands.NewLicenseCheckCmd())
cmd.AddCommand(commands.NewInstallCheckCmd())

return cmd
}

func NewRunDiagnosticsCmdFunc() func(cmd *cobra.Command, args []string) {
return func(cmd *cobra.Command, args []string) {
d := diagnostics.New()

commands.RegisterInstallValidators(cmd, d)
commands.RegisterLicenseValidators(cmd, d)

err := d.Run()
ui.ExitOnError("Running validations", err)
ui.NL(2)
}
}
40 changes: 40 additions & 0 deletions cmd/kubectl-testkube/commands/diagnostics/install.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package diagnostics

import (
"github.com/spf13/cobra"

"github.com/kubeshop/testkube/pkg/diagnostics"
"github.com/kubeshop/testkube/pkg/diagnostics/validators/deps"
"github.com/kubeshop/testkube/pkg/ui"
)

func RegisterInstallValidators(_ *cobra.Command, d diagnostics.Diagnostics) {
depsGroup := d.AddValidatorGroup("install.dependencies", nil)
depsGroup.AddValidator(deps.NewKubectlDependencyValidator())
depsGroup.AddValidator(deps.NewHelmDependencyValidator())
}

func NewInstallCheckCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "install",
Aliases: []string{"ins", "i"},
Short: "Diagnose pre-installation dependencies",
Run: RunInstallCheckFunc(),
}

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

return cmd
}

func RunInstallCheckFunc() func(cmd *cobra.Command, args []string) {
return func(cmd *cobra.Command, args []string) {
d := diagnostics.New()
RegisterInstallValidators(cmd, d)

err := d.Run()
ui.ExitOnError("Running validations", err)
ui.NL(2)
}
}
Loading

0 comments on commit c805f6b

Please sign in to comment.