Merge branch 'v0.3.0' into stale-account-request-cleanup

This commit is contained in:
Cam Otts 2023-01-12 10:14:03 -06:00
commit 3a79608474
No known key found for this signature in database
GPG Key ID: 367B7C7EBD84A8BD
21 changed files with 1075 additions and 161 deletions

View File

@ -1,8 +1,10 @@
package main package main
import ( import (
tea "github.com/charmbracelet/bubbletea"
"github.com/go-openapi/runtime" "github.com/go-openapi/runtime"
httptransport "github.com/go-openapi/runtime/client" httptransport "github.com/go-openapi/runtime/client"
"github.com/openziti-test-kitchen/zrok/endpoints"
"github.com/openziti-test-kitchen/zrok/endpoints/privateFrontend" "github.com/openziti-test-kitchen/zrok/endpoints/privateFrontend"
"github.com/openziti-test-kitchen/zrok/rest_client_zrok" "github.com/openziti-test-kitchen/zrok/rest_client_zrok"
"github.com/openziti-test-kitchen/zrok/rest_client_zrok/share" "github.com/openziti-test-kitchen/zrok/rest_client_zrok/share"
@ -84,6 +86,7 @@ func (cmd *accessPrivateCommand) run(_ *cobra.Command, args []string) {
cfg := privateFrontend.DefaultConfig("backend") cfg := privateFrontend.DefaultConfig("backend")
cfg.ShrToken = shrToken cfg.ShrToken = shrToken
cfg.Address = cmd.bindAddress cfg.Address = cmd.bindAddress
cfg.RequestsChan = make(chan *endpoints.Request, 1024)
c := make(chan os.Signal) c := make(chan os.Signal)
signal.Notify(c, os.Interrupt, syscall.SIGTERM) signal.Notify(c, os.Interrupt, syscall.SIGTERM)
@ -101,13 +104,36 @@ func (cmd *accessPrivateCommand) run(_ *cobra.Command, args []string) {
panic(err) panic(err)
} }
logrus.Infof("access your share at: %v", endpointUrl.String()) go func() {
if err := frontend.Run(); err != nil {
if err := frontend.Run(); err != nil { if !panicInstead {
if !panicInstead { tui.Error("unable to run frontend", err)
tui.Error("unable to run frontend", err) }
} }
}()
mdl := newAccessModel(shrToken, endpointUrl.String())
logrus.SetOutput(mdl)
prg := tea.NewProgram(mdl, tea.WithAltScreen())
mdl.prg = prg
go func() {
for {
select {
case req := <-cfg.RequestsChan:
if req != nil {
prg.Send(req)
}
}
}
}()
if _, err := prg.Run(); err != nil {
tui.Error("An error occurred", err)
} }
close(cfg.RequestsChan)
cmd.destroy(accessResp.Payload.FrontendToken, zrd.Env.ZId, shrToken, zrok, auth)
} }
func (cmd *accessPrivateCommand) destroy(frotendName, envZId, shrToken string, zrok *rest_client_zrok.Zrok, auth runtime.ClientAuthInfoWriter) { func (cmd *accessPrivateCommand) destroy(frotendName, envZId, shrToken string, zrok *rest_client_zrok.Zrok, auth runtime.ClientAuthInfoWriter) {

202
cmd/zrok/accessTui.go Normal file
View File

@ -0,0 +1,202 @@
package main
import (
"fmt"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/muesli/reflow/wordwrap"
"github.com/openziti-test-kitchen/zrok/endpoints"
"strings"
"time"
)
const accessTuiBacklog = 256
type accessModel struct {
shrToken string
localEndpoint string
requests []*endpoints.Request
log []string
showLog bool
width int
height int
prg *tea.Program
}
type accessLogLine string
func newAccessModel(shrToken, localEndpoint string) *accessModel {
return &accessModel{
shrToken: shrToken,
localEndpoint: localEndpoint,
}
}
func (m *accessModel) Init() tea.Cmd { return nil }
func (m *accessModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case *endpoints.Request:
m.requests = append(m.requests, msg)
if len(m.requests) > accessTuiBacklog {
m.requests = m.requests[1:]
}
case accessLogLine:
m.showLog = true
m.adjustPaneHeights()
m.log = append(m.log, string(msg))
if len(m.log) > accessTuiBacklog {
m.log = m.log[1:]
}
case tea.WindowSizeMsg:
m.width = msg.Width
accessHeaderStyle.Width(m.width - 2)
accessRequestsStyle.Width(m.width - 2)
accessLogStyle.Width(m.width - 2)
m.height = msg.Height
m.adjustPaneHeights()
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "q":
return m, tea.Quit
case "ctrl+l":
return m, tea.ClearScreen
case "l":
m.showLog = !m.showLog
m.adjustPaneHeights()
}
}
return m, nil
}
func (m *accessModel) View() string {
var panes string
if m.showLog {
panes = lipgloss.JoinVertical(lipgloss.Left,
accessRequestsStyle.Render(m.renderRequests()),
accessLogStyle.Render(m.renderLog()),
)
} else {
panes = accessRequestsStyle.Render(m.renderRequests())
}
return lipgloss.JoinVertical(
lipgloss.Left,
accessHeaderStyle.Render(fmt.Sprintf("%v -> %v", m.localEndpoint, m.shrToken)),
panes,
)
}
func (m *accessModel) adjustPaneHeights() {
if !m.showLog {
accessRequestsStyle.Height(m.height - 5)
} else {
splitHeight := m.height - 5
accessRequestsStyle.Height(splitHeight/2 - 1)
accessLogStyle.Height(splitHeight/2 - 1)
}
}
func (m *accessModel) renderRequests() string {
var requestLines []string
for _, req := range m.requests {
reqLine := fmt.Sprintf("%v %v -> %v %v",
timeStyle.Render(req.Stamp.Format(time.RFC850)),
addressStyle.Render(req.RemoteAddr),
m.renderMethod(req.Method),
req.Path,
)
reqLineWrapped := wordwrap.String(reqLine, m.width-2)
splitWrapped := strings.Split(reqLineWrapped, "\n")
for _, splitLine := range splitWrapped {
splitLine := strings.ReplaceAll(splitLine, "\n", "")
if splitLine != "" {
requestLines = append(requestLines, splitLine)
}
}
}
maxRows := accessRequestsStyle.GetHeight()
startRow := 0
if len(requestLines) > maxRows {
startRow = len(requestLines) - maxRows
}
out := ""
for i := startRow; i < len(requestLines); i++ {
outLine := requestLines[i]
if i < len(requestLines)-1 {
outLine += "\n"
}
out += outLine
}
return out
}
func (m *accessModel) renderMethod(method string) string {
switch strings.ToLower(method) {
case "get":
return getStyle.Render(method)
case "post":
return postStyle.Render(method)
default:
return otherMethodStyle.Render(method)
}
}
func (m *accessModel) renderLog() string {
var splitLines []string
for _, line := range m.log {
wrapped := wordwrap.String(line, m.width-2)
wrappedLines := strings.Split(wrapped, "\n")
for _, wrappedLine := range wrappedLines {
splitLine := strings.ReplaceAll(wrappedLine, "\n", "")
if splitLine != "" {
splitLines = append(splitLines, splitLine)
}
}
}
maxRows := accessLogStyle.GetHeight()
startRow := 0
if len(splitLines) > maxRows {
startRow = len(splitLines) - maxRows
}
out := ""
for i := startRow; i < len(splitLines); i++ {
outLine := splitLines[i]
if i < len(splitLines)-1 {
outLine += "\n"
}
out += outLine
}
return out
}
func (m *accessModel) Write(p []byte) (n int, err error) {
in := string(p)
lines := strings.Split(in, "\n")
for _, line := range lines {
cleanLine := strings.ReplaceAll(line, "\n", "")
if cleanLine != "" {
m.prg.Send(accessLogLine(cleanLine))
}
}
return len(p), nil
}
var accessHeaderStyle = lipgloss.NewStyle().
BorderStyle(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("63")).
Align(lipgloss.Center)
var accessRequestsStyle = lipgloss.NewStyle().
BorderStyle(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("63"))
var accessLogStyle = lipgloss.NewStyle().
BorderStyle(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("63"))

View File

@ -70,5 +70,5 @@ func (cmd *disableCommand) run(_ *cobra.Command, _ []string) {
tui.Error("error removing zrok backend identity", err) tui.Error("error removing zrok backend identity", err)
} }
} }
fmt.Printf("zrok environment '%v' disabled for '%v'\n", zrd.Env.ZId, zrd.Env.Token) fmt.Println("zrok environment disabled...")
} }

View File

