package main import ( "errors" "strconv" "context" "os/user" "io" "sync" "github.com/knusbaum/go9p" "github.com/knusbaum/go9p/fs" "github.com/knusbaum/go9p/proto" dtypes "github.com/docker/docker/api/types" containertypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/client" ) /// /// /// Dynamic Tree Implementation /// /// type NewStat func (name, uid, gid string, mode uint32) *proto.Stat type Mappable interface { MapFS(t *Tree) []fs.FSNode } type NodeGenerator func (t *Tree) []fs.FSNode; func (ng NodeGenerator) MapFS(t *Tree) []fs.FSNode { return ng(t) }; type Tree struct { tStat proto.Stat tParent fs.Dir //tcache []fs.FSNode NodeNS NewStat //NodeGen NodeGenerator NodeGen Mappable } func (t *Tree) Stat() proto.Stat { return t.tStat } func (t *Tree) WriteStat(s *proto.Stat) error { return errors.New("attributes are read only") } func (t *Tree) SetParent(p fs.Dir) {} func (t *Tree) Parent() fs.Dir { return t.tParent } func (t *Tree) Children() map[string]fs.FSNode { ret := make(map[string]fs.FSNode) for _, n := range t.NodeGen.MapFS(t) { //t.tcache { //t.NodeGen.MapFS(t) { //t.NodeGen(t) { ret[n.Stat().Name] = n } return ret } //func (t *Tree) Update() { // t.tcache = t.NodeGen.MapFS(t) //} //func NewTree(s *proto.Stat, p fs.Dir, ns NewStat, ng NodeGenerator) Tree { func NewTree(s *proto.Stat, p fs.Dir, ns NewStat, ng Mappable) *Tree { t := &Tree{ tStat: *s, tParent: p, //tcache: make([]fs.FSNode, 0), NodeNS: ns, NodeGen: ng, } t.tStat.Mode |= proto.DMDIR t.tStat.Qid.Qtype = uint8(t.tStat.Mode >> 24) //t.Update() return t } // This is just a helper for merging Mappables/NodeGenerators together // Allows for making distinct node generators for sections of a Dynamic Tree // without much hassle func ConcatMapFS(m ...Mappable) NodeGenerator { return func(t *Tree) []fs.FSNode { ret := make([]fs.FSNode, 0) for _, n := range m { ret = append(ret, n.MapFS(t)...) } return ret } } /// /// /// DockerFS Implementation /// /// type ContainerNode dtypes.Container func (cn ContainerNode) MapFS(t *Tree) []fs.FSNode { hnewstat := func(name string) *proto.Stat { return t.NodeNS(name, t.Stat().Uid, t.Stat().Gid, 0444) } ID_Node := fs.NewStaticFile(hnewstat("ID"), []byte(cn.ID)) Names_Node_ng := NodeGenerator(func(t *Tree) []fs.FSNode { ret := make([]fs.FSNode, 0) for i, n := range cn.Names { ret = append(ret, fs.NewStaticFile(hnewstat(strconv.Itoa(i)), []byte(n))) } return ret }) Names_Node := NewTree(t.NodeNS("Names", t.Stat().Uid, t.Stat().Gid, 0777), t, t.NodeNS, Names_Node_ng) Image_Node := fs.NewStaticFile(hnewstat("Image"), []byte(cn.Image)) ImageID_Node := fs.NewStaticFile(hnewstat("ImageID"), []byte(cn.ImageID)) Command_Node := fs.NewStaticFile(hnewstat("Command"), []byte(cn.Command)) Created_Node := fs.NewStaticFile(hnewstat("Created"), []byte(strconv.Itoa(int(cn.Created)))) return []fs.FSNode{ID_Node, Names_Node, Image_Node, ImageID_Node, Command_Node, Created_Node} } type ClientNode struct { *client.Client ctx context.Context } // Alot of this was hacked together with ideas from fs.DynamicFile type ContainerLogsNode struct { nStat proto.Stat nParent fs.Dir sync.RWMutex fidContent map[uint64]io.ReadCloser cli *ClientNode containerNode ContainerNode } func (n *ContainerLogsNode) Stat() proto.Stat { return n.nStat } func (n *ContainerLogsNode) WriteStat(s *proto.Stat) error { return errors.New("attributes are read only") } func (n *ContainerLogsNode) SetParent(p fs.Dir) {} func (n *ContainerLogsNode) Parent() fs.Dir { return n.nParent } func (n *ContainerLogsNode) Open(fid uint64, omode proto.Mode) error { n.Lock() defer n.Unlock() out, err := n.cli.ContainerLogs( //n.cli.ctx, n.nParent.Stat().Name, n.cli.ctx, n.containerNode.ID, // Tail is set to 0, but a default 1024 lines is planned // Follow is set to true so the file can be read with a // stream of the latest logs // Options structs will later just be incorporated as // part of either the ClientNode or ContainerNode with // a ctl file for changing the options containertypes.LogsOptions{ShowStdout: true, ShowStderr: true, Tail: "0", Follow: true}, ) if err != nil { panic(err) } n.fidContent[fid] = out return nil } func (n *ContainerLogsNode) Read(fid uint64, offset uint64, count uint64) ([]byte, error) { n.Lock() defer n.Unlock() bs := make([]byte, count) switch _, err := n.fidContent[fid].Read(bs); err { case nil: return bs, nil case io.EOF: return []byte{}, nil default: println(err.Error()) return []byte{}, err } } func (n *ContainerLogsNode) Write(fid uint64, offset uint64, data []byte) (uint32, error) { return 0, errors.New("Cannot write to file.") } func (n *ContainerLogsNode) Close(fid uint64) error { delete(n.fidContent, fid); return nil } // This wasn't really that clever, idk maybe we'll see /* func (n *ContainerLogsNode) MapFS(t *Tree) []fs.FSNode { n.nStat = *t.NodeNS("Logs", t.Stat().Uid, t.Stat().Gid, 0777) n.nParent = t n.fidContent = make(map[uint64]io.ReadCloser) return []fs.FSNode{n} } */ //func (n *ContainerLogsNode) MapFS(t *Tree) func NewContainerLogsNode(s *proto.Stat, p fs.Dir, cliNode *ClientNode, containerNode ContainerNode) *ContainerLogsNode { return &ContainerLogsNode{ nStat: *s, nParent: p, fidContent: make(map[uint64]io.ReadCloser), cli: cliNode, containerNode: containerNode, } } func (cn *ClientNode) MapRunningContainers(t *Tree) []fs.FSNode { ret := make([]fs.FSNode, 0) containers, err := cn.ContainerList(cn.ctx, containertypes.ListOptions{}) if err != nil { panic(err) } for _, c := range containers { //cl := &ContainerLogsNode{cli: cn} containerNode := ContainerNode(c) api_ng := func(t *Tree) []fs.FSNode { return []fs.FSNode{ NewContainerLogsNode( t.NodeNS("Logs", t.Stat().Uid, t.Stat().Gid, 0777), t, cn, containerNode, ), } } ctree := NewTree( t.NodeNS(c.ID, t.Stat().Uid, t.Stat().Gid, 0777), t, t.NodeNS, ConcatMapFS( //ContainerNode(c), containerNode, NodeGenerator(api_ng), ), ) ret = append(ret, ctree) } return ret } func (cn *ClientNode) MapFS(t *Tree) []fs.FSNode { ret := make([]fs.FSNode, 0) running_ng := NodeGenerator(cn.MapRunningContainers) ret = append(ret, NewTree( t.NodeNS("running", t.Stat().Uid, t.Stat().Gid, 0777), t, t.NodeNS, running_ng, )) return ret } func InitFS() *fs.FS { var rfs fs.FS ctx := context.Background() cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) if err != nil { panic(err) } clinode := &ClientNode{cli, ctx} u, _ := user.Current() s := rfs.NewStat("/", u.Username, u.Username, 0777) t := NewTree(s, nil, rfs.NewStat, clinode) rfs.Root = t return &rfs } func main() { rfs := InitFS() go9p.Serve("localhost:9999", rfs.Server()) }