Skip to content

Commit

Permalink
Merge pull request #622 from homeport/rework/pod-exec
Browse files Browse the repository at this point in the history
Introduce glob logic for pod-exec
  • Loading branch information
HeavyWombat authored May 14, 2024
2 parents 4edc3a9 + 60cad27 commit e3f2cde
Show file tree
Hide file tree
Showing 10 changed files with 192 additions and 126 deletions.
13 changes: 0 additions & 13 deletions .github/workflows/golangci-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,6 @@ jobs:
with:
go-version: 1.22.x

- name: Setup Go build cache
uses: actions/cache@v4
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Build source code
run: go build ./...

- name: golangci-lint
uses: golangci/golangci-lint-action@v6
with:
Expand Down
7 changes: 1 addition & 6 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,8 @@ jobs:
restore-keys: |
${{ runner.os }}-go-
- name: Build
run: go build ./...

- name: Test
run: |
go install -mod=mod github.com/onsi/ginkgo/v2/ginkgo
make test
run: make test

- name: Upload Code Coverage Profile
uses: codecov/codecov-action@v4
Expand Down
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ all: test
.PHONY: clean
clean:
@rm -rf dist
@go clean -cache $(shell go list ./...)
@go clean -i -cache

.PHONY: todo-list
todo-list:
Expand All @@ -39,14 +39,14 @@ lint:

.PHONY: misspell
misspell:
@find . -type f \( -name "*.go" -o -name "*.md" \) -print0 | xargs -0 misspell -error
@find . -type f -not -path "./vendor/*" \( -name "*.go" -o -name "*.md" \) -print0 | xargs -0 misspell -error

.PHONY: unit-test
unit-test: test

.PHONY: test
test:
@ginkgo run \
@go run -mod=mod github.com/onsi/ginkgo/v2/ginkgo run \
--coverprofile=unit.coverprofile \
--randomize-all \
--randomize-suites \
Expand Down
6 changes: 2 additions & 4 deletions internal/cmd/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func init() {
}