@ -2,6 +2,8 @@ package main
import ( import (
"fmt" "fmt"
"github.com/charmbracelet/bubbles/spinner"
tea "github.com/charmbracelet/bubbletea"
httptransport "github.com/go-openapi/runtime/client" httptransport "github.com/go-openapi/runtime/client"
"github.com/openziti-test-kitchen/zrok/rest_client_zrok/environment" "github.com/openziti-test-kitchen/zrok/rest_client_zrok/environment"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok" "github.com/openziti-test-kitchen/zrok/rest_model_zrok"
@ -9,6 +11,7 @@ import (
"github.com/openziti-test-kitchen/zrok/zrokdir" "github.com/openziti-test-kitchen/zrok/zrokdir"
"github.com/shirou/gopsutil/v3/host" "github.com/shirou/gopsutil/v3/host"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"os"
user2 "os/user" user2 "os/user"
) )
@ -52,7 +55,6 @@ func (cmd *enableCommand) run(_ *cobra.Command, args []string) {
if cmd.description == "<user>@<hostname>" { if cmd.description == "<user>@<hostname>" {
cmd.description = fmt.Sprintf("%v@%v", user.Username, hostName) cmd.description = fmt.Sprintf("%v@%v", user.Username, hostName)
} }
zrok, err := zrd.Client() zrok, err := zrd.Client()
if err != nil { if err != nil {
panic(err) panic(err)
@ -63,29 +65,49 @@ func (cmd *enableCommand) run(_ *cobra.Command, args []string) {
Description: cmd.description, Description: cmd.description,
Host: hostDetail, Host: hostDetail,
} }
var prg *tea.Program
var mdl enableTuiModel
var done = make(chan struct{})
go func() {
mdl = newEnableTuiModel()
mdl.msg = "contacting the zrok service..."
prg = tea.NewProgram(mdl)
if _, err := prg.Run(); err != nil {
fmt.Println(err)
}
close(done)
if mdl.quitting {
os.Exit(1)
}
}()
resp, err := zrok.Environment.Enable(req, auth) resp, err := zrok.Environment.Enable(req, auth)
if err != nil { if err != nil {
if !panicInstead { prg.Send(fmt.Sprintf("the zrok service returned an error: %v", err))
tui.Error("the zrok service returned an error", err) prg.Quit()
} <-done
panic(err) os.Exit(1)
} }
prg.Send("writing the environment details...")
apiEndpoint, _ := zrd.ApiEndpoint() apiEndpoint, _ := zrd.ApiEndpoint()
zrd.Env = &zrokdir.Environment{Token: token, ZId: resp.Payload.Identity, ApiEndpoint: apiEndpoint} zrd.Env = &zrokdir.Environment{Token: token, ZId: resp.Payload.Identity, ApiEndpoint: apiEndpoint}
if err := zrd.Save(); err != nil { if err := zrd.Save(); err != nil {
if !panicInstead { prg.Send(fmt.Sprintf("there was an error saving the new environment: %v", err))
tui.Error("there was an error saving the new environment", err) prg.Quit()
} <-done
panic(err) os.Exit(1)
} }
if err := zrokdir.SaveZitiIdentity("backend", resp.Payload.Cfg); err != nil { if err := zrokdir.SaveZitiIdentity("backend", resp.Payload.Cfg); err != nil {
if !panicInstead { prg.Send(fmt.Sprintf("there was an error writing the environment: %v", err))
tui.Error("there was an error writing the environment file", err) prg.Quit()
} <-done
panic(err) os.Exit(1)
} }
fmt.Printf("zrok environment '%v' enabled for '%v'\n", resp.Payload.Identity, token) prg.Send(fmt.Sprintf("the zrok environment was successfully enabled..."))
prg.Quit()
<-done
} }
func getHost() (string, string, error) { func getHost() (string, string, error) {
@ -97,3 +119,52 @@ func getHost() (string, string, error) {
info.Hostname, info.OS, info.Platform, info.PlatformFamily, info.PlatformVersion, info.KernelVersion, info.KernelArch) info.Hostname, info.OS, info.Platform, info.PlatformFamily, info.PlatformVersion, info.KernelVersion, info.KernelArch)
return info.Hostname, thisHost, nil return info.Hostname, thisHost, nil
} }
type enableTuiModel struct {
spinner spinner.Model
msg string
quitting bool
}
func newEnableTuiModel() enableTuiModel {
s := spinner.New()
s.Spinner = spinner.Dot
s.Style = tui.WarningStyle
return enableTuiModel{spinner: s}
}
func (m enableTuiModel) Init() tea.Cmd { return m.spinner.Tick }
func (m enableTuiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case string:
m.msg = msg
return m, nil
case tea.KeyMsg:
switch msg.String() {
case "q", "esc", "ctrl+c":
m.quitting = true
return m, tea.Quit
default:
return m, nil
}
case struct{}:
return m, tea.Quit
default:
var cmd tea.Cmd
m.spinner, cmd = m.spinner.Update(msg)
return m, cmd
}
}
func (m enableTuiModel) View() string {
str := fmt.Sprintf("%s %s\n", m.spinner.View(), m.msg)
if m.quitting {
return str
}
return str
}

View File

