diff --git a/.github/workflows/build-by-tag.yml b/.github/workflows/build-by-tag.yml index 3754a798..2c90144e 100644 --- a/.github/workflows/build-by-tag.yml +++ b/.github/workflows/build-by-tag.yml @@ -60,3 +60,6 @@ jobs: tag: ${{ github.ref }} overwrite: true file_glob: true + + - name: Update new version in krew-index + uses: rajatjindal/krew-release-bot@v0.0.43 diff --git a/.krew.yaml b/.krew.yaml new file mode 100644 index 00000000..e58bd85c --- /dev/null +++ b/.krew.yaml @@ -0,0 +1,50 @@ +apiVersion: krew.googlecontainertools.github.com/v1alpha2 +kind: Plugin +metadata: + name: nsenter +spec: + version: {{ .TagName }} + homepage: https://github.com/pabateman/kubectl-nsenter + shortDescription: "Run shell command in Pod's namespace on the node over SSH connection" + description: | + This plugin establishes a connection to node that's running the Pod over SSH and uses + nsenter to run commands in the container's namespace. You would need something + like this to run programs that are missing in the container image but presents on node. + caveats: | + * This plugin needs SSH access to nodes + * Remote user must have root access + * Nodes need to have 'nsenter' + platforms: + - selector: + matchLabels: + os: linux + arch: amd64 + {{addURIAndSha "https://github.com/pabateman/kubectl-nsenter/releases/download/{{ .TagName }}/kubectl-nsenter-linux-amd64.tar.gz" .TagName }} + bin: kubectl-nsenter + files: + - from: kubectl-nsenter-linux-amd64 + to: kubectl-nsenter + - from: LICENSE + to: . + - selector: + matchLabels: + os: darwin + arch: amd64 + {{addURIAndSha "https://github.com/pabateman/kubectl-nsenter/releases/download/{{ .TagName }}/kubectl-nsenter-darwin-amd64.tar.gz" .TagName }} + bin: kubectl-nsenter + files: + - from: kubectl-nsenter-darwin-amd64 + to: kubectl-nsenter + - from: LICENSE + to: . + - selector: + matchLabels: + os: darwin + arch: arm64 + {{addURIAndSha "https://github.com/pabateman/kubectl-nsenter/releases/download/{{ .TagName }}/kubectl-nsenter-darwin-arm64.tar.gz" .TagName }} + bin: kubectl-nsenter + files: + - from: kubectl-nsenter-darwin-arm64 + to: kubectl-nsenter + - from: LICENSE + to: . diff --git a/.krew/nsenter.yaml b/.krew/nsenter.yaml deleted file mode 100644 index 0c30f0d7..00000000 --- a/.krew/nsenter.yaml +++ /dev/null @@ -1,49 +0,0 @@ -apiVersion: krew.googlecontainertools.github.com/v1alpha2 -kind: Plugin -metadata: - name: nsenter -spec: - version: "v0.1.0" - homepage: https://github.com/pabateman/kubectl-nsenter - shortDescription: "Execute shell command under container namespace of pod" - description: | - Kubectl plugin for pod's linux namespaces command execution via direct node ssh connection - caveats: | - Almost done. Now activate your ssh agent ang go on! - platforms: - - selector: - matchLabels: - os: linux - arch: amd64 - uri: https://github.com/pabateman/kubectl-nsenter/releases/download/v0.1.0/kubectl-nsenter-linux-amd64.tar.gz - sha256: 51707d4fb17e3280ddbb68a3730f5e6e01c1d4d493293adbf8d168306aaf5609 - bin: kubectl-nsenter - files: - - from: kubectl-nsenter-linux-amd64 - to: kubectl-nsenter - - from: LICENSE - to: . - - selector: - matchLabels: - os: darwin - arch: amd64 - uri: https://github.com/pabateman/kubectl-nsenter/releases/download/v0.1.0/kubectl-nsenter-darwin-amd64.tar.gz - sha256: ee9d1edb25e834310f032669e53a676965a4cb17ced4ea49a4e0c8f484387a65 - bin: kubectl-nsenter - files: - - from: kubectl-nsenter-darwin-amd64 - to: kubectl-nsenter - - from: LICENSE - to: . - - selector: - matchLabels: - os: darwin - arch: arm64 - uri: https://github.com/pabateman/kubectl-nsenter/releases/download/v0.1.0/kubectl-nsenter-darwin-arm64.tar.gz - sha256: f0a6ec223938d17779b66a186a0b9cd95eccfc21ec27457b4f3ebcd8d7e0159b - bin: kubectl-nsenter - files: - - from: kubectl-nsenter-darwin-arm64 - to: kubectl-nsenter - - from: LICENSE - to: . diff --git a/cmd/kubectl-nsenter/main.go b/cmd/kubectl-nsenter/main.go index ed3e7722..a0bf831b 100644 --- a/cmd/kubectl-nsenter/main.go +++ b/cmd/kubectl-nsenter/main.go @@ -13,7 +13,7 @@ import ( func main() { app := &cli.App{ Name: "kubectl-nsenter", - Version: "v0.1.0", + Version: "v0.1.1", Compiled: time.Now(), Authors: []*cli.Author{ { @@ -78,6 +78,11 @@ func main() { DefaultText: "current shell auth sock", Required: false, }, + &cli.StringFlag{ + Name: "host", + Usage: "override node ip", + Required: false, + }, &cli.StringFlag{ Name: "port", Aliases: []string{"p"}, diff --git a/internal/nsenter/containerinfo.go b/internal/nsenter/containerinfo.go index 585b63b8..4c280099 100644 --- a/internal/nsenter/containerinfo.go +++ b/internal/nsenter/containerinfo.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/pkg/errors" + "github.com/urfave/cli/v2" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" @@ -18,41 +19,51 @@ type ContainerInfo struct { ContainerRuntime string } -func GetContainerInfo(kubeconfigFiles []string, contextOverride string, namespaceOverride string, pod string, container string) (*ContainerInfo, error) { - +func GetClientSet(clictx *cli.Context) (*kubernetes.Clientset, string, error) { config := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( - &clientcmd.ClientConfigLoadingRules{Precedence: kubeconfigFiles}, + &clientcmd.ClientConfigLoadingRules{Precedence: strings.Split(clictx.String("kubeconfig"), ":")}, &clientcmd.ConfigOverrides{ - CurrentContext: contextOverride, + CurrentContext: clictx.String("context"), }) - var namespace string - var err error - if namespaceOverride == "" { - namespace, _, err = config.Namespace() - if err != nil { - return nil, errors.Wrap(err, "can't get current namespace") - } - } else { - namespace = namespaceOverride - } - clientConfig, err := config.ClientConfig() if err != nil { - return nil, errors.Wrap(err, "can't build config") + return nil, "", errors.Wrap(err, "can't build config") } + clientset, err := kubernetes.NewForConfig(clientConfig) + if err != nil { + return nil, "", errors.Wrap(err, "can't build client") + } + + namespace, _, err := config.Namespace() + if err != nil { + return nil, "", errors.Wrap(err, "can't get current namespace") + } + + return clientset, namespace, nil +} + +func GetContainerInfo(clictx *cli.Context) (*ContainerInfo, error) { + + kubeClient, namespace, err := GetClientSet(clictx) if err != nil { return nil, errors.Wrap(err, "can't build client") } - podSpec, err := clientset.CoreV1().Pods(namespace).Get(context.TODO(), pod, metav1.GetOptions{}) + if clictx.String("namespace") != "" { + namespace = clictx.String("namespace") + } + + podSpec, err := kubeClient.CoreV1().Pods(namespace).Get(context.TODO(), clictx.Args().First(), metav1.GetOptions{}) if err != nil { return nil, errors.Wrap(err, "can't get pod spec") } var containerID string var containerRuntime string + container := clictx.String("container") + if container != "" { for _, containerStatus := range podSpec.Status.ContainerStatuses { if containerStatus.Name == container { diff --git a/internal/nsenter/nsenter.go b/internal/nsenter/nsenter.go index f7af5e94..7164b284 100644 --- a/internal/nsenter/nsenter.go +++ b/internal/nsenter/nsenter.go @@ -26,27 +26,19 @@ func requestPassword(user, host string) (string, error) { } func Nsenter(clictx *cli.Context) error { - container := clictx.String("container") - kubeconfigPath := clictx.String("kubeconfig") - contextOverride := clictx.String("context") - namespaceOverride := clictx.String("namespace") podName := clictx.Args().First() if podName == "" { fmt.Println("you must specify pod name!") return cli.ShowAppHelp(clictx) } + command := clictx.Args().Tail() if len(command) == 0 { fmt.Println("you must provide a command!") return cli.ShowAppHelp(clictx) } - kubeconfigFiles := strings.Split(kubeconfigPath, ":") - containerInfo, err := GetContainerInfo( - kubeconfigFiles, - contextOverride, - namespaceOverride, - podName, - container) + + containerInfo, err := GetContainerInfo(clictx) if err != nil { return errors.WithMessage(err, "can't get container info") } @@ -74,6 +66,10 @@ func Nsenter(clictx *cli.Context) error { } } + if clictx.String("host") != "" { + containerInfo.NodeIP = clictx.String("host") + } + sshPort := clictx.String("port") sshHost := net.JoinHostPort(containerInfo.NodeIP, sshPort) @@ -112,7 +108,7 @@ func Nsenter(clictx *cli.Context) error { switch containerInfo.ContainerRuntime { case "docker": pidDiscoverCommand = fmt.Sprintf("sudo docker inspect %s --format {{.State.Pid}}", containerInfo.ContainerID) - case "containerd": + case "containerd", "cri-o": pidDiscoverCommand = fmt.Sprintf("sudo crictl inspect --output go-template --template={{.info.pid}} %s", containerInfo.ContainerID) default: return fmt.Errorf("unsupported container runtime")