feat: initial daemon implementation
This commit is contained in:
262
client/client.go
Normal file
262
client/client.go
Normal file
@ -0,0 +1,262 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"code.dumpstack.io/tools/out-of-tree/api"
|
||||
"code.dumpstack.io/tools/out-of-tree/config/dotfiles"
|
||||
"code.dumpstack.io/tools/out-of-tree/distro"
|
||||
"code.dumpstack.io/tools/out-of-tree/fs"
|
||||
"code.dumpstack.io/tools/out-of-tree/qemu"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
RemoteAddr string
|
||||
}
|
||||
|
||||
func (c Client) client() *tls.Conn {
|
||||
if !fs.PathExists(dotfiles.File("daemon/cert.pem")) {
|
||||
log.Fatal().Msgf("no {cert,key}.pem at %s",
|
||||
dotfiles.Dir("daemon"))
|
||||
}
|
||||
|
||||
cert, err := tls.LoadX509KeyPair(
|
||||
dotfiles.File("daemon/cert.pem"),
|
||||
dotfiles.File("daemon/key.pem"))
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("")
|
||||
}
|
||||
|
||||
cacert, err := os.ReadFile(dotfiles.File("daemon/cert.pem"))
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("")
|
||||
}
|
||||
certpool := x509.NewCertPool()
|
||||
certpool.AppendCertsFromPEM(cacert)
|
||||
|
||||
tlscfg := &tls.Config{
|
||||
RootCAs: certpool,
|
||||
Certificates: []tls.Certificate{cert},
|
||||
}
|
||||
|
||||
conn, err := tls.Dial("tcp", c.RemoteAddr, tlscfg)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("")
|
||||
}
|
||||
|
||||
return conn // conn.Close()
|
||||
}
|
||||
|
||||
func (c Client) request(cmd api.Command, data any) (resp api.Resp, err error) {
|
||||
req := api.Req{Command: cmd}
|
||||
if data != nil {
|
||||
req.SetData(data)
|
||||
}
|
||||
|
||||
conn := c.client()
|
||||
defer conn.Close()
|
||||
|
||||
req.Encode(conn)
|
||||
|
||||
err = resp.Decode(conn)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msgf("request %v", req)
|
||||
}
|
||||
|
||||
log.Debug().Msgf("resp: %v", resp)
|
||||
|
||||
if resp.Error != "" {
|
||||
err = errors.New(resp.Error)
|
||||
log.Fatal().Err(err).Msg("")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c Client) Jobs() (jobs []api.Job, err error) {
|
||||
resp, _ := c.request(api.ListJobs, nil)
|
||||
|
||||
err = resp.GetData(&jobs)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c Client) AddJob(job api.Job) (uuid string, err error) {
|
||||
resp, err := c.request(api.AddJob, &job)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = resp.GetData(&uuid)
|
||||
return
|
||||
}
|
||||
|
||||
func (c Client) Repos() (repos []api.Repo, err error) {
|
||||
resp, _ := c.request(api.ListRepos, nil)
|
||||
|
||||
log.Debug().Msgf("resp: %v", spew.Sdump(resp))
|
||||
|
||||
err = resp.GetData(&repos)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type logWriter struct {
|
||||
tag string
|
||||
}
|
||||
|
||||
func (lw logWriter) Write(p []byte) (n int, err error) {
|
||||
n = len(p)
|
||||
log.Trace().Str("tag", lw.tag).Msgf("%v", strconv.Quote(string(p)))
|
||||
return
|
||||
}
|
||||
|
||||
func (c Client) handler(cConn net.Conn) {
|
||||
defer cConn.Close()
|
||||
|
||||
dConn := c.client()
|
||||
defer dConn.Close()
|
||||
|
||||
req := api.Req{Command: api.RawMode}
|
||||
req.Encode(dConn)
|
||||
|
||||
go io.Copy(cConn, io.TeeReader(dConn, logWriter{"recv"}))
|
||||
io.Copy(dConn, io.TeeReader(cConn, logWriter{"send"}))
|
||||
}
|
||||
|
||||
var ErrRepoNotFound = errors.New("repo not found")
|
||||
|
||||
// GetRepo virtual API call
|
||||
func (c Client) GetRepo(name string) (repo api.Repo, err error) {
|
||||
// TODO add API call
|
||||
|
||||
repos, err := c.Repos()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, r := range repos {
|
||||
if r.Name == name {
|
||||
repo = r
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = ErrRepoNotFound
|
||||
return
|
||||
}
|
||||
|
||||
func (c Client) GitProxy(addr string, ready *sync.Mutex) {
|
||||
l, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("git proxy listen")
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
log.Debug().Msgf("git proxy listen on %v", addr)
|
||||
|
||||
for {
|
||||
ready.Unlock()
|
||||
conn, err := l.Accept()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("accept")
|
||||
}
|
||||
log.Debug().Msgf("git proxy accept %s", conn.RemoteAddr())
|
||||
|
||||
go c.handler(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func (c Client) PushRepo(repo api.Repo) (err error) {
|
||||
addr := qemu.GetFreeAddrPort()
|
||||
|
||||
ready := &sync.Mutex{}
|
||||
|
||||
ready.Lock()
|
||||
go c.GitProxy(addr, ready)
|
||||
|
||||
ready.Lock()
|
||||
|
||||
remote := fmt.Sprintf("git://%s/%s", addr, repo.Name)
|
||||
log.Debug().Msgf("git proxy remote: %v", remote)
|
||||
|
||||
raw, err := exec.Command("git", "--work-tree", repo.Path, "push", remote).
|
||||
CombinedOutput()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
log.Info().Msgf("push repo %v\n%v", repo, string(raw))
|
||||
return
|
||||
}
|
||||
|
||||
func (c Client) AddRepo(repo api.Repo) (err error) {
|
||||
_, err = c.request(api.AddRepo, &repo)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
log.Info().Msgf("add repo %v", repo)
|
||||
return
|
||||
}
|
||||
|
||||
func (c Client) Kernels() (kernels []distro.KernelInfo, err error) {
|
||||
resp, err := c.request(api.Kernels, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = resp.GetData(&kernels)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("")
|
||||
}
|
||||
|
||||
log.Info().Msgf("got %d kernels", len(kernels))
|
||||
return
|
||||
}
|
||||
|
||||
func (c Client) JobStatus(uuid string) (st api.Status, err error) {
|
||||
resp, err := c.request(api.JobStatus, &uuid)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = resp.GetData(&st)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c Client) JobLogs(uuid string) (logs []api.JobLog, err error) {
|
||||
resp, err := c.request(api.JobLogs, &uuid)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = resp.GetData(&logs)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
Reference in New Issue
Block a user