@ -2,13 +2,17 @@ package main
import ( import (
"fmt" "fmt"
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/openziti-test-kitchen/zrok/rest_client_zrok/account" "github.com/openziti-test-kitchen/zrok/rest_client_zrok/account"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok" "github.com/openziti-test-kitchen/zrok/rest_model_zrok"
"github.com/openziti-test-kitchen/zrok/tui" "github.com/openziti-test-kitchen/zrok/tui"
"github.com/openziti-test-kitchen/zrok/util" "github.com/openziti-test-kitchen/zrok/util"
"github.com/openziti-test-kitchen/zrok/zrokdir" "github.com/openziti-test-kitchen/zrok/zrokdir"
"github.com/openziti/foundation/v2/term"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"os"
"strings"
) )
func init() { func init() {
@ -17,6 +21,7 @@ func init() {
type inviteCommand struct { type inviteCommand struct {
cmd *cobra.Command cmd *cobra.Command
tui inviteTui
} }
func newInviteCommand() *inviteCommand { func newInviteCommand() *inviteCommand {
@ -25,50 +30,188 @@ func newInviteCommand() *inviteCommand {
Short: "Invite a new user to zrok", Short: "Invite a new user to zrok",
Args: cobra.ExactArgs(0), Args: cobra.ExactArgs(0),
} }
command := &inviteCommand{cmd: cmd} command := &inviteCommand{
cmd: cmd,
tui: newInviteTui(),
}
cmd.Run = command.run cmd.Run = command.run
return command return command
} }
func (cmd *inviteCommand) run(_ *cobra.Command, _ []string) { func (cmd *inviteCommand) run(_ *cobra.Command, _ []string) {
email, err := term.Prompt("New Email: ") if _, err := tea.NewProgram(&cmd.tui).Run(); err != nil {
if err != nil { tui.Error("unable to run interface", err)
panic(err) os.Exit(1)
}
if !util.IsValidEmail(email) {
tui.Error(fmt.Sprintf("'%v' is not a valid email address", email), nil)
}
confirm, err := term.Prompt("Confirm Email: ")
if err != nil {
panic(err)
}
if confirm != email {
tui.Error("entered emails do not match... aborting!", nil)
} }
if cmd.tui.done {
email := cmd.tui.inputs[0].Value()
zrd, err := zrokdir.Load() zrd, err := zrokdir.Load()
if err != nil { if err != nil {
tui.Error("error loading zrokdir", err) tui.Error("error loading zrokdir", err)
}
zrok, err := zrd.Client()
if err != nil {
if !panicInstead {
tui.Error("error creating zrok api client", err)
} }
panic(err)
}
req := account.NewInviteParams()
req.Body = &rest_model_zrok.InviteRequest{
Email: email,
}
_, err = zrok.Account.Invite(req)
if err != nil {
if !panicInstead {
tui.Error("error creating invitation", err)
}
panic(err)
}
fmt.Printf("invitation sent to '%v'!\n", email) zrok, err := zrd.Client()
if err != nil {
if !panicInstead {
tui.Error("error creating zrok api client", err)
}
panic(err)
}
req := account.NewInviteParams()
req.Body = &rest_model_zrok.InviteRequest{
Email: email,
}
_, err = zrok.Account.Invite(req)
if err != nil {
if !panicInstead {
tui.Error("error creating invitation", err)
}
panic(err)
}
fmt.Printf("invitation sent to '%v'!\n", email)
}
}
type inviteTui struct {
focusIndex int
msg string
inputs []textinput.Model
cursorMode textinput.CursorMode
done bool
msgOk string
msgMismatch string
focusedStyle lipgloss.Style
blurredStyle lipgloss.Style
errorStyle lipgloss.Style
cursorStyle lipgloss.Style
noStyle lipgloss.Style
helpStyle lipgloss.Style
focusedButton string
blurredButton string
}
func newInviteTui() inviteTui {
m := inviteTui{
inputs: make([]textinput.Model, 2),
}
m.focusedStyle = tui.WarningStyle
m.blurredStyle = tui.CodeStyle
m.errorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#F00"))
m.cursorStyle = m.focusedStyle.Copy()
m.noStyle = lipgloss.NewStyle()
m.helpStyle = m.blurredStyle.Copy()
m.focusedButton = m.focusedStyle.Copy().Render("[ Submit ]")
m.blurredButton = fmt.Sprintf("[ %v ]", m.blurredStyle.Render("Submit"))
m.msgOk = m.noStyle.Render("enter and confirm your email address...")
m.msg = m.msgOk
m.msgMismatch = m.errorStyle.Render("email is invalid or does not match confirmation...")
var t textinput.Model
for i := range m.inputs {
t = textinput.New()
t.CursorStyle = m.cursorStyle
t.CharLimit = 96
switch i {
case 0:
t.Placeholder = "Email Address"
t.Focus()
t.PromptStyle = m.focusedStyle
t.TextStyle = m.focusedStyle
case 1:
t.Placeholder = "Confirm Email"
}
m.inputs[i] = t
}
return m
}
func (m inviteTui) Init() tea.Cmd { return textinput.Blink }
func (m *inviteTui) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "esc":
return m, tea.Quit
case "tab", "shift+tab", "enter", "up", "down":
s := msg.String()
if s == "enter" && m.focusIndex == len(m.inputs) {
if util.IsValidEmail(m.inputs[0].Value()) && m.inputs[0].Value() == m.inputs[1].Value() {
m.done = true
return m, tea.Quit
}
m.msg = m.msgMismatch
return m, nil
}
if s == "up" || s == "shift+tab" {
m.msg = m.msgOk
m.focusIndex--
} else {
m.msg = m.msgOk
m.focusIndex++
}
if m.focusIndex > len(m.inputs) {
m.focusIndex = 0
} else if m.focusIndex < 0 {
m.focusIndex = len(m.inputs)
}
cmds := make([]tea.Cmd, len(m.inputs))
for i := 0; i <= len(m.inputs)-1; i++ {
if i == m.focusIndex {
cmds[i] = m.inputs[i].Focus()
m.inputs[i].PromptStyle = m.focusedStyle
m.inputs[i].TextStyle = m.focusedStyle
continue
}
m.inputs[i].Blur()
m.inputs[i].PromptStyle = m.noStyle
m.inputs[i].TextStyle = m.noStyle
}
return m, tea.Batch(cmds...)
}
}
cmd := m.updateInputs(msg)
return m, cmd
}
func (m *inviteTui) updateInputs(msg tea.Msg) tea.Cmd {
cmds := make([]tea.Cmd, len(m.inputs))
for i := range m.inputs {
m.inputs[i], cmds[i] = m.inputs[i].Update(msg)
}
return tea.Batch(cmds...)
}
func (m inviteTui) View() string {
var b strings.Builder
b.WriteString(fmt.Sprintf("\n%v\n\n", m.msg))
for i := range m.inputs {
b.WriteString(m.inputs[i].View())
if i < len(m.inputs)-1 {
b.WriteRune('\n')
}
}
button := &m.blurredButton
if m.focusIndex == len(m.inputs) {
button = &m.focusedButton
}
_, _ = fmt.Fprintf(&b, "\n\n%s\n\n", *button)
return b.String()
} }

View File

