Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Filebrowser #3053

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
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
58 changes: 58 additions & 0 deletions internal/dao/dir_local.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s

package dao

import (
"context"
"errors"
"os"
"path/filepath"

"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/render"
"k8s.io/apimachinery/pkg/runtime"
)

var _ Accessor = (*Dir)(nil)

// DirLocal tracks standard and custom command aliases.
type DirLocal struct {
NonResource
}

// NewDirLocal returns a new set of aliases.
func NewDirLocal(f Factory) *Dir {
var a Dir
a.Init(f, client.NewGVR("dirlocal"))
return &a
}

// List returns a collection of aliases.
func (a *DirLocal) List(ctx context.Context, _ string) ([]runtime.Object, error) {
dir, ok := ctx.Value(internal.KeyPath).(string)
if !ok {
return nil, errors.New("no dir in context")
}

files, err := os.ReadDir(dir)
if err != nil {
return nil, err
}

oo := make([]runtime.Object, 0, len(files))
for _, f := range files {
oo = append(oo, render.DirRes{
Path: filepath.Join(dir, f.Name()),
Entry: f,
})
}

return oo, err
}

// Get fetch a resource.
func (a *DirLocal) Get(_ context.Context, _ string) (runtime.Object, error) {
return nil, errors.New("nyi")
}
87 changes: 87 additions & 0 deletions internal/dao/dir_remote.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s

package dao

import (
"context"
"errors"
"path/filepath"
"strings"

"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/render"
"k8s.io/apimachinery/pkg/runtime"
)

var _ Accessor = (*DirRemote)(nil)

// Dir tracks standard and custom command aliases.
type DirRemote struct {
NonResource
}

// NewDirRemote returns a new set of aliases.
func NewDirRemote(f Factory) *DirRemote {
var a DirRemote
a.Init(f, client.NewGVR("dirremote"))
return &a
}

// List returns a collection of aliases.
func (a *DirRemote) List(ctx context.Context, _ string) ([]runtime.Object, error) {
dir, ok := ctx.Value(internal.KeyPath).(string)
if !ok {
return nil, errors.New("no dir in context")
}

// It would be better to list files here ?
// No access to view/exec.go, though
txt, ok := ctx.Value(internal.KeyContents).(string)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed! Not keen on stuffing the dir contents into the context. Best if it worked like local list. I think we should have a util call to list out dir content on a pod. If the container is not specified, we can use the main container.

if !ok {
return nil, errors.New("no contents in context")
}

var err error = nil
lines := strings.Split(txt, "\n")
oo := make([]runtime.Object, 0, len(lines))
for _, name := range lines {
if len(name) == 0 {
continue
}
name = strings.TrimSuffix(name, "\n")
name = strings.TrimSuffix(name, "\r")
if name == "./" {
continue
}
if name == "../" && dir == "/" {
continue
}
//if strings.HasSuffix(name, "/") { // directory
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

// do not strip the trailing slash
//name = strings.TrimSuffix(name, "/")
//}
name = strings.TrimSuffix(name, "*") // executable
if strings.HasSuffix(name, "@") { // symlink
continue // kubectl cp ignores symlinks
}
if strings.HasSuffix(name, "|") { // pipe
continue
}
if strings.HasSuffix(name, "=") { // socket
continue
}
oo = append(oo, render.DirRemoteRes{
Path: filepath.Join(dir, name),
Name: name,
})
}

return oo, err
}

