Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,9 @@ require (
)

require (
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/tidwall/gjson v1.14.2 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

require (
Expand Down
3 changes: 0 additions & 3 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o=
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
Expand All @@ -25,7 +23,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
7 changes: 5 additions & 2 deletions pkg/internal/enterprise.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ kubeslice:
type: %s
`

var runCommandCustomIO = util.RunCommandCustomIO
var getNodeIPFunc = getNodeIP

func InstallKubeSliceUI(ApplicationConfiguration *ConfigurationSpecs) {
util.Printf("\nInstalling KubeSlice Manager...")
if ApplicationConfiguration.Configuration.HelmChartConfiguration.UIChart.ChartName == "" {
Expand Down Expand Up @@ -112,7 +115,7 @@ func GetUIEndpoint(cc *Cluster, profile string) string {
ep := ""

var outB, errB bytes.Buffer
err := util.RunCommandCustomIO("kubectl", &outB, &errB, true, "--context="+cc.ContextName, "--kubeconfig="+cc.KubeConfigPath, "get", "services", "kubeslice-ui-proxy", "-n", KUBESLICE_CONTROLLER_NAMESPACE, "-o", "jsonpath='{.spec}'")
err := runCommandCustomIO("kubectl", &outB, &errB, true, "--context="+cc.ContextName, "--kubeconfig="+cc.KubeConfigPath, "get", "services", "kubeslice-ui-proxy", "-n", KUBESLICE_CONTROLLER_NAMESPACE, "-o", "jsonpath='{.spec}'")
if err == nil {
jsonMap := make(map[string]interface{})
err = json.Unmarshal(outB.Bytes()[1:len(outB.Bytes())-1], &jsonMap)
Expand All @@ -129,7 +132,7 @@ func GetUIEndpoint(cc *Cluster, profile string) string {
portMap := port.(map[string]interface{})
if portMap["name"] == "http" { // Assuming that http is the name of the port that you want to use
nodePort := int(portMap["nodePort"].(float64))
nodeIP, err := getNodeIP(cc)
nodeIP, err := getNodeIPFunc(cc)
if err == nil {
ep = fmt.Sprintf("https://%s:%d", strings.Trim(nodeIP, "'"), nodePort)
} else {
Expand Down
129 changes: 129 additions & 0 deletions pkg/internal/enterprise_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package internal

import (
"errors"
"io"
"testing"

"github.com/kubeslice/kubeslice-cli/util"
)

// Mock getNodeIP to return dummy IP
func mockGetNodeIP(_ *Cluster) (string, error) {
return "192.168.1.100", nil
}

func TestGetUIEndpoint_NodePort(t *testing.T) {
runCalled := false

// Mock kubectl output for NodePort
nodePortJSON := `{
"type": "NodePort",
"ports": [{
"name": "http",
"nodePort": 30080
}]
}`

runCommandCustomIO = func(name string, stdout, stderr io.Writer, _ bool, args ...string) error {
runCalled = true
stdout.Write([]byte("'" + nodePortJSON + "'")) // wrap in quotes to simulate jsonpath output
return nil
}
getNodeIPFunc = mockGetNodeIP
defer func() {
runCommandCustomIO = util.RunCommandCustomIO
getNodeIPFunc = getNodeIP
}()

cluster := &Cluster{
ContextName: "mock-context",
KubeConfigPath: "/fake/config",
}
endpoint := GetUIEndpoint(cluster, "some-profile")

expected := "https://192.168.1.100:30080"
if endpoint != expected {
t.Errorf("Expected endpoint %q, got %q", expected, endpoint)
}
if !runCalled {
t.Error("Expected RunCommandCustomIO to be called")
}
}

// Mocks a LoadBalancer service and checks the endpoint.
func TestGetUIEndpoint_LoadBalancer(t *testing.T) {
runCalled := false

// Mock output for LoadBalancer
loadBalancerJSON := `{
"type": "LoadBalancer",
"externalIPs": ["1.2.3.4"],
"ports": [{
"name": "http",
"port": 443
}]
}`

runCommandCustomIO = func(name string, stdout, stderr io.Writer, _ bool, args ...string) error {
runCalled = true
stdout.Write([]byte("'" + loadBalancerJSON + "'"))
return nil
}
defer func() {
runCommandCustomIO = util.RunCommandCustomIO
}()

cluster := &Cluster{
ContextName: "mock-context",
KubeConfigPath: "/fake/config",
}
endpoint := GetUIEndpoint(cluster, "some-profile")

expected := "https://1.2.3.4:443"
if endpoint != expected {
t.Errorf("Expected endpoint %q, got %q", expected, endpoint)
}
if !runCalled {
t.Error("Expected RunCommandCustomIO to be called")
}
}

// Mocks invalid JSON output and checks that the function returns an empty string
func TestGetUIEndpoint_InvalidJSON(t *testing.T) {
runCommandCustomIO = func(name string, stdout, stderr io.Writer, _ bool, args ...string) error {
stdout.Write([]byte("'not-a-json'"))
return nil
}
defer func() {
runCommandCustomIO = util.RunCommandCustomIO
}()

cluster := &Cluster{
ContextName: "mock-context",
KubeConfigPath: "/fake/config",
}
endpoint := GetUIEndpoint(cluster, "profile")
if endpoint != "" {
t.Errorf("Expected empty endpoint on invalid JSON, got %q", endpoint)
}
}

// Mocks a command failure and checks that the function returns an empty string
func TestGetUIEndpoint_CommandFailure(t *testing.T) {
runCommandCustomIO = func(name string, stdout, stderr io.Writer, _ bool, args ...string) error {
return errors.New("kubectl failed")
}
defer func() {
runCommandCustomIO = util.RunCommandCustomIO
}()

cluster := &Cluster{
ContextName: "mock-context",
KubeConfigPath: "/fake/config",
}
endpoint := GetUIEndpoint(cluster, "profile")
if endpoint != "" {
t.Errorf("Expected empty endpoint on command failure, got %q", endpoint)
}
}
2 changes: 1 addition & 1 deletion pkg/internal/kubernetes-operation.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func ApplyKubectlManifest(fileName, namespace string, cluster *Cluster) {
}
}

func GetKubectlResources(resourceType string, resourceName string, namespace string, cluster *Cluster, outputFormat string) {
var GetKubectlResources = func(resourceType string, resourceName string, namespace string, cluster *Cluster, outputFormat string) {
cmdArgs := []string{}
if cluster != nil {
cmdArgs = append(cmdArgs, "--context="+cluster.ContextName, "--kubeconfig="+cluster.KubeConfigPath)
Expand Down
138 changes: 138 additions & 0 deletions pkg/internal/secret_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package internal

import (
"fmt"
"os"
"os/exec"
"path/filepath"
"testing"
"time"
)

// Helper to create a fake kubectl binary for testing
func createFakeKubectl(t *testing.T, output string, fail bool) string {
dir := t.TempDir()
kubectlPath := filepath.Join(dir, "kubectl")
script := "#!/bin/sh\n"
if fail {
script += "exit 1\n"
} else {
script += "echo \"" + output + "\"\n"
}
if err := os.WriteFile(kubectlPath, []byte(script), 0755); err != nil {
t.Fatalf("failed to write fake kubectl: %v", err)
}
return kubectlPath
}

func TestGetSecretName_Found(t *testing.T) {
kubectl := createFakeKubectl(t, "worker-foo-secret some-data\nother-secret data", false)

origKubectlPath := os.Getenv("KUBECTL_PATH")
os.Setenv("KUBECTL_PATH", kubectl)
defer os.Setenv("KUBECTL_PATH", origKubectlPath)

origPath := os.Getenv("PATH")
tempDir := filepath.Dir(kubectl)
os.Setenv("PATH", tempDir+":"+origPath)
defer os.Setenv("PATH", origPath)

t.Logf("Testing command pipeline manually...")
cmd := exec.Command("sh", "-c", fmt.Sprintf("%s get secret -n default | grep worker-foo | awk '{print $1}'", kubectl))
output, err := cmd.CombinedOutput()
if err != nil {
t.Logf("Manual command failed: %v, output: %s", err, string(output))
} else {
t.Logf("Manual command succeeded: %s", string(output))
}

t.Logf("Running GetSecretName...")
name := GetSecretName("foo", "default", nil)
t.Logf("GetSecretName returned: %q", name)

if name != "worker-foo-secret" {
t.Errorf("expected 'worker-foo-secret', got %q", name)
}
}

func TestGetSecretName_NotFound(t *testing.T) {
kubectl := createFakeKubectl(t, "other-secret data", false)
origPath := os.Getenv("PATH")
os.Setenv("PATH", filepath.Dir(kubectl)+":"+origPath)
defer os.Setenv("PATH", origPath)

orig := "/home/excellarate/.local/bin/kubectl"
if _, err := os.Stat(orig); err == nil {
os.Remove(orig)
}
os.Symlink(kubectl, orig)
defer os.Remove(orig)

name := GetSecretName("foo", "default", nil)
if name != "" {
t.Errorf("expected '', got %q", name)
}
}

func TestGetSecretName_KubectlFails(t *testing.T) {
kubectl := createFakeKubectl(t, "", true)
origPath := os.Getenv("PATH")
os.Setenv("PATH", filepath.Dir(kubectl)+":"+origPath)
defer os.Setenv("PATH", origPath)

orig := "/home/excellarate/.local/bin/kubectl"
if _, err := os.Stat(orig); err == nil {
os.Remove(orig)
}
os.Symlink(kubectl, orig)
defer os.Remove(orig)

name := GetSecretName("foo", "default", nil)
if name != "" {
t.Errorf("expected '', got %q", name)
}
}

func TestGetSecrets_CallsKubectl(t *testing.T) {
// This test just checks that the function runs without panic
kubectl := createFakeKubectl(t, "worker-foo-secret some-data", false)
origPath := os.Getenv("PATH")
os.Setenv("PATH", filepath.Dir(kubectl)+":"+origPath)
defer os.Setenv("PATH", origPath)

orig := "/home/excellarate/.local/bin/kubectl"
if _, err := os.Stat(orig); err == nil {
os.Remove(orig)
}
os.Symlink(kubectl, orig)
defer os.Remove(orig)
origFunc := GetKubectlResources
GetKubectlResources = func(a, b, c string, d *Cluster, e string) {}
defer func() { GetKubectlResources = origFunc }()

GetSecrets("foo", "default", nil, "yaml")
}

func TestGetSecrets_Sleep(t *testing.T) {
start := time.Now()
kubectl := createFakeKubectl(t, "worker-foo-secret some-data", false)
origPath := os.Getenv("PATH")
os.Setenv("PATH", filepath.Dir(kubectl)+":"+origPath)
defer os.Setenv("PATH", origPath)

orig := "/home/excellarate/.local/bin/kubectl"
if _, err := os.Stat(orig); err == nil {
os.Remove(orig)
}
os.Symlink(kubectl, orig)
defer os.Remove(orig)

origFunc := GetKubectlResources
GetKubectlResources = func(a, b, c string, d *Cluster, e string) {}
defer func() { GetKubectlResources = origFunc }()

GetSecrets("foo", "default", nil, "yaml")
if time.Since(start) < 200*time.Millisecond {
t.Errorf("expected at least 200ms sleep")
}
}
7 changes: 6 additions & 1 deletion pkg/internal/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package internal

import (
"bytes"
"os"
"os/exec"
"strings"
"time"
Expand All @@ -20,7 +21,11 @@ func GetSecretName(workerName string, namespace string, controllerCluster *Clust
cmdArgs := []string{}
cmdArgs = append(cmdArgs, "get", SecretObject, "-n", namespace)
var outB bytes.Buffer
c1 := exec.Command("/home/excellarate/.local/bin/kubectl", cmdArgs...)
kubectlPath := os.Getenv("KUBECTL_PATH")
if kubectlPath == "" {
kubectlPath = "/home/excellarate/.local/bin/kubectl"
}
c1 := exec.Command(kubectlPath, cmdArgs...)
c2 := exec.Command("grep", "worker-"+workerName)
c3 := exec.Command("awk", "{print $1}")
c2.Stdin, _ = c1.StdoutPipe()
Expand Down
4 changes: 3 additions & 1 deletion pkg/ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package pkg

import "github.com/kubeslice/kubeslice-cli/pkg/internal"

var getUIEndpointFunc = internal.GetUIEndpoint

func GetUIEndpoint() {
internal.GetUIEndpoint(CliOptions.Cluster, ApplicationConfiguration.Configuration.ClusterConfiguration.Profile)
getUIEndpointFunc(CliOptions.Cluster, ApplicationConfiguration.Configuration.ClusterConfiguration.Profile)
}
Loading