func retrieveClusterEvents(hvnr havener.Havener) error {
namespaces, err := havener.ListNamespaces(hvnr.Client())
namespaces, err := hvnr.ListNamespaces()
if err != nil {
return fmt.Errorf("failed to get a list of namespaces: %w", err)
}
Expand All @@ -95,10 +95,8 @@ func retrieveClusterEvents(hvnr havener.Havener) error {
for event := range watcher.ResultChan() {
switch event.Type {
case watch.Added, watch.Modified:
switch event.Object.(type) {
switch data := event.Object.(type) {
case *corev1.Event:
data := *(event.Object.(*corev1.Event))

resourceName := data.Name
if strings.Contains(resourceName, ".") {
parts := strings.Split(resourceName, ".")
Expand Down
166 changes: 94 additions & 72 deletions internal/cmd/pexec.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,29 @@
package cmd

import (
"context"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"sync"

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/gonvenience/term"
"github.com/homeport/havener/pkg/havener"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"k8s.io/client-go/kubernetes"
)

const podDefaultCommand = "/bin/sh"

type target struct {
namespace string
podName string
containerName string
}

var (
podExecNoTty bool
podExecBlock bool
Expand Down Expand Up @@ -103,20 +107,20 @@ func execInClusterPods(hvnr havener.Havener, args []string) error {
switch {
case len(args) >= 2: // pod and command is given
input, command = args[0], args[1:]
podMap, err = lookupPodsByName(hvnr.Client(), input)
podMap, err = lookupPodsByName(hvnr, input)
if err != nil {
return err
}

case len(args) == 1: // only pod is given
input, command = args[0], []string{podDefaultCommand}
podMap, err = lookupPodsByName(hvnr.Client(), input)
podMap, err = lookupPodsByName(hvnr, input)
if err != nil {
return err
}

default:
return availablePodsError(hvnr.Client(), "no pod name specified")
return availablePodsError(hvnr, "no pod name specified")
}

// Count number of containers from all pods
Expand Down Expand Up @@ -162,7 +166,7 @@ func execInClusterPods(hvnr havener.Havener, args []string) error {
printer = make(chan bool, 1)
counter = 0
)
// wg.Add(countContainers)

for pod, containers := range podMap {
for i := range containers {
wg.Add(1)
Expand Down Expand Up @@ -209,111 +213,129 @@ func execInClusterPods(hvnr havener.Havener, args []string) error {
return nil
}

func lookupPodContainers(client kubernetes.Interface, p *corev1.Pod) (containerList []string, err error) {
for _, c := range p.Spec.Containers {
containerList = append(containerList, c.Name)
func containerNames(pod *corev1.Pod) []string {
var result []string
for _, container := range pod.Spec.Containers {
result = append(result, container.Name)
}
return containerList, err

return result
}

func lookupAllPods(client kubernetes.Interface, namespaces []string) (map[*corev1.Pod][]string, error) {
var podLists = make(map[*corev1.Pod][]string)
for _, namespace := range namespaces {
podsPerNs, err := client.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{})
func (t target) String() string {
return fmt.Sprintf("%s/%s/%s",
t.namespace,
t.podName,
t.containerName,
)
}

func lookupPodsByName(h havener.Havener, input string) (map[*corev1.Pod][]string, error) {
var targets = map[*corev1.Pod][]string{}

// In case special term `all` is used, immediately return the full list of all pod containers
if input == "all" {
list, err := h.ListPods()
if err != nil {
return nil, err
}

for i := range podsPerNs.Items {
listOfContainers, err := lookupPodContainers(client, &(podsPerNs.Items[i]))
if err != nil {
return nil, err
}
podLists[&(podsPerNs.Items[i])] = listOfContainers
for _, pod := range list {
targets[pod] = containerNames(pod)
}
}
return podLists, nil
}

func lookupPodsByName(client kubernetes.Interface, input string) (map[*corev1.Pod][]string, error) {
inputList := strings.Split(input, ",")
return targets, nil
}

podList := make(map[*corev1.Pod][]string, len(inputList))
for _, podName := range inputList {
splited := strings.Split(podName, "/")
var keys, candidates []target
var lookUp = map[target]*corev1.Pod{}

for _, str := range strings.Split(input, ",") {
var splited = strings.Split(str, "/")
switch len(splited) {
case 1: // only the pod name is given
namespaces, err := havener.ListNamespaces(client)
if err != nil {
namespace, podName, containerName := "*", splited[0], "*"
candidates = append(candidates, target{namespace, podName, containerName})
if err := updateLookUps(h, &keys, lookUp, namespace); err != nil {
return nil, err
}

if input == "all" {
return lookupAllPods(client, namespaces)
case 2: // namespace, and pod name is given
namespace, podName, containerName := splited[0], splited[1], "*"
candidates = append(candidates, target{namespace, podName, containerName})
if err := updateLookUps(h, &keys, lookUp, namespace); err != nil {
return nil, err
}

pods := []*corev1.Pod{}
for _, namespace := range namespaces {
if pod, err := client.CoreV1().Pods(namespace).Get(context.TODO(), input, metav1.GetOptions{}); err == nil {
pods = append(pods, pod)
}
case 3: // namespace, pod, and container name is given
namespace, podName, containerName := splited[0], splited[1], splited[2]
candidates = append(candidates, target{namespace, podName, containerName})
if err := updateLookUps(h, &keys, lookUp, namespace); err != nil {
return nil, err
}

switch {
case len(pods) < 1:
return nil, availablePodsError(client, fmt.Sprintf("unable to find a pod named %s", input))
default:
return nil, fmt.Errorf("unsupported naming schema, it needs to be [namespace/]pod[/container]")
}
}

case len(pods) > 1:
return nil, fmt.Errorf("more than one pod named %s found, please specify a namespace", input)
for _, candidate := range candidates {
for _, key := range keys {
match, err := filepath.Match(candidate.String(), key.String())
if err != nil {
return nil, err
}

podList[pods[0]] = []string{pods[0].Spec.Containers[0].Name}

case 2: // namespace, and pod name is given
namespace, podName := splited[0], splited[1]
pod, err := client.CoreV1().Pods(namespace).Get(context.TODO(), podName, metav1.GetOptions{})
if err != nil {
return nil, availablePodsError(client, fmt.Sprintf("pod %s not found", input))
if match {
pod := lookUp[key]
targets[pod] = append(targets[pod], key.containerName)
}
}
}

podList[pod] = []string{pod.Spec.Containers[0].Name}
return targets, nil
}

case 3: // namespace, pod, and container name is given
namespace, podName, container := splited[0], splited[1], splited[2]
pod, err := client.CoreV1().Pods(namespace).Get(context.TODO(), podName, metav1.GetOptions{})
if err != nil {
return nil, availablePodsError(client, fmt.Sprintf("pod %s not found", input))
}
func updateLookUps(h havener.Havener, keys *[]target, lookUp map[target]*corev1.Pod, namespace string) error {
var namespaces []string
if namespace != "*" {
namespaces = append(namespaces, namespace)
}

podList[pod] = []string{container}
list, err := h.ListPods(namespaces...)
if err != nil {
return err
}

default:
return nil, fmt.Errorf("unsupported naming schema, it needs to be [namespace/]pod[/container]")
for i, pod := range list {
for _, containerName := range containerNames(pod) {
key := target{pod.Namespace, pod.Name, containerName}
*keys = append(*keys, key)
lookUp[key] = list[i]
}
}

return podList, nil
return nil
}

func availablePodsError(client kubernetes.Interface, title string) error {
pods, err := havener.ListPods(client)
func availablePodsError(h havener.Havener, format string, a ...any) error {
pods, err := h.ListPods()
if err != nil {
return fmt.Errorf("failed to list all pods in cluster: %w", err)
}
podList := []string{}

var targets []string
for _, pod := range pods {
for i := range pod.Spec.Containers {
podList = append(podList, fmt.Sprintf("%s/%s/%s",
pod.ObjectMeta.Namespace,
pod.Name,
pod.Spec.Containers[i].Name,
))
for _, container := range pod.Spec.Containers {
target := target{pod.Namespace, pod.Name, container.Name}
targets = append(targets, target.String())
}
}

return fmt.Errorf("%s: %w",
title,
fmt.Errorf("> Usage:\npod-exec [flags] <pod> <command>\n> List of available pods:\n%s", strings.Join(podList, "\n")),
fmt.Sprintf(format, a...),
fmt.Errorf("List of available pods:\n%s",
strings.Join(targets, "\n"),
),
)
}
2 changes: 1 addition & 1 deletion internal/cmd/top.go
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,7 @@ func renderProgressBar(value int64, max int64, caption string, text string, leng
const symbol = "■"

if !strings.HasSuffix(caption, " ") {
caption = caption + " "
caption += " "
}

if !strings.HasPrefix(text, " ") {
Expand Down
6 changes: 3 additions & 3 deletions internal/cmd/watch.go
Original file line number Diff line number Diff line change
Expand Up @@ -389,15 +389,15 @@ func humanReadableDuration(duration time.Duration) string {

if seconds >= 60 {
minutes = seconds / 60
seconds = seconds % 60
seconds %= 60

if minutes >= 60 {
hours = minutes / 60
minutes = minutes % 60
minutes %= 60

if hours >= 24 {
days = hours / 24
hours = hours % 24
hours %= 24
}
}
}
Expand Down
Loading

0 comments on commit e3f2cde

Please sign in to comment.