// Get fetch a resource.
func (a *DirRemote) Get(_ context.Context, _ string) (runtime.Object, error) {
return nil, errors.New("nyi")
}
14 changes: 14 additions & 0 deletions internal/dao/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ func AccessorFor(f Factory, gvr client.GVR) (Accessor, error) {
client.NewGVR("benchmarks"): &Benchmark{},
client.NewGVR("portforwards"): &PortForward{},
client.NewGVR("dir"): &Dir{},
client.NewGVR("dirlocal"): &DirLocal{},
client.NewGVR("dirremote"): &DirRemote{},
client.NewGVR("v1/services"): &Service{},
client.NewGVR("v1/pods"): &Pod{},
client.NewGVR("v1/nodes"): &Node{},
Expand Down Expand Up @@ -227,6 +229,18 @@ func loadK9s(m ResourceMetas) {
SingularName: "dir",
Categories: []string{k9sCat},
}
m[client.NewGVR("dirlocal")] = metav1.APIResource{
Name: "dirlocal",
Kind: "DirLocal",
SingularName: "dirlocal",
Categories: []string{k9sCat},
}
m[client.NewGVR("dirremote")] = metav1.APIResource{
Name: "dirremote",
Kind: "DirRemote",
SingularName: "dirremote",
Categories: []string{k9sCat},
}
m[client.NewGVR("xrays")] = metav1.APIResource{
Name: "xray",
Kind: "XRays",
Expand Down
1 change: 1 addition & 0 deletions internal/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,5 @@ const (
KeyWait ContextKey = "wait"
KeyPodCounting ContextKey = "podCounting"
KeyEnableImgScan ContextKey = "vulScan"
KeyContents ContextKey = "contents"
)
8 changes: 8 additions & 0 deletions internal/model/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ var Registry = map[string]ResourceMeta{
DAO: &dao.Dir{},
Renderer: &render.Dir{},
},
"dirlocal": {
DAO: &dao.DirLocal{},
Renderer: &render.Dir{},
},
"dirremote": {
DAO: &dao.DirRemote{},
Renderer: &render.DirRemote{},
},
"pulses": {
DAO: &dao.Pulse{},
},
Expand Down
75 changes: 75 additions & 0 deletions internal/render/dir_remote.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s

package render

import (
"fmt"
"strings"

"github.com/derailed/k9s/internal/model1"
"github.com/derailed/tcell/v2"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)

// DirRemote renders a directory entry to screen.
type DirRemote struct{}

// IsGeneric identifies a generic handler.
func (DirRemote) IsGeneric() bool {
return false
}

// ColorerFunc colors a resource row.
func (DirRemote) ColorerFunc() model1.ColorerFunc {
return func(ns string, _ model1.Header, re *model1.RowEvent) tcell.Color {
return tcell.ColorCadetBlue
}
}

// Header returns a header row.
func (DirRemote) Header(ns string) model1.Header {
return model1.Header{
model1.HeaderColumn{Name: "NAME"},
}
}

// Render renders a K8s resource to screen.
// BOZO!! Pass in a row with pre-alloc fields??
func (DirRemote) Render(o interface{}, ns string, r *model1.Row) error {
d, ok := o.(DirRemoteRes)
if !ok {
return fmt.Errorf("expected DirRemoteRes, but got %T", o)
}
var name string
var path = d.Path
if strings.HasSuffix(d.Name, "/") { // directory
name = "📁 " + strings.TrimSuffix(d.Name, "/")
path = path + "/" // was stripped by filepath
} else {
name = "📄 " + d.Name
}
r.ID, r.Fields = path, append(r.Fields, name)

return nil
}

// ----------------------------------------------------------------------------
// Helpers...

// DirRes represents an alias resource.
type DirRemoteRes struct {
Name string
Path string
}

// GetObjectKind returns a schema object.
func (DirRemoteRes) GetObjectKind() schema.ObjectKind {
return nil
}

// DeepCopyObject returns a container copy.
func (d DirRemoteRes) DeepCopyObject() runtime.Object {
return d
}
12 changes: 9 additions & 3 deletions internal/ui/dialog/transfer.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ type TransferDialogOpts struct {
Containers []string
Pod string
Title, Message string
File string
Download bool
Retries int
Ack TransferFn
Cancel cancelFunc
Expand All @@ -47,11 +49,15 @@ func ShowUploads(styles config.Dialog, pages *ui.Pages, opts TransferDialogOpts)
modal := tview.NewModalForm("<"+opts.Title+">", f)

args := TransferArgs{
From: opts.Pod,
Retries: opts.Retries,
Download: opts.Download,
Retries: opts.Retries,
}
if opts.Download {
args.From, args.To = opts.Pod, opts.File
} else {
args.From, args.To = opts.File, opts.Pod
}
var fromField, toField *tview.InputField
args.Download = true
f.AddCheckbox("Download:", args.Download, func(_ string, flag bool) {
if flag {
modal.SetText(strings.Replace(opts.Message, "Upload", "Download", 1))
Expand Down
16 changes: 16 additions & 0 deletions internal/view/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,13 @@ func (c *Container) bindDangerousKeys(aa *ui.KeyActions) {
Visible: true,
Dangerous: true,
}),
ui.KeyB: ui.NewKeyActionWithOpts(
"Browse",
c.browseCmd,
ui.ActionOpts{
Visible: true,
Dangerous: true,
}),
})
}

Expand Down Expand Up @@ -204,6 +211,15 @@ func (c *Container) portFwdCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil
}

func (c *Container) browseCmd(evt *tcell.EventKey) *tcell.EventKey {
co := c.GetTable().GetSelectedItem()
if co == "" {
return evt
}
dirRemoteIn(c.App(), c, c.GetTable().Path, co, "")
return nil
}

func checkRunningStatus(co string, ss []v1.ContainerStatus) error {
var cs *v1.ContainerStatus
for i := range ss {
Expand Down
2 changes: 1 addition & 1 deletion internal/view/container_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ func TestContainerNew(t *testing.T) {

assert.Nil(t, c.Init(makeCtx()))
assert.Equal(t, "Containers", c.Name())
assert.Equal(t, 19, len(c.Hints()))
assert.Equal(t, 20, len(c.Hints()))
}
Loading