diff --git a/cmd/zrok/accessPrivate.go b/cmd/zrok/accessPrivate.go index 7346c30e..d76e32a4 100644 --- a/cmd/zrok/accessPrivate.go +++ b/cmd/zrok/accessPrivate.go @@ -1,8 +1,10 @@ package main import ( + tea "github.com/charmbracelet/bubbletea" "github.com/go-openapi/runtime" 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/rest_client_zrok" "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.ShrToken = shrToken cfg.Address = cmd.bindAddress + cfg.RequestsChan = make(chan *endpoints.Request, 1024) c := make(chan os.Signal) signal.Notify(c, os.Interrupt, syscall.SIGTERM) @@ -101,13 +104,36 @@ func (cmd *accessPrivateCommand) run(_ *cobra.Command, args []string) { panic(err) } - logrus.Infof("access your share at: %v", endpointUrl.String()) - - if err := frontend.Run(); err != nil { - if !panicInstead { - tui.Error("unable to run frontend", err) + go func() { + if err := frontend.Run(); err != nil { + if !panicInstead { + 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) { diff --git a/cmd/zrok/accessTui.go b/cmd/zrok/accessTui.go new file mode 100644 index 00000000..fee5e292 --- /dev/null +++ b/cmd/zrok/accessTui.go @@ -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")) diff --git a/cmd/zrok/disable.go b/cmd/zrok/disable.go index f305eac7..6e2ea4d0 100644 --- a/cmd/zrok/disable.go +++ b/cmd/zrok/disable.go @@ -70,5 +70,5 @@ func (cmd *disableCommand) run(_ *cobra.Command, _ []string) { 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...") } diff --git a/cmd/zrok/enable.go b/cmd/zrok/enable.go index e220123d..043895d4 100644 --- a/cmd/zrok/enable.go +++ b/cmd/zrok/enable.go @@ -2,6 +2,8 @@ package main import ( "fmt" + "github.com/charmbracelet/bubbles/spinner" + tea "github.com/charmbracelet/bubbletea" httptransport "github.com/go-openapi/runtime/client" "github.com/openziti-test-kitchen/zrok/rest_client_zrok/environment" "github.com/openziti-test-kitchen/zrok/rest_model_zrok" @@ -9,6 +11,7 @@ import ( "github.com/openziti-test-kitchen/zrok/zrokdir" "github.com/shirou/gopsutil/v3/host" "github.com/spf13/cobra" + "os" user2 "os/user" ) @@ -52,7 +55,6 @@ func (cmd *enableCommand) run(_ *cobra.Command, args []string) { if cmd.description == "@" { cmd.description = fmt.Sprintf("%v@%v", user.Username, hostName) } - zrok, err := zrd.Client() if err != nil { panic(err) @@ -63,29 +65,49 @@ func (cmd *enableCommand) run(_ *cobra.Command, args []string) { Description: cmd.description, 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) if err != nil { - if !panicInstead { - tui.Error("the zrok service returned an error", err) - } - panic(err) + prg.Send(fmt.Sprintf("the zrok service returned an error: %v", err)) + prg.Quit() + <-done + os.Exit(1) } + prg.Send("writing the environment details...") apiEndpoint, _ := zrd.ApiEndpoint() zrd.Env = &zrokdir.Environment{Token: token, ZId: resp.Payload.Identity, ApiEndpoint: apiEndpoint} if err := zrd.Save(); err != nil { - if !panicInstead { - tui.Error("there was an error saving the new environment", err) - } - panic(err) + prg.Send(fmt.Sprintf("there was an error saving the new environment: %v", err)) + prg.Quit() + <-done + os.Exit(1) } if err := zrokdir.SaveZitiIdentity("backend", resp.Payload.Cfg); err != nil { - if !panicInstead { - tui.Error("there was an error writing the environment file", err) - } - panic(err) + prg.Send(fmt.Sprintf("there was an error writing the environment: %v", err)) + prg.Quit() + <-done + 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) { @@ -97,3 +119,52 @@ func getHost() (string, string, error) { info.Hostname, info.OS, info.Platform, info.PlatformFamily, info.PlatformVersion, info.KernelVersion, info.KernelArch) 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 +} diff --git a/cmd/zrok/invite.go b/cmd/zrok/invite.go index 8a5a91d4..0a7d2cad 100644 --- a/cmd/zrok/invite.go +++ b/cmd/zrok/invite.go @@ -2,13 +2,17 @@ package main import ( "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_model_zrok" "github.com/openziti-test-kitchen/zrok/tui" "github.com/openziti-test-kitchen/zrok/util" "github.com/openziti-test-kitchen/zrok/zrokdir" - "github.com/openziti/foundation/v2/term" "github.com/spf13/cobra" + "os" + "strings" ) func init() { @@ -17,6 +21,7 @@ func init() { type inviteCommand struct { cmd *cobra.Command + tui inviteTui } func newInviteCommand() *inviteCommand { @@ -25,50 +30,188 @@ func newInviteCommand() *inviteCommand { Short: "Invite a new user to zrok", Args: cobra.ExactArgs(0), } - command := &inviteCommand{cmd: cmd} + command := &inviteCommand{ + cmd: cmd, + tui: newInviteTui(), + } cmd.Run = command.run return command } func (cmd *inviteCommand) run(_ *cobra.Command, _ []string) { - email, err := term.Prompt("New Email: ") - if err != nil { - panic(err) - } - 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 _, err := tea.NewProgram(&cmd.tui).Run(); err != nil { + tui.Error("unable to run interface", err) + os.Exit(1) } + if cmd.tui.done { + email := cmd.tui.inputs[0].Value() - zrd, err := zrokdir.Load() - if err != nil { - tui.Error("error loading zrokdir", err) - } - - zrok, err := zrd.Client() - if err != nil { - if !panicInstead { - tui.Error("error creating zrok api client", err) + zrd, err := zrokdir.Load() + if err != nil { + tui.Error("error loading zrokdir", 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() } diff --git a/cmd/zrok/reserve.go b/cmd/zrok/reserve.go index 501639e5..22c7f5f0 100644 --- a/cmd/zrok/reserve.go +++ b/cmd/zrok/reserve.go @@ -21,18 +21,20 @@ func init() { type reserveCommand struct { basicAuth []string frontendSelection []string + backendMode string cmd *cobra.Command } func newReserveCommand() *reserveCommand { cmd := &cobra.Command{ - Use: "reserve ", + Use: "reserve ", Short: "Create a reserved share", Args: cobra.ExactArgs(2), } command := &reserveCommand{cmd: cmd} cmd.Flags().StringArrayVar(&command.basicAuth, "basic-auth", []string{}, "Basic authentication users (,...)") 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 return command } @@ -43,15 +45,23 @@ func (cmd *reserveCommand) run(_ *cobra.Command, args []string) { tui.Error("invalid sharing mode; expecting 'public' or 'private'", nil) } - targetEndpoint, err := url.Parse(args[1]) - if err != nil { - if !panicInstead { - tui.Error("invalid target endpoint URL", err) + var target string + switch cmd.backendMode { + case "proxy": + 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() @@ -78,8 +88,8 @@ func (cmd *reserveCommand) run(_ *cobra.Command, args []string) { req.Body = &rest_model_zrok.ShareRequest{ EnvZID: zrd.Env.ZId, ShareMode: shareMode, - BackendMode: "proxy", - BackendProxyEndpoint: targetEndpoint.String(), + BackendMode: cmd.backendMode, + BackendProxyEndpoint: target, AuthScheme: string(model.None), Reserved: true, } diff --git a/cmd/zrok/sharePrivate.go b/cmd/zrok/sharePrivate.go index f13e6c2d..851ebc20 100644 --- a/cmd/zrok/sharePrivate.go +++ b/cmd/zrok/sharePrivate.go @@ -2,8 +2,10 @@ package main import ( "fmt" + tea "github.com/charmbracelet/bubbletea" "github.com/go-openapi/runtime" 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/webBackend" "github.com/openziti-test-kitchen/zrok/model" @@ -20,7 +22,6 @@ import ( "os/signal" "strings" "syscall" - "time" ) func init() { @@ -30,18 +31,20 @@ func init() { type sharePrivateCommand struct { basicAuth []string backendMode string + headless bool cmd *cobra.Command } func newSharePrivateCommand() *sharePrivateCommand { cmd := &cobra.Command{ - Use: "private ", - Short: "Share a target endpoint privately", + Use: "private ", + Short: "Share a target resource privately", Args: cobra.ExactArgs(1), } command := &sharePrivateCommand{cmd: cmd} cmd.Flags().StringArrayVar(&command.basicAuth, "basic-auth", []string{}, "Basic authentication users (,...") 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 return command } @@ -135,12 +138,14 @@ func (cmd *sharePrivateCommand) run(_ *cobra.Command, args []string) { os.Exit(0) }() + requestsChan := make(chan *endpoints.Request, 1024) switch cmd.backendMode { case "proxy": cfg := &proxyBackend.Config{ IdentityPath: zif, EndpointAddress: target, ShrToken: resp.Payload.ShrToken, + RequestsChan: requestsChan, } _, err = cmd.proxyBackendMode(cfg) if err != nil { @@ -155,6 +160,7 @@ func (cmd *sharePrivateCommand) run(_ *cobra.Command, args []string) { IdentityPath: zif, WebRoot: target, ShrToken: resp.Payload.ShrToken, + RequestsChan: requestsChan, } _, err = cmd.webBackendMode(cfg) if err != nil { @@ -168,14 +174,41 @@ func (cmd *sharePrivateCommand) run(_ *cobra.Command, args []string) { 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 { - time.Sleep(30 * time.Second) + } else { + 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) if err != nil { 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 } -func (cmd *sharePrivateCommand) webBackendMode(cfg *webBackend.Config) (backendHandler, error) { +func (cmd *sharePrivateCommand) 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") diff --git a/cmd/zrok/sharePublic.go b/cmd/zrok/sharePublic.go index 894b05c0..7baf2436 100644 --- a/cmd/zrok/sharePublic.go +++ b/cmd/zrok/sharePublic.go @@ -2,8 +2,10 @@ package main import ( "fmt" + tea "github.com/charmbracelet/bubbletea" "github.com/go-openapi/runtime" 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/webBackend" "github.com/openziti-test-kitchen/zrok/model" @@ -20,7 +22,6 @@ import ( "os/signal" "strings" "syscall" - "time" ) func init() { @@ -31,6 +32,7 @@ type sharePublicCommand struct { basicAuth []string frontendSelection []string backendMode string + headless bool cmd *cobra.Command } @@ -44,6 +46,7 @@ func newSharePublicCommand() *sharePublicCommand { cmd.Flags().StringArrayVar(&command.basicAuth, "basic-auth", []string{}, "Basic authentication users (,...)") 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().BoolVar(&command.headless, "headless", false, "Disable TUI and run headless") cmd.Run = command.run return command } @@ -75,7 +78,7 @@ func (cmd *sharePublicCommand) run(_ *cobra.Command, args []string) { zrd, err := zrokdir.Load() if err != nil { if !panicInstead { - tui.Error("unable to load zrokdir", nil) + tui.Error("unable to load zrokdir", err) } panic(err) } @@ -99,6 +102,7 @@ func (cmd *sharePublicCommand) run(_ *cobra.Command, args []string) { } panic(err) } + auth := httptransport.APIKeyAuth("X-TOKEN", "header", zrd.Env.Token) req := share.NewShareParams() 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) if err != nil { if !panicInstead { - tui.Error("unable to create tunnel", err) + tui.Error("unable to create share", err) } panic(err) } @@ -137,15 +141,16 @@ func (cmd *sharePublicCommand) run(_ *cobra.Command, args []string) { os.Exit(0) }() - var bh backendHandler + requestsChan := make(chan *endpoints.Request, 1024) switch cmd.backendMode { case "proxy": cfg := &proxyBackend.Config{ IdentityPath: zif, EndpointAddress: target, ShrToken: resp.Payload.ShrToken, + RequestsChan: requestsChan, } - bh, err = cmd.proxyBackendMode(cfg) + _, err = cmd.proxyBackendMode(cfg) if err != nil { if !panicInstead { tui.Error("unable to create proxy backend handler", err) @@ -158,8 +163,9 @@ func (cmd *sharePublicCommand) run(_ *cobra.Command, args []string) { IdentityPath: zif, WebRoot: target, ShrToken: resp.Payload.ShrToken, + RequestsChan: requestsChan, } - bh, err = cmd.webBackendMode(cfg) + _, err = cmd.webBackendMode(cfg) if err != nil { if !panicInstead { 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) } - logrus.Infof("access your zrok share: %v", resp.Payload.FrontendProxyEndpoints[0]) - for { - time.Sleep(5 * time.Second) - logrus.Infof("requests: %d", bh.Requests()()) + if cmd.headless { + logrus.Infof("access your zrok share at the following endpoints:\n %v", strings.Join(resp.Payload.FrontendProxyEndpoints, "\n")) + for { + 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) if err != nil { 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 } -func (cmd *sharePublicCommand) webBackendMode(cfg *webBackend.Config) (backendHandler, error) { +func (cmd *sharePublicCommand) 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") diff --git a/cmd/zrok/shareReserved.go b/cmd/zrok/shareReserved.go index 2b7e8617..f57f381f 100644 --- a/cmd/zrok/shareReserved.go +++ b/cmd/zrok/shareReserved.go @@ -1,17 +1,20 @@ package main import ( + "fmt" + tea "github.com/charmbracelet/bubbletea" 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/webBackend" "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_model_zrok" "github.com/openziti-test-kitchen/zrok/tui" "github.com/openziti-test-kitchen/zrok/zrokdir" + "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" - "net/url" - "time" ) func init() { @@ -20,6 +23,7 @@ func init() { type shareReservedCommand struct { overrideEndpoint string + headless bool cmd *cobra.Command } @@ -30,26 +34,14 @@ func newShareReservedCommand() *shareReservedCommand { } command := &shareReservedCommand{cmd: cmd} 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 return command } func (cmd *shareReservedCommand) run(_ *cobra.Command, args []string) { shrToken := args[0] - targetEndpoint := "" - 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() - } + var target string zrd, err := zrokdir.Load() if err != nil { @@ -80,8 +72,8 @@ func (cmd *shareReservedCommand) run(_ *cobra.Command, args []string) { } panic(err) } - if targetEndpoint == "" { - targetEndpoint = resp.Payload.BackendProxyEndpoint + if target == "" { + target = resp.Payload.BackendProxyEndpoint } zif, err := zrokdir.ZitiIdentityFile("backend") @@ -91,18 +83,14 @@ func (cmd *shareReservedCommand) run(_ *cobra.Command, args []string) { } 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.Body = &rest_model_zrok.UpdateShareRequest{ ShrToken: shrToken, - BackendProxyEndpoint: targetEndpoint, + BackendProxyEndpoint: target, } if _, err := zrok.Share.UpdateShare(upReq, auth); err != nil { if !panicInstead { @@ -110,37 +98,118 @@ func (cmd *shareReservedCommand) run(_ *cobra.Command, args []string) { } panic(err) } - logrus.Infof("updated backend proxy endpoint to: %v", targetEndpoint) + logrus.Infof("updated backend proxy endpoint to: %v", target) } else { - logrus.Infof("using existing backend proxy endpoint: %v", targetEndpoint) + logrus.Infof("using existing backend proxy endpoint: %v", target) } - httpProxy, err := proxyBackend.NewBackend(cfg) - if err != nil { - if !panicInstead { - tui.Error("unable to create http backend", err) + requestsChan := make(chan *endpoints.Request, 1024) + switch resp.Payload.BackendMode { + case "proxy": + cfg := &proxyBackend.Config{ + IdentityPath: zif, + EndpointAddress: target, + ShrToken: shrToken, + RequestsChan: requestsChan, } - panic(err) - } - - go func() { - if err := httpProxy.Run(); err != nil { + _, err := cmd.proxyBackendMode(cfg) + if err != nil { if !panicInstead { - tui.Error("unable to run http proxy", err) + tui.Error("unable to create proxy backend handler", err) } panic(err) } - }() - switch resp.Payload.ShareMode { - case "public": - logrus.Infof("access your zrok share: %v", resp.Payload.FrontendEndpoint) + case "web": + cfg := &webBackend.Config{ + 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": - logrus.Infof("use this command to access your zrok share: 'zrok access private %v'", shrToken) + default: + tui.Error("invalid backend mode", nil) } - for { - time.Sleep(30 * time.Second) + if cmd.headless { + 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 +} diff --git a/cmd/zrok/shareTui.go b/cmd/zrok/shareTui.go new file mode 100644 index 00000000..71c39174 --- /dev/null +++ b/cmd/zrok/shareTui.go @@ -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")) diff --git a/cmd/zrok/util.go b/cmd/zrok/util.go index be53c919..248b43f8 100644 --- a/cmd/zrok/util.go +++ b/cmd/zrok/util.go @@ -6,10 +6,6 @@ import ( "os" ) -type backendHandler interface { - Requests() func() int32 -} - func mustGetAdminAuth() runtime.ClientAuthInfoWriter { adminToken := os.Getenv("ZROK_ADMIN_TOKEN") if adminToken == "" { diff --git a/controller/enable.go b/controller/enable.go index dd6bba26..dfbf5f29 100644 --- a/controller/enable.go +++ b/controller/enable.go @@ -31,7 +31,12 @@ func (h *enableHandler) Handle(params environment.EnableParams, principal *rest_ logrus.Errorf("error getting edge client: %v", err) 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 { logrus.Error(err) return environment.NewEnableInternalServerError() diff --git a/controller/zrokEdgeSdk/identity.go b/controller/zrokEdgeSdk/identity.go index 35be16c4..dbcd7bdd 100644 --- a/controller/zrokEdgeSdk/identity.go +++ b/controller/zrokEdgeSdk/identity.go @@ -12,10 +12,10 @@ import ( "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 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) { diff --git a/endpoints/privateFrontend/config.go b/endpoints/privateFrontend/config.go index 4a309d83..49c4e1d2 100644 --- a/endpoints/privateFrontend/config.go +++ b/endpoints/privateFrontend/config.go @@ -1,9 +1,12 @@ package privateFrontend +import "github.com/openziti-test-kitchen/zrok/endpoints" + type Config struct { IdentityName string ShrToken string Address string + RequestsChan chan *endpoints.Request } func DefaultConfig(identityName string) *Config { diff --git a/endpoints/privateFrontend/http.go b/endpoints/privateFrontend/http.go index 48b70e33..c80c76c2 100644 --- a/endpoints/privateFrontend/http.go +++ b/endpoints/privateFrontend/http.go @@ -16,6 +16,7 @@ import ( "net/http" "net/http/httputil" "net/url" + "time" ) type httpFrontend struct { @@ -75,6 +76,14 @@ func newServiceProxy(cfg *Config, ctx ziti.Context) (*httputil.ReverseProxy, err proxy := serviceTargetProxy(cfg, ctx) director := proxy.Director 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) req.Header.Set("X-Proxy", "zrok") } @@ -98,7 +107,7 @@ func serviceTargetProxy(cfg *Config, ctx ziti.Context) *httputil.ReverseProxy { logrus.Warn("no config!") } 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 req.URL.Scheme = target.Scheme diff --git a/endpoints/proxyBackend/http.go b/endpoints/proxyBackend/http.go index bf471302..963e05b2 100644 --- a/endpoints/proxyBackend/http.go +++ b/endpoints/proxyBackend/http.go @@ -3,6 +3,7 @@ package proxyBackend import ( "context" "fmt" + "github.com/openziti-test-kitchen/zrok/endpoints" "github.com/openziti-test-kitchen/zrok/util" "github.com/openziti/sdk-golang/ziti" "github.com/openziti/sdk-golang/ziti/config" @@ -20,6 +21,7 @@ type Config struct { IdentityPath string EndpointAddress string ShrToken string + RequestsChan chan *endpoints.Request } type backend struct { @@ -43,7 +45,7 @@ func NewBackend(cfg *Config) (*backend, error) { return nil, errors.Wrap(err, "error listening") } - proxy, err := newReverseProxy(cfg.EndpointAddress) + proxy, err := newReverseProxy(cfg.EndpointAddress, cfg.RequestsChan) if err != nil { return nil, err } @@ -68,7 +70,7 @@ func (self *backend) Requests() func() int32 { 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) if err != nil { return nil, err @@ -81,9 +83,15 @@ func newReverseProxy(target string) (*httputil.ReverseProxy, error) { proxy.Transport = tpt director := proxy.Director 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) - logrus.Debugf("-> %v", req.URL.String()) req.Header.Set("X-Proxy", "zrok") } proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) { diff --git a/endpoints/requests.go b/endpoints/requests.go new file mode 100644 index 00000000..883a8d58 --- /dev/null +++ b/endpoints/requests.go @@ -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 +} diff --git a/endpoints/webBackend/web.go b/endpoints/webBackend/web.go index 6023fa16..06f2eff4 100644 --- a/endpoints/webBackend/web.go +++ b/endpoints/webBackend/web.go @@ -2,6 +2,7 @@ package webBackend import ( "fmt" + "github.com/openziti-test-kitchen/zrok/endpoints" "github.com/openziti/sdk-golang/ziti" "github.com/openziti/sdk-golang/ziti/config" "github.com/openziti/sdk-golang/ziti/edge" @@ -14,6 +15,7 @@ type Config struct { IdentityPath string WebRoot string ShrToken string + RequestsChan chan *endpoints.Request } type backend struct { @@ -36,11 +38,16 @@ func NewBackend(cfg *Config) (*backend, error) { return nil, errors.Wrap(err, "error listening") } - return &backend{ + be := &backend{ cfg: cfg, 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 { @@ -54,11 +61,19 @@ func (self *backend) Requests() func() int32 { return func() int32 { return 0 } } -type requestLogger struct { - handler http.Handler +type requestGrabber struct { + requests chan *endpoints.Request + handler http.Handler } -func (rl *requestLogger) ServeHTTP(resp http.ResponseWriter, req *http.Request) { - fmt.Printf("web <= %v %v <= %v\n", req.Method, req.URL.String(), req.Header["X-Real-Ip"]) +func (rl *requestGrabber) ServeHTTP(resp http.ResponseWriter, req *http.Request) { + 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) } diff --git a/go.mod b/go.mod index 9bf2d188..c98cb934 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/openziti-test-kitchen/zrok go 1.18 require ( + github.com/charmbracelet/bubbletea v0.23.1 github.com/charmbracelet/lipgloss v0.6.0 github.com/go-openapi/errors v0.20.2 github.com/go-openapi/loads v0.21.1 @@ -39,7 +40,11 @@ require ( require ( github.com/Jeffail/gabs v1.4.0 // 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/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/deepmap/oapi-codegen v1.8.2 // 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/mattn/go-colorable v0.1.13 // 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/miekg/pkcs11 v1.1.1 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68 // indirect - github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 // indirect + github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // 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/oklog/ulid v1.3.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/sys v0.0.0-20220926163933-8cfa568d3c25 // 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 gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index d6f5b9cd..163be561 100644 --- a/go.sum +++ b/go.sum @@ -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-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/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/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/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= 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/go.mod h1:tHh2wr34xcHjC2HCXIlGSG1jaDF0S0atAUvBMP6Ppuk= 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-20200629203442-efcf912fb354/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-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= @@ -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.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 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/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= 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.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= 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-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.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.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.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= 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 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 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/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.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/go.mod h1:uasYkYSp0MmNSlKOWJ2sVzxPms8e58TS4ENq4yro86k= 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.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/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/shirou/gopsutil/v3 v3.22.8 h1:a4s3hXogo5mE2PfdfJIonDbstO/P+9JszdfhAHSzD9Y= 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-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-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-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/zrokdir/version.go b/zrokdir/version.go index b7f3c5b5..177f186d 100644 --- a/zrokdir/version.go +++ b/zrokdir/version.go @@ -46,7 +46,7 @@ func writeMetadata() error { if err := os.MkdirAll(filepath.Dir(mf), os.FileMode(0700)); err != nil { 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 nil