@ -21,18 +21,20 @@ func init() {
type reserveCommand struct { type reserveCommand struct {
basicAuth []string basicAuth []string
frontendSelection []string frontendSelection []string
backendMode string
cmd *cobra.Command cmd *cobra.Command
} }
func newReserveCommand() *reserveCommand { func newReserveCommand() *reserveCommand {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "reserve <public|private> <targetEndpoint>", Use: "reserve <public|private> <target>",
Short: "Create a reserved share", Short: "Create a reserved share",
Args: cobra.ExactArgs(2), Args: cobra.ExactArgs(2),
} }
command := &reserveCommand{cmd: cmd} command := &reserveCommand{cmd: cmd}
cmd.Flags().StringArrayVar(&command.basicAuth, "basic-auth", []string{}, "Basic authentication users (<username:password>,...)") cmd.Flags().StringArrayVar(&command.basicAuth, "basic-auth", []string{}, "Basic authentication users (<username:password>,...)")
cmd.Flags().StringArrayVar(&command.frontendSelection, "frontends", []string{"public"}, "Selected frontends to use for the share") cmd.Flags().StringArrayVar(&command.frontendSelection, "frontends", []string{"public"}, "Selected frontends to use for the share")
cmd.Flags().StringVar(&command.backendMode, "backend-mode", "proxy", "The backend mode {proxy, web}")
cmd.Run = command.run cmd.Run = command.run
return command return command
} }
@ -43,15 +45,23 @@ func (cmd *reserveCommand) run(_ *cobra.Command, args []string) {
tui.Error("invalid sharing mode; expecting 'public' or 'private'", nil) tui.Error("invalid sharing mode; expecting 'public' or 'private'", nil)
} }
targetEndpoint, err := url.Parse(args[1]) var target string
if err != nil { switch cmd.backendMode {
if !panicInstead { case "proxy":
tui.Error("invalid target endpoint URL", err) targetEndpoint, err := url.Parse(args[1])
if err != nil {
if !panicInstead {
tui.Error("invalid target endpoint URL", err)
}
panic(err)
} }
panic(err) if targetEndpoint.Scheme == "" {
} targetEndpoint.Scheme = "https"
if targetEndpoint.Scheme == "" { }
targetEndpoint.Scheme = "https" target = targetEndpoint.String()
case "web":
target = args[1]
} }
zrd, err := zrokdir.Load() zrd, err := zrokdir.Load()
@ -78,8 +88,8 @@ func (cmd *reserveCommand) run(_ *cobra.Command, args []string) {
req.Body = &rest_model_zrok.ShareRequest{ req.Body = &rest_model_zrok.ShareRequest{
EnvZID: zrd.Env.ZId, EnvZID: zrd.Env.ZId,
ShareMode: shareMode, ShareMode: shareMode,
BackendMode: "proxy", BackendMode: cmd.backendMode,
BackendProxyEndpoint: targetEndpoint.String(), BackendProxyEndpoint: target,
AuthScheme: string(model.None), AuthScheme: string(model.None),
Reserved: true, Reserved: true,
} }

View File

@ -2,8 +2,10 @@ package main
import ( import (
"fmt" "fmt"
tea "github.com/charmbracelet/bubbletea"
"github.com/go-openapi/runtime" "github.com/go-openapi/runtime"
httptransport "github.com/go-openapi/runtime/client" httptransport "github.com/go-openapi/runtime/client"
"github.com/openziti-test-kitchen/zrok/endpoints"
"github.com/openziti-test-kitchen/zrok/endpoints/proxyBackend" "github.com/openziti-test-kitchen/zrok/endpoints/proxyBackend"
"github.com/openziti-test-kitchen/zrok/endpoints/webBackend" "github.com/openziti-test-kitchen/zrok/endpoints/webBackend"
"github.com/openziti-test-kitchen/zrok/model" "github.com/openziti-test-kitchen/zrok/model"
@ -20,7 +22,6 @@ import (
"os/signal" "os/signal"
"strings" "strings"
"syscall" "syscall"
"time"
) )
func init() { func init() {
@ -30,18 +31,20 @@ func init() {
type sharePrivateCommand struct { type sharePrivateCommand struct {
basicAuth []string basicAuth []string
backendMode string backendMode string
headless bool
cmd *cobra.Command cmd *cobra.Command
} }
func newSharePrivateCommand() *sharePrivateCommand { func newSharePrivateCommand() *sharePrivateCommand {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "private <targetEndpoint>", Use: "private <target>",
Short: "Share a target endpoint privately", Short: "Share a target resource privately",
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
} }
command := &sharePrivateCommand{cmd: cmd} command := &sharePrivateCommand{cmd: cmd}
cmd.Flags().StringArrayVar(&command.basicAuth, "basic-auth", []string{}, "Basic authentication users (<username:password>,...") cmd.Flags().StringArrayVar(&command.basicAuth, "basic-auth", []string{}, "Basic authentication users (<username:password>,...")
cmd.Flags().StringVar(&command.backendMode, "backend-mode", "proxy", "The backend mode {proxy, web}") cmd.Flags().StringVar(&command.backendMode, "backend-mode", "proxy", "The backend mode {proxy, web}")
cmd.Flags().BoolVar(&command.headless, "headless", false, "Disable TUI and run headless")
cmd.Run = command.run cmd.Run = command.run
return command return command
} }
@ -135,12 +138,14 @@ func (cmd *sharePrivateCommand) run(_ *cobra.Command, args []string) {
os.Exit(0) os.Exit(0)
}() }()
requestsChan := make(chan *endpoints.Request, 1024)
switch cmd.backendMode { switch cmd.backendMode {
case "proxy": case "proxy":
cfg := &proxyBackend.Config{ cfg := &proxyBackend.Config{
IdentityPath: zif, IdentityPath: zif,
EndpointAddress: target, EndpointAddress: target,
ShrToken: resp.Payload.ShrToken, ShrToken: resp.Payload.ShrToken,
RequestsChan: requestsChan,
} }
_, err = cmd.proxyBackendMode(cfg) _, err = cmd.proxyBackendMode(cfg)
if err != nil { if err != nil {
@ -155,6 +160,7 @@ func (cmd *sharePrivateCommand) run(_ *cobra.Command, args []string) {
IdentityPath: zif, IdentityPath: zif,
WebRoot: target, WebRoot: target,
ShrToken: resp.Payload.ShrToken, ShrToken: resp.Payload.ShrToken,
RequestsChan: requestsChan,
} }
_, err = cmd.webBackendMode(cfg) _, err = cmd.webBackendMode(cfg)
if err != nil { if err != nil {
@ -168,14 +174,41 @@ func (cmd *sharePrivateCommand) run(_ *cobra.Command, args []string) {
tui.Error("invalid backend mode", nil) tui.Error("invalid backend mode", nil)
} }
logrus.Infof("share with others; they will use this command for access: 'zrok access private %v'", resp.Payload.ShrToken) if cmd.headless {
logrus.Infof("allow other to access your share with the following command:\nzrok access private %v", resp.Payload.ShrToken)
for {
select {
case req := <-requestsChan:
logrus.Infof("%v -> %v %v", req.RemoteAddr, req.Method, req.Path)
}
}
for { } else {
time.Sleep(30 * time.Second) shareDescription := fmt.Sprintf("access your share with: %v", tui.CodeStyle.Render(fmt.Sprintf("zrok access private %v", resp.Payload.ShrToken)))
mdl := newShareModel(resp.Payload.ShrToken, []string{shareDescription}, "private", cmd.backendMode)
logrus.SetOutput(mdl)
prg := tea.NewProgram(mdl, tea.WithAltScreen())
mdl.prg = prg
go func() {
for {
select {
case req := <-requestsChan:
prg.Send(req)
}
}
}()
if _, err := prg.Run(); err != nil {
tui.Error("An error occurred", err)
}
close(requestsChan)
cmd.destroy(zrd.Env.ZId, resp.Payload.ShrToken, zrok, auth)
} }
} }
func (cmd *sharePrivateCommand) proxyBackendMode(cfg *proxyBackend.Config) (backendHandler, error) { func (cmd *sharePrivateCommand) proxyBackendMode(cfg *proxyBackend.Config) (endpoints.RequestHandler, error) {
be, err := proxyBackend.NewBackend(cfg) be, err := proxyBackend.NewBackend(cfg)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "error creating http proxy backend") return nil, errors.Wrap(err, "error creating http proxy backend")
@ -190,7 +223,7 @@ func (cmd *sharePrivateCommand) proxyBackendMode(cfg *proxyBackend.Config) (back
return be, nil return be, nil
} }
func (cmd *sharePrivateCommand) webBackendMode(cfg *webBackend.Config) (backendHandler, error) { func (cmd *sharePrivateCommand) webBackendMode(cfg *webBackend.Config) (endpoints.RequestHandler, error) {
be, err := webBackend.NewBackend(cfg) be, err := webBackend.NewBackend(cfg)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "error creating http web backend") return nil, errors.Wrap(err, "error creating http web backend")

View File

@ -2,8 +2,10 @@ package main
import ( import (
"fmt" "fmt"
tea "github.com/charmbracelet/bubbletea"
"github.com/go-openapi/runtime" "github.com/go-openapi/runtime"
httptransport "github.com/go-openapi/runtime/client" httptransport "github.com/go-openapi/runtime/client"
"github.com/openziti-test-kitchen/zrok/endpoints"
"github.com/openziti-test-kitchen/zrok/endpoints/proxyBackend" "github.com/openziti-test-kitchen/zrok/endpoints/proxyBackend"
"github.com/openziti-test-kitchen/zrok/endpoints/webBackend" "github.com/openziti-test-kitchen/zrok/endpoints/webBackend"
"github.com/openziti-test-kitchen/zrok/model" "github.com/openziti-test-kitchen/zrok/model"
@ -20,7 +22,6 @@ import (
"os/signal" "os/signal"
"strings" "strings"
"syscall" "syscall"
"time"
) )
func init() { func init() {
@ -31,6 +32,7 @@ type sharePublicCommand struct {
basicAuth []string basicAuth []string
frontendSelection []string frontendSelection []string
backendMode string backendMode string
headless bool
cmd *cobra.Command cmd *cobra.Command
} }
@ -44,6 +46,7 @@ func newSharePublicCommand() *sharePublicCommand {
cmd.Flags().StringArrayVar(&command.basicAuth, "basic-auth", []string{}, "Basic authentication users (<username:password>,...)") cmd.Flags().StringArrayVar(&command.basicAuth, "basic-auth", []string{}, "Basic authentication users (<username:password>,...)")
cmd.Flags().StringArrayVar(&command.frontendSelection, "frontends", []string{"public"}, "Selected frontends to use for the share") cmd.Flags().StringArrayVar(&command.frontendSelection, "frontends", []string{"public"}, "Selected frontends to use for the share")
cmd.Flags().StringVar(&command.backendMode, "backend-mode", "proxy", "The backend mode {proxy, web}") cmd.Flags().StringVar(&command.backendMode, "backend-mode", "proxy", "The backend mode {proxy, web}")
cmd.Flags().BoolVar(&command.headless, "headless", false, "Disable TUI and run headless")
cmd.Run = command.run cmd.Run = command.run
return command return command
} }
@ -75,7 +78,7 @@ func (cmd *sharePublicCommand) run(_ *cobra.Command, args []string) {
zrd, err := zrokdir.Load() zrd, err := zrokdir.Load()
if err != nil { if err != nil {
if !panicInstead { if !panicInstead {
tui.Error("unable to load zrokdir", nil) tui.Error("unable to load zrokdir", err)
} }
panic(err) panic(err)
} }
@ -99,6 +102,7 @@ func (cmd *sharePublicCommand) run(_ *cobra.Command, args []string) {
} }
panic(err) panic(err)
} }
auth := httptransport.APIKeyAuth("X-TOKEN", "header", zrd.Env.Token) auth := httptransport.APIKeyAuth("X-TOKEN", "header", zrd.Env.Token)
req := share.NewShareParams() req := share.NewShareParams()
req.Body = &rest_model_zrok.ShareRequest{ req.Body = &rest_model_zrok.ShareRequest{
@ -124,7 +128,7 @@ func (cmd *sharePublicCommand) run(_ *cobra.Command, args []string) {
resp, err := zrok.Share.Share(req, auth) resp, err := zrok.Share.Share(req, auth)
if err != nil { if err != nil {
if !panicInstead { if !panicInstead {
tui.Error("unable to create tunnel", err) tui.Error("unable to create share", err)
} }
panic(err) panic(err)
} }
@ -137,15 +141,16 @@ func (cmd *sharePublicCommand) run(_ *cobra.Command, args []string) {
os.Exit(0) os.Exit(0)
}() }()
var bh backendHandler requestsChan := make(chan *endpoints.Request, 1024)
switch cmd.backendMode { switch cmd.backendMode {
case "proxy": case "proxy":
cfg := &proxyBackend.Config{ cfg := &proxyBackend.Config{
IdentityPath: zif, IdentityPath: zif,
EndpointAddress: target, EndpointAddress: target,
ShrToken: resp.Payload.ShrToken, ShrToken: resp.Payload.ShrToken,
RequestsChan: requestsChan,
} }
bh, err = cmd.proxyBackendMode(cfg) _, err = cmd.proxyBackendMode(cfg)
if err != nil { if err != nil {
if !panicInstead { if !panicInstead {
tui.Error("unable to create proxy backend handler", err) tui.Error("unable to create proxy backend handler", err)
@ -158,8 +163,9 @@ func (cmd *sharePublicCommand) run(_ *cobra.Command, args []string) {
IdentityPath: zif, IdentityPath: zif,
WebRoot: target, WebRoot: target,
ShrToken: resp.Payload.ShrToken, ShrToken: resp.Payload.ShrToken,
RequestsChan: requestsChan,
} }
bh, err = cmd.webBackendMode(cfg) _, err = cmd.webBackendMode(cfg)
if err != nil { if err != nil {
if !panicInstead { if !panicInstead {
tui.Error("unable to create web backend handler", err) tui.Error("unable to create web backend handler", err)
@ -171,14 +177,40 @@ func (cmd *sharePublicCommand) run(_ *cobra.Command, args []string) {
tui.Error("invalid backend mode", nil) tui.Error("invalid backend mode", nil)
} }
logrus.Infof("access your zrok share: %v", resp.Payload.FrontendProxyEndpoints[0]) if cmd.headless {
for { logrus.Infof("access your zrok share at the following endpoints:\n %v", strings.Join(resp.Payload.FrontendProxyEndpoints, "\n"))
time.Sleep(5 * time.Second) for {
logrus.Infof("requests: %d", bh.Requests()()) select {
case req := <-requestsChan:
logrus.Infof("%v -> %v %v", req.RemoteAddr, req.Method, req.Path)
}
}
} else {
mdl := newShareModel(resp.Payload.ShrToken, resp.Payload.FrontendProxyEndpoints, "public", cmd.backendMode)
logrus.SetOutput(mdl)
prg := tea.NewProgram(mdl, tea.WithAltScreen())
mdl.prg = prg
go func() {
for {
select {
case req := <-requestsChan:
prg.Send(req)
}
}
}()
if _, err := prg.Run(); err != nil {
tui.Error("An error occurred", err)
}
close(requestsChan)
cmd.destroy(zrd.Env.ZId, resp.Payload.ShrToken, zrok, auth)
} }
} }
func (cmd *sharePublicCommand) proxyBackendMode(cfg *proxyBackend.Config) (backendHandler, error) { func (cmd *sharePublicCommand) proxyBackendMode(cfg *proxyBackend.Config) (endpoints.RequestHandler, error) {
be, err := proxyBackend.NewBackend(cfg) be, err := proxyBackend.NewBackend(cfg)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "error creating http proxy backend") return nil, errors.Wrap(err, "error creating http proxy backend")
@ -193,7 +225,7 @@ func (cmd *sharePublicCommand) proxyBackendMode(cfg *proxyBackend.Config) (backe
return be, nil return be, nil
} }
func (cmd *sharePublicCommand) webBackendMode(cfg *webBackend.Config) (backendHandler, error) { func (cmd *sharePublicCommand) webBackendMode(cfg *webBackend.Config) (endpoints.RequestHandler, error) {
be, err := webBackend.NewBackend(cfg) be, err := webBackend.NewBackend(cfg)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "error creating http web backend") return nil, errors.Wrap(err, "error creating http web backend")

View File

@ -1,17 +1,20 @@
package main package main
import ( import (
"fmt"
tea "github.com/charmbracelet/bubbletea"
httptransport "github.com/go-openapi/runtime/client" httptransport "github.com/go-openapi/runtime/client"
"github.com/openziti-test-kitchen/zrok/endpoints"
"github.com/openziti-test-kitchen/zrok/endpoints/proxyBackend" "github.com/openziti-test-kitchen/zrok/endpoints/proxyBackend"
"github.com/openziti-test-kitchen/zrok/endpoints/webBackend"
"github.com/openziti-test-kitchen/zrok/rest_client_zrok/metadata" "github.com/openziti-test-kitchen/zrok/rest_client_zrok/metadata"
"github.com/openziti-test-kitchen/zrok/rest_client_zrok/share" "github.com/openziti-test-kitchen/zrok/rest_client_zrok/share"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok" "github.com/openziti-test-kitchen/zrok/rest_model_zrok"
"github.com/openziti-test-kitchen/zrok/tui" "github.com/openziti-test-kitchen/zrok/tui"
"github.com/openziti-test-kitchen/zrok/zrokdir" "github.com/openziti-test-kitchen/zrok/zrokdir"
"github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"net/url"
"time"
) )
func init() { func init() {
@ -20,6 +23,7 @@ func init() {
type shareReservedCommand struct { type shareReservedCommand struct {
overrideEndpoint string overrideEndpoint string
headless bool
cmd *cobra.Command cmd *cobra.Command
} }
@ -30,26 +34,14 @@ func newShareReservedCommand() *shareReservedCommand {
} }
command := &shareReservedCommand{cmd: cmd} command := &shareReservedCommand{cmd: cmd}
cmd.Flags().StringVar(&command.overrideEndpoint, "override-endpoint", "", "Override the stored target endpoint with a replacement") cmd.Flags().StringVar(&command.overrideEndpoint, "override-endpoint", "", "Override the stored target endpoint with a replacement")
cmd.Flags().BoolVar(&command.headless, "headless", false, "Disable TUI and run headless")
cmd.Run = command.run cmd.Run = command.run
return command return command
} }
func (cmd *shareReservedCommand) run(_ *cobra.Command, args []string) { func (cmd *shareReservedCommand) run(_ *cobra.Command, args []string) {
shrToken := args[0] shrToken := args[0]
targetEndpoint := "" var target string
if cmd.overrideEndpoint != "" {
e, err := url.Parse(cmd.overrideEndpoint)
if err != nil {
if !panicInstead {
tui.Error("invalid override endpoint URL", err)
}
panic(err)
}
if e.Scheme == "" {
e.Scheme = "https"
}
targetEndpoint = e.String()
}
zrd, err := zrokdir.Load() zrd, err := zrokdir.Load()
if err != nil { if err != nil {
@ -80,8 +72,8 @@ func (cmd *shareReservedCommand) run(_ *cobra.Command, args []string) {
} }
panic(err) panic(err)
} }
if targetEndpoint == "" { if target == "" {
targetEndpoint = resp.Payload.BackendProxyEndpoint target = resp.Payload.BackendProxyEndpoint
} }
zif, err := zrokdir.ZitiIdentityFile("backend") zif, err := zrokdir.ZitiIdentityFile("backend")
@ -91,18 +83,14 @@ func (cmd *shareReservedCommand) run(_ *cobra.Command, args []string) {
} }
panic(err) panic(err)
} }
cfg := &proxyBackend.Config{
IdentityPath: zif,
EndpointAddress: targetEndpoint,
ShrToken: shrToken,
}
logrus.Infof("sharing target endpoint: '%v'", cfg.EndpointAddress)
if resp.Payload.BackendProxyEndpoint != targetEndpoint { logrus.Infof("sharing target: '%v'", target)
if resp.Payload.BackendProxyEndpoint != target {
upReq := share.NewUpdateShareParams() upReq := share.NewUpdateShareParams()
upReq.Body = &rest_model_zrok.UpdateShareRequest{ upReq.Body = &rest_model_zrok.UpdateShareRequest{
ShrToken: shrToken, ShrToken: shrToken,
BackendProxyEndpoint: targetEndpoint, BackendProxyEndpoint: target,
} }
if _, err := zrok.Share.UpdateShare(upReq, auth); err != nil { if _, err := zrok.Share.UpdateShare(upReq, auth); err != nil {
if !panicInstead { if !panicInstead {
@ -110,37 +98,118 @@ func (cmd *shareReservedCommand) run(_ *cobra.Command, args []string) {
} }
panic(err) panic(err)
} }
logrus.Infof("updated backend proxy endpoint to: %v", targetEndpoint) logrus.Infof("updated backend proxy endpoint to: %v", target)
} else { } else {
logrus.Infof("using existing backend proxy endpoint: %v", targetEndpoint) logrus.Infof("using existing backend proxy endpoint: %v", target)
} }
httpProxy, err := proxyBackend.NewBackend(cfg) requestsChan := make(chan *endpoints.Request, 1024)
if err != nil { switch resp.Payload.BackendMode {
if !panicInstead { case "proxy":
tui.Error("unable to create http backend", err) cfg := &proxyBackend.Config{
IdentityPath: zif,
EndpointAddress: target,
ShrToken: shrToken,
RequestsChan: requestsChan,
} }
panic(err) _, err := cmd.proxyBackendMode(cfg)
} if err != nil {
go func() {
if err := httpProxy.Run(); err != nil {
if !panicInstead { if !panicInstead {
tui.Error("unable to run http proxy", err) tui.Error("unable to create proxy backend handler", err)
} }
panic(err) panic(err)
} }
}()
switch resp.Payload.ShareMode { case "web":
case "public": cfg := &webBackend.Config{
logrus.Infof("access your zrok share: %v", resp.Payload.FrontendEndpoint) IdentityPath: zif,
WebRoot: target,
ShrToken: shrToken,
RequestsChan: requestsChan,
}
_, err := cmd.webBackendMode(cfg)
if err != nil {
if !panicInstead {
tui.Error("unable to create web backend handler", err)
}
panic(err)
}
case "private": default:
logrus.Infof("use this command to access your zrok share: 'zrok access private %v'", shrToken) tui.Error("invalid backend mode", nil)
} }
for { if cmd.headless {
time.Sleep(30 * time.Second) switch resp.Payload.ShareMode {
case "public":
logrus.Infof("access your zrok share: %v", resp.Payload.FrontendEndpoint)
case "private":
logrus.Infof("use this command to access your zrok share: 'zrok access private %v'", shrToken)
}
for {
select {
case req := <-requestsChan:
logrus.Infof("%v -> %v %v", req.RemoteAddr, req.Method, req.Path)
}
}
} else {
var shareDescription string
switch resp.Payload.ShareMode {
case "public":
shareDescription = resp.Payload.FrontendEndpoint
case "private":
shareDescription = fmt.Sprintf("access your share with: %v", tui.CodeStyle.Render(fmt.Sprintf("zrok access private %v", shrToken)))
}
mdl := newShareModel(shrToken, []string{shareDescription}, resp.Payload.ShareMode, resp.Payload.BackendMode)
logrus.SetOutput(mdl)
prg := tea.NewProgram(mdl, tea.WithAltScreen())
mdl.prg = prg
go func() {
for {
select {
case req := <-requestsChan:
prg.Send(req)
}
}
}()
if _, err := prg.Run(); err != nil {
tui.Error("An error occurred", err)
}
close(requestsChan)
} }
} }
func (cmd *shareReservedCommand) proxyBackendMode(cfg *proxyBackend.Config) (endpoints.RequestHandler, error) {
be, err := proxyBackend.NewBackend(cfg)
if err != nil {
return nil, errors.Wrap(err, "error creating http proxy backend")
}
go func() {
if err := be.Run(); err != nil {
logrus.Errorf("error running http proxy backend: %v", err)
}
}()
return be, nil
}
func (cmd *shareReservedCommand) webBackendMode(cfg *webBackend.Config) (endpoints.RequestHandler, error) {
be, err := webBackend.NewBackend(cfg)
if err != nil {
return nil, errors.Wrap(err, "error creating http web backend")
}
go func() {
if err := be.Run(); err != nil {
logrus.Errorf("error running http web backend: %v", err)
}
}()
return be, nil
}

240
cmd/zrok/shareTui.go Normal file
View File

@ -0,0 +1,240 @@
package main
import (
"fmt"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/muesli/reflow/wordwrap"
"github.com/openziti-test-kitchen/zrok/endpoints"
"strings"
"time"
)
const shareTuiBacklog = 256
type shareModel struct {
shrToken string
frontendDescriptions []string
shareMode string
backendMode string
requests []*endpoints.Request
log []string
showLog bool
width int
height int
headerHeight int
prg *tea.Program
}
type shareLogLine string
func newShareModel(shrToken string, frontendEndpoints []string, shareMode, backendMode string) *shareModel {
return &shareModel{
shrToken: shrToken,
frontendDescriptions: frontendEndpoints,
shareMode: shareMode,
backendMode: backendMode,
}
}
func (m *shareModel) Init() tea.Cmd { return nil }
func (m *shareModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case *endpoints.Request:
m.requests = append(m.requests, msg)
if len(m.requests) > shareTuiBacklog {
m.requests = m.requests[1:]
}
case shareLogLine:
m.showLog = true
m.adjustPaneHeights()
m.log = append(m.log, string(msg))
if len(m.log) > shareTuiBacklog {
m.log = m.log[1:]
}
case tea.WindowSizeMsg:
m.width = msg.Width
shareHeaderStyle.Width(m.width - 30)
shareConfigStyle.Width(26)
shareRequestsStyle.Width(m.width - 2)
shareLogStyle.Width(m.width - 2)
m.height = msg.Height
m.headerHeight = len(m.frontendDescriptions) + 4
m.adjustPaneHeights()
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "q":
return m, tea.Quit
case "ctrl+l":
return m, tea.ClearScreen
case "l":
m.showLog = !m.showLog
m.adjustPaneHeights()
}
}
return m, nil
}
func (m *shareModel) View() string {
topRow := lipgloss.JoinHorizontal(lipgloss.Top,
shareHeaderStyle.Render(strings.Join(m.frontendDescriptions, "\n")),
shareConfigStyle.Render(m.renderConfig()),
)
var panes string
if m.showLog {
panes = lipgloss.JoinVertical(lipgloss.Left,
shareRequestsStyle.Render(m.renderRequests()),
shareLogStyle.Render(m.renderLog()),
)
} else {
panes = shareRequestsStyle.Render(m.renderRequests())
}
return lipgloss.JoinVertical(lipgloss.Left, topRow, panes)
}
func (m *shareModel) adjustPaneHeights() {
if !m.showLog {
shareRequestsStyle.Height(m.height - m.headerHeight)
} else {
splitHeight := m.height - m.headerHeight
shareRequestsStyle.Height(splitHeight/2 - 1)
shareLogStyle.Height(splitHeight/2 - 1)
}
}
func (m *shareModel) renderConfig() string {
out := "["
if m.shareMode == "public" {
out += shareModePublicStyle.Render(strings.ToUpper(m.shareMode))
} else {
out += shareModePrivateStyle.Render(strings.ToUpper(m.shareMode))
}
out += "] ["
if m.backendMode == "proxy" {
out += backendModeProxyStyle.Render(strings.ToUpper(m.backendMode))
} else {
out += backendModeWebStyle.Render(strings.ToUpper(m.backendMode))
}
out += "]"
return out
}
func (m *shareModel) renderRequests() string {
var requestLines []string
for _, req := range m.requests {
reqLine := fmt.Sprintf("%v %v -> %v %v",
timeStyle.Render(req.Stamp.Format(time.RFC850)),
addressStyle.Render(req.RemoteAddr),
m.renderMethod(req.Method),
req.Path,
)
reqLineWrapped := wordwrap.String(reqLine, m.width-2)
splitWrapped := strings.Split(reqLineWrapped, "\n")
for _, splitLine := range splitWrapped {
splitLine := strings.ReplaceAll(splitLine, "\n", "")
if splitLine != "" {
requestLines = append(requestLines, splitLine)
}
}
}
maxRows := shareRequestsStyle.GetHeight()
startRow := 0
if len(requestLines) > maxRows {
startRow = len(requestLines) - maxRows
}
out := ""
for i := startRow; i < len(requestLines); i++ {
outLine := requestLines[i]
if i < len(requestLines)-1 {
outLine += "\n"
}
out += outLine
}
return out
}
func (m *shareModel) renderMethod(method string) string {
switch strings.ToLower(method) {
case "get":
return getStyle.Render(method)
case "post":
return postStyle.Render(method)
default:
return otherMethodStyle.Render(method)
}
}
func (m *shareModel) renderLog() string {
var splitLines []string
for _, line := range m.log {
wrapped := wordwrap.String(line, m.width-2)
wrappedLines := strings.Split(wrapped, "\n")
for _, wrappedLine := range wrappedLines {
splitLine := strings.ReplaceAll(wrappedLine, "\n", "")
if splitLine != "" {
splitLines = append(splitLines, splitLine)
}
}
}
maxRows := shareLogStyle.GetHeight()
startRow := 0
if len(splitLines) > maxRows {
startRow = len(splitLines) - maxRows
}
out := ""
for i := startRow; i < len(splitLines); i++ {
outLine := splitLines[i]
if i < len(splitLines)-1 {
outLine += "\n"
}
out += outLine
}
return out
}
func (m *shareModel) Write(p []byte) (n int, err error) {
in := string(p)
lines := strings.Split(in, "\n")
for _, line := range lines {
cleanLine := strings.ReplaceAll(line, "\n", "")
if cleanLine != "" {
m.prg.Send(shareLogLine(cleanLine))
}
}
return len(p), nil
}
var shareHeaderStyle = lipgloss.NewStyle().
BorderStyle(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("63")).
Align(lipgloss.Center)
var shareConfigStyle = lipgloss.NewStyle().
BorderStyle(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("63")).
Align(lipgloss.Center)
var shareRequestsStyle = lipgloss.NewStyle().
BorderStyle(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("63"))
var shareLogStyle = lipgloss.NewStyle().
BorderStyle(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("63"))
var shareModePublicStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#0F0"))
var shareModePrivateStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#F00"))
var backendModeProxyStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#FFA500"))
var backendModeWebStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#0CC"))
var timeStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#444"))
var addressStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#FFA500"))
var getStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("98"))
var postStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("101"))
var otherMethodStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("166"))

View File

@ -6,10 +6,6 @@ import (
"os" "os"
) )
type backendHandler interface {
Requests() func() int32
}
func mustGetAdminAuth() runtime.ClientAuthInfoWriter { func mustGetAdminAuth() runtime.ClientAuthInfoWriter {
adminToken := os.Getenv("ZROK_ADMIN_TOKEN") adminToken := os.Getenv("ZROK_ADMIN_TOKEN")
if adminToken == "" { if adminToken == "" {

View File

@ -31,7 +31,12 @@ func (h *enableHandler) Handle(params environment.EnableParams, principal *rest_
logrus.Errorf("error getting edge client: %v", err) logrus.Errorf("error getting edge client: %v", err)
return environment.NewEnableInternalServerError() return environment.NewEnableInternalServerError()
} }
ident, err := zrokEdgeSdk.CreateEnvironmentIdentity(principal.Email, params.Body.Description, client) uniqueToken, err := createShareToken()
if err != nil {
logrus.Errorf("error creating unique identity token: %v", err)
return environment.NewEnableInternalServerError()
}
ident, err := zrokEdgeSdk.CreateEnvironmentIdentity(uniqueToken, principal.Email, params.Body.Description, client)
if err != nil { if err != nil {
logrus.Error(err) logrus.Error(err)
return environment.NewEnableInternalServerError() return environment.NewEnableInternalServerError()

View File

@ -12,10 +12,10 @@ import (
"time" "time"
) )
func CreateEnvironmentIdentity(accountEmail, envDescription string, edge *rest_management_api_client.ZitiEdgeManagement) (*identity.CreateIdentityCreated, error) { func CreateEnvironmentIdentity(uniqueToken, accountEmail, envDescription string, edge *rest_management_api_client.ZitiEdgeManagement) (*identity.CreateIdentityCreated, error) {
identityType := rest_model_edge.IdentityTypeUser identityType := rest_model_edge.IdentityTypeUser
moreTags := map[string]interface{}{"zrokEmail": accountEmail} moreTags := map[string]interface{}{"zrokEmail": accountEmail}
return CreateIdentity(accountEmail+"-"+envDescription, identityType, moreTags, edge) return CreateIdentity(accountEmail+"-"+uniqueToken+"-"+envDescription, identityType, moreTags, edge)
} }
func CreateIdentity(name string, identityType rest_model_edge.IdentityType, addlTags map[string]interface{}, edge *rest_management_api_client.ZitiEdgeManagement) (*identity.CreateIdentityCreated, error) { func CreateIdentity(name string, identityType rest_model_edge.IdentityType, addlTags map[string]interface{}, edge *rest_management_api_client.ZitiEdgeManagement) (*identity.CreateIdentityCreated, error) {

View File

@ -1,9 +1,12 @@
package privateFrontend package privateFrontend
import "github.com/openziti-test-kitchen/zrok/endpoints"
type Config struct { type Config struct {
IdentityName string IdentityName string
ShrToken string ShrToken string
Address string Address string
RequestsChan chan *endpoints.Request
} }
func DefaultConfig(identityName string) *Config { func DefaultConfig(identityName string) *Config {

View File

@ -16,6 +16,7 @@ import (
"net/http" "net/http"
"net/http/httputil" "net/http/httputil"
"net/url" "net/url"
"time"
) )
type httpFrontend struct { type httpFrontend struct {
@ -75,6 +76,14 @@ func newServiceProxy(cfg *Config, ctx ziti.Context) (*httputil.ReverseProxy, err
proxy := serviceTargetProxy(cfg, ctx) proxy := serviceTargetProxy(cfg, ctx)
director := proxy.Director director := proxy.Director
proxy.Director = func(req *http.Request) { proxy.Director = func(req *http.Request) {
if cfg.RequestsChan != nil {
cfg.RequestsChan <- &endpoints.Request{
Stamp: time.Now(),
RemoteAddr: fmt.Sprintf("%v", req.Header["X-Real-Ip"]),
Method: req.Method,
Path: req.URL.String(),
}
}
director(req) director(req)
req.Header.Set("X-Proxy", "zrok") req.Header.Set("X-Proxy", "zrok")
} }
@ -98,7 +107,7 @@ func serviceTargetProxy(cfg *Config, ctx ziti.Context) *httputil.ReverseProxy {
logrus.Warn("no config!") logrus.Warn("no config!")
} }
if target, err := url.Parse(fmt.Sprintf("http://%v", targetShrToken)); err == nil { if target, err := url.Parse(fmt.Sprintf("http://%v", targetShrToken)); err == nil {
logrus.Infof("[%v] -> %v", targetShrToken, req.URL) logrus.Debugf("[%v] -> %v", targetShrToken, req.URL)
targetQuery := target.RawQuery targetQuery := target.RawQuery
req.URL.Scheme = target.Scheme req.URL.Scheme = target.Scheme

View File

@ -3,6 +3,7 @@ package proxyBackend
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/openziti-test-kitchen/zrok/endpoints"
"github.com/openziti-test-kitchen/zrok/util" "github.com/openziti-test-kitchen/zrok/util"
"github.com/openziti/sdk-golang/ziti" "github.com/openziti/sdk-golang/ziti"
"github.com/openziti/sdk-golang/ziti/config" "github.com/openziti/sdk-golang/ziti/config"
@ -20,6 +21,7 @@ type Config struct {
IdentityPath string IdentityPath string
EndpointAddress string EndpointAddress string
ShrToken string ShrToken string
RequestsChan chan *endpoints.Request
} }
type backend struct { type backend struct {
@ -43,7 +45,7 @@ func NewBackend(cfg *Config) (*backend, error) {
return nil, errors.Wrap(err, "error listening") return nil, errors.Wrap(err, "error listening")
} }
proxy, err := newReverseProxy(cfg.EndpointAddress) proxy, err := newReverseProxy(cfg.EndpointAddress, cfg.RequestsChan)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -68,7 +70,7 @@ func (self *backend) Requests() func() int32 {
return self.requests return self.requests
} }
func newReverseProxy(target string) (*httputil.ReverseProxy, error) { func newReverseProxy(target string, requests chan *endpoints.Request) (*httputil.ReverseProxy, error) {
targetURL, err := url.Parse(target) targetURL, err := url.Parse(target)
if err != nil { if err != nil {
return nil, err return nil, err
@ -81,9 +83,15 @@ func newReverseProxy(target string) (*httputil.ReverseProxy, error) {
proxy.Transport = tpt proxy.Transport = tpt
director := proxy.Director director := proxy.Director
proxy.Director = func(req *http.Request) { proxy.Director = func(req *http.Request) {
fmt.Printf("proxy <= %v %v <= %v\n", req.Method, req.URL.String(), req.Header["X-Real-Ip"]) if requests != nil {
requests <- &endpoints.Request{
Stamp: time.Now(),
RemoteAddr: fmt.Sprintf("%v", req.Header["X-Real-Ip"]),
Method: req.Method,
Path: req.URL.String(),
}
}
director(req) director(req)
logrus.Debugf("-> %v", req.URL.String())
req.Header.Set("X-Proxy", "zrok") req.Header.Set("X-Proxy", "zrok")
} }
proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) { proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) {

14
endpoints/requests.go Normal file
View File

@ -0,0 +1,14 @@
package endpoints
import "time"
type RequestHandler interface {
Requests() func() int32
}
type Request struct {
Stamp time.Time
RemoteAddr string
Method string
Path string
}

View File

@ -2,6 +2,7 @@ package webBackend
import ( import (
"fmt" "fmt"
"github.com/openziti-test-kitchen/zrok/endpoints"
"github.com/openziti/sdk-golang/ziti" "github.com/openziti/sdk-golang/ziti"
"github.com/openziti/sdk-golang/ziti/config" "github.com/openziti/sdk-golang/ziti/config"
"github.com/openziti/sdk-golang/ziti/edge" "github.com/openziti/sdk-golang/ziti/edge"
@ -14,6 +15,7 @@ type Config struct {
IdentityPath string IdentityPath string
WebRoot string WebRoot string
ShrToken string ShrToken string
RequestsChan chan *endpoints.Request
} }
type backend struct { type backend struct {
@ -36,11 +38,16 @@ func NewBackend(cfg *Config) (*backend, error) {
return nil, errors.Wrap(err, "error listening") return nil, errors.Wrap(err, "error listening")
} }
return &backend{ be := &backend{
cfg: cfg, cfg: cfg,
listener: listener, listener: listener,
handler: &requestLogger{handler: http.FileServer(http.Dir(cfg.WebRoot))}, }
}, nil if cfg.RequestsChan != nil {
be.handler = &requestGrabber{requests: cfg.RequestsChan, handler: http.FileServer(http.Dir(cfg.WebRoot))}
} else {
be.handler = http.FileServer(http.Dir(cfg.WebRoot))
}
return be, nil
} }
func (self *backend) Run() error { func (self *backend) Run() error {
@ -54,11 +61,19 @@ func (self *backend) Requests() func() int32 {
return func() int32 { return 0 } return func() int32 { return 0 }
} }
type requestLogger struct { type requestGrabber struct {
handler http.Handler requests chan *endpoints.Request
handler http.Handler
} }
func (rl *requestLogger) ServeHTTP(resp http.ResponseWriter, req *http.Request) { func (rl *requestGrabber) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
fmt.Printf("web <= %v %v <= %v\n", req.Method, req.URL.String(), req.Header["X-Real-Ip"]) if rl.requests != nil {
rl.requests <- &endpoints.Request{
Stamp: time.Now(),
RemoteAddr: fmt.Sprintf("%v", req.Header["X-Real-Ip"]),
Method: req.Method,
Path: req.URL.String(),
}
}
rl.handler.ServeHTTP(resp, req) rl.handler.ServeHTTP(resp, req)
} }

15
go.mod
View File

@ -3,6 +3,7 @@ module github.com/openziti-test-kitchen/zrok
go 1.18 go 1.18
require ( require (
github.com/charmbracelet/bubbletea v0.23.1
github.com/charmbracelet/lipgloss v0.6.0 github.com/charmbracelet/lipgloss v0.6.0
github.com/go-openapi/errors v0.20.2 github.com/go-openapi/errors v0.20.2
github.com/go-openapi/loads v0.21.1 github.com/go-openapi/loads v0.21.1
@ -39,7 +40,11 @@ require (
require ( require (
github.com/Jeffail/gabs v1.4.0 // indirect github.com/Jeffail/gabs v1.4.0 // indirect
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52 v1.0.3 // indirect
github.com/cenkalti/backoff/v4 v4.1.3 // indirect github.com/cenkalti/backoff/v4 v4.1.3 // indirect
github.com/charmbracelet/bubbles v0.14.0 // indirect
github.com/containerd/console v1.0.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/deepmap/oapi-codegen v1.8.2 // indirect github.com/deepmap/oapi-codegen v1.8.2 // indirect
github.com/docker/go-units v0.4.0 // indirect github.com/docker/go-units v0.4.0 // indirect
@ -61,13 +66,16 @@ require (
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect github.com/mattn/go-isatty v0.0.16 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/miekg/pkcs11 v1.1.1 // indirect github.com/miekg/pkcs11 v1.1.1 // indirect
github.com/mitchellh/go-ps v1.0.0 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68 // indirect github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 // indirect github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.13.0 // indirect
github.com/netfoundry/secretstream v0.1.2 // indirect github.com/netfoundry/secretstream v0.1.2 // indirect
github.com/oklog/ulid v1.3.1 // indirect github.com/oklog/ulid v1.3.1 // indirect
github.com/openziti/channel/v2 v2.0.1 // indirect github.com/openziti/channel/v2 v2.0.1 // indirect
@ -90,6 +98,7 @@ require (
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be // indirect golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be // indirect
golang.org/x/sys v0.0.0-20220926163933-8cfa568d3c25 // indirect golang.org/x/sys v0.0.0-20220926163933-8cfa568d3c25 // indirect
golang.org/x/term v0.0.0-20220919170432-7a66f970e087 // indirect golang.org/x/term v0.0.0-20220919170432-7a66f970e087 // indirect
golang.org/x/text v0.3.7 // indirect
google.golang.org/protobuf v1.28.1 // indirect google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect

35
go.sum
View File

@ -53,11 +53,22 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ=
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-osc52 v1.0.3 h1:DTwqENW7X9arYimJrPeGZcV0ln14sGMt3pHZspWD+Mg=
github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4=
github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/charmbracelet/bubbles v0.14.0 h1:DJfCwnARfWjZLvMglhSQzo76UZ2gucuHPy9jLWX45Og=
github.com/charmbracelet/bubbles v0.14.0/go.mod h1:bbeTiXwPww4M031aGi8UK2HT9RDWoiNibae+1yCMtcc=
github.com/charmbracelet/bubbletea v0.21.0/go.mod h1:GgmJMec61d08zXsOhqRC/AiOx4K4pmz+VIcRIm1FKr4=
github.com/charmbracelet/bubbletea v0.23.1 h1:CYdteX1wCiCzKNUlwm25ZHBIc1GXlYFyUIte8WPvhck=
github.com/charmbracelet/bubbletea v0.23.1/go.mod h1:JAfGK/3/pPKHTnAS8JIE2u9f61BjWTQY57RbT25aMXU=
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
github.com/charmbracelet/lipgloss v0.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0xFhg5SaqxScmgs=
github.com/charmbracelet/lipgloss v0.6.0 h1:1StyZB9vBSOyuZxQUcUwGr17JmojPNm87inij9N3wJY= github.com/charmbracelet/lipgloss v0.6.0 h1:1StyZB9vBSOyuZxQUcUwGr17JmojPNm87inij9N3wJY=
github.com/charmbracelet/lipgloss v0.6.0/go.mod h1:tHh2wr34xcHjC2HCXIlGSG1jaDF0S0atAUvBMP6Ppuk= github.com/charmbracelet/lipgloss v0.6.0/go.mod h1:tHh2wr34xcHjC2HCXIlGSG1jaDF0S0atAUvBMP6Ppuk=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
@ -67,6 +78,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
@ -320,6 +333,7 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
@ -359,11 +373,15 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.14 h1:qZgc/Rwetq+MtyE18WhzjokPD93dNqLGNT3QJuLvBGw= github.com/mattn/go-sqlite3 v1.14.14 h1:qZgc/Rwetq+MtyE18WhzjokPD93dNqLGNT3QJuLvBGw=
@ -399,10 +417,18 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68 h1:y1p/ycavWjGT9FnmSjdbWUlLGvcxrY0Rw3ATltrxOhk= github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
github.com/muesli/cancelreader v0.2.0/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ= github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 h1:STjmj0uFfRryL9fzRA/OupNppeAID6QJYPMavTL7jtY= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
github.com/muesli/termenv v0.13.0 h1:wK20DRpJdDX8b7Ek2QfhvqhRQFZ237RGRO0RQ/Iqdy0=
github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc=
github.com/netfoundry/secretstream v0.1.2 h1:NgqrYytDnjKbOfWI29TT0SJM+RwB3yf9MIkJVJaU+J0= github.com/netfoundry/secretstream v0.1.2 h1:NgqrYytDnjKbOfWI29TT0SJM+RwB3yf9MIkJVJaU+J0=
github.com/netfoundry/secretstream v0.1.2/go.mod h1:uasYkYSp0MmNSlKOWJ2sVzxPms8e58TS4ENq4yro86k= github.com/netfoundry/secretstream v0.1.2/go.mod h1:uasYkYSp0MmNSlKOWJ2sVzxPms8e58TS4ENq4yro86k=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
@ -472,6 +498,7 @@ github.com/rubenv/sql-migrate v1.1.2/go.mod h1:/7TZymwxN8VWumcIxw1jjHEcR1djpdkMH
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shirou/gopsutil/v3 v3.22.8 h1:a4s3hXogo5mE2PfdfJIonDbstO/P+9JszdfhAHSzD9Y= github.com/shirou/gopsutil/v3 v3.22.8 h1:a4s3hXogo5mE2PfdfJIonDbstO/P+9JszdfhAHSzD9Y=
github.com/shirou/gopsutil/v3 v3.22.8/go.mod h1:s648gW4IywYzUfE/KjXxUsqrqx/T2xO5VqOXxONeRfI= github.com/shirou/gopsutil/v3 v3.22.8/go.mod h1:s648gW4IywYzUfE/KjXxUsqrqx/T2xO5VqOXxONeRfI=
@ -748,6 +775,8 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

View File

@ -46,7 +46,7 @@ func writeMetadata() error {
if err := os.MkdirAll(filepath.Dir(mf), os.FileMode(0700)); err != nil { if err := os.MkdirAll(filepath.Dir(mf), os.FileMode(0700)); err != nil {
return err return err
} }
if err := os.WriteFile(mf, data, os.FileMode(0400)); err != nil { if err := os.WriteFile(mf, data, os.FileMode(0600)); err != nil {
return err return err
} }
return nil return nil