1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-02 20:35:25 +02:00

feat(helm): update helm view [r8s-256] (#582)

Co-authored-by: Cara Ryan <cara.ryan@portainer.io>
Co-authored-by: James Player <james.player@portainer.io>
Co-authored-by: stevensbkang <skan070@gmail.com>
This commit is contained in:
Ali 2025-04-10 16:08:24 +12:00 committed by GitHub
parent 46eddbe7b9
commit 0ca9321db1
57 changed files with 2635 additions and 222 deletions

63
pkg/libkubectl/client.go Normal file
View file

@ -0,0 +1,63 @@
package libkubectl
import (
"bytes"
"errors"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericiooptions"
"k8s.io/kubectl/pkg/cmd/util"
)
type ClientAccess struct {
Token string
ServerUrl string
}
type Client struct {
factory util.Factory
streams genericclioptions.IOStreams
out *bytes.Buffer
}
// NewClient creates a new kubectl client
func NewClient(libKubectlAccess *ClientAccess, namespace, kubeconfig string, insecure bool) (*Client, error) {
configFlags, err := generateConfigFlags(libKubectlAccess.Token, libKubectlAccess.ServerUrl, namespace, kubeconfig, insecure)
if err != nil {
return nil, err
}
streams, _, out, _ := genericiooptions.NewTestIOStreams()
return &Client{
factory: util.NewFactory(configFlags),
streams: streams,
out: out,
}, nil
}
// generateConfigFlags generates the config flags for the kubectl client
// If kubeconfigPath is provided, it will be used instead of server and token
// If server and token are provided, they will be used to connect to the cluster
// If neither kubeconfigPath or server and token are provided, an error will be returned
func generateConfigFlags(token, server, namespace, kubeconfigPath string, insecure bool) (*genericclioptions.ConfigFlags, error) {
if kubeconfigPath == "" && (server == "" || token == "") {
return nil, errors.New("must provide either a kubeconfig path or a server and token")
}
configFlags := genericclioptions.NewConfigFlags(true)
if namespace != "" {
configFlags.Namespace = &namespace
}
if kubeconfigPath != "" {
configFlags.KubeConfig = &kubeconfigPath
} else {
configFlags.APIServer = &server
configFlags.BearerToken = &token
}
configFlags.Insecure = &insecure
return configFlags, nil
}

View file

@ -0,0 +1,101 @@
package libkubectl
import (
"testing"
)
func TestNewClient(t *testing.T) {
tests := []struct {
name string
libKubectlAccess ClientAccess
namespace string
kubeconfig string
insecure bool
wantErr bool
errContains string
}{
{
name: "valid client with token and server",
libKubectlAccess: ClientAccess{Token: "test-token", ServerUrl: "https://localhost:6443"},
namespace: "default",
insecure: true,
wantErr: false,
},
{
name: "valid client with kubeconfig",
kubeconfig: "/path/to/kubeconfig",
namespace: "test-namespace",
insecure: false,
wantErr: false,
},
{
name: "missing both token/server and kubeconfig",
namespace: "default",
insecure: false,
wantErr: true,
errContains: "must provide either a kubeconfig path or a server and token",
},
{
name: "missing token with server",
libKubectlAccess: ClientAccess{ServerUrl: "https://localhost:6443"},
namespace: "default",
insecure: false,
wantErr: true,
errContains: "must provide either a kubeconfig path or a server and token",
},
{
name: "missing server with token",
libKubectlAccess: ClientAccess{Token: "test-token"},
namespace: "default",
insecure: false,
wantErr: true,
errContains: "must provide either a kubeconfig path or a server and token",
},
{
name: "empty namespace is valid",
libKubectlAccess: ClientAccess{Token: "test-token", ServerUrl: "https://localhost:6443"},
namespace: "",
insecure: false,
wantErr: false,
},
{
name: "insecure true with valid credentials",
libKubectlAccess: ClientAccess{Token: "test-token", ServerUrl: "https://localhost:6443"},
namespace: "default",
insecure: true,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client, err := NewClient(&tt.libKubectlAccess, tt.namespace, tt.kubeconfig, tt.insecure)
if (err != nil) != tt.wantErr {
t.Errorf("NewClient() error = %v, wantErr %v", err, tt.wantErr)
return
}
if err != nil && tt.errContains != "" {
if got := err.Error(); got != tt.errContains {
t.Errorf("NewClient() error = %v, want error containing %v", got, tt.errContains)
}
return
}
if !tt.wantErr {
if client == nil {
t.Error("NewClient() returned nil client when no error was expected")
return
}
// Verify client fields are properly initialized
if client.factory == nil {
t.Error("NewClient() client.factory is nil")
}
if client.out == nil {
t.Error("NewClient() client.out is nil")
}
}
})
}
}

View file

@ -0,0 +1,40 @@
package libkubectl
import (
"fmt"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/cli-runtime/pkg/resource"
describecmd "k8s.io/kubectl/pkg/cmd/describe"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/describe"
)
// Describe returns the description of a resource
// name is the name of the resource, kind is the kind of the resource, and namespace is the namespace of the resource
// this is identical to running `kubectl describe <kind> <name> --namespace <namespace>`
func (c *Client) Describe(namespace, name, kind string) (string, error) {
describeOptions := &describecmd.DescribeOptions{
BuilderArgs: []string{kind, name},
Describer: func(mapping *meta.RESTMapping) (describe.ResourceDescriber, error) {
return describe.DescriberFn(c.factory, mapping)
},
FilenameOptions: &resource.FilenameOptions{},
DescriberSettings: &describe.DescriberSettings{
ShowEvents: true,
ChunkSize: cmdutil.DefaultChunkSize,
},
IOStreams: c.streams,
NewBuilder: c.factory.NewBuilder,
}
if namespace != "" {
describeOptions.Namespace = namespace
}
if err := describeOptions.Run(); err != nil {
return "", fmt.Errorf("error describing resources: %w", err)
}
return c.out.String(), nil
}