From ecbcb0132728cc18016819a214378b642d92278e Mon Sep 17 00:00:00 2001 From: Simon Ruderich Date: Tue, 18 May 2021 18:15:23 +0200 Subject: [PATCH] safcm: move sync_changes.go and term.go to frontend package --- cmd/safcm/sync.go | 12 +-- cmd/safcm/sync_sync.go | 8 +- .../sync_changes.go => frontend/changes.go | 93 ++++++++++--------- .../changes_test.go | 57 +++++------- {cmd/safcm => frontend}/term.go | 4 +- {cmd/safcm => frontend}/term_test.go | 2 +- 6 files changed, 89 insertions(+), 87 deletions(-) rename cmd/safcm/sync_changes.go => frontend/changes.go (76%) rename cmd/safcm/sync_changes_test.go => frontend/changes_test.go (96%) rename {cmd/safcm => frontend}/term.go (97%) rename {cmd/safcm => frontend}/term_test.go (99%) diff --git a/cmd/safcm/sync.go b/cmd/safcm/sync.go index 10edc68..f1877cf 100644 --- a/cmd/safcm/sync.go +++ b/cmd/safcm/sync.go @@ -214,11 +214,11 @@ are only available after the hosts were contacted. func logEvent(x frontend.Event, level safcm.LogLevel, isTTY bool, failed *bool) { // We have multiple event sources so this is somewhat ugly. var prefix, data string - var color Color + var color frontend.Color if x.Error != nil { prefix = "[error]" data = x.Error.Error() - color = ColorRed + color = frontend.ColorRed // We logged an error, tell the caller *failed = true } else if x.Log.Level != 0 { @@ -237,7 +237,7 @@ func logEvent(x frontend.Event, level safcm.LogLevel, isTTY bool, failed *bool) prefix = "[debug2]" default: prefix = fmt.Sprintf("[INVALID=%d]", x.Log.Level) - color = ColorRed + color = frontend.ColorRed } data = x.Log.Text } else { @@ -254,19 +254,19 @@ func logEvent(x frontend.Event, level safcm.LogLevel, isTTY bool, failed *bool) x.ConnEvent.Data = "remote helper upload in progress" default: prefix = fmt.Sprintf("[INVALID=%d]", x.ConnEvent.Type) - color = ColorRed + color = frontend.ColorRed } data = x.ConnEvent.Data } host := x.Host.Name() if color != 0 { - host = ColorString(isTTY, color, host) + host = frontend.ColorString(isTTY, color, host) } // Make sure to escape control characters to prevent terminal // injection attacks if !x.Escaped { - data = EscapeControlCharacters(isTTY, data) + data = frontend.EscapeControlCharacters(isTTY, data) } log.Printf("%-9s [%s] %s", prefix, host, data) } diff --git a/cmd/safcm/sync_sync.go b/cmd/safcm/sync_sync.go index 3f0c30b..7e4d225 100644 --- a/cmd/safcm/sync_sync.go +++ b/cmd/safcm/sync_sync.go @@ -26,6 +26,7 @@ import ( "ruderich.org/simon/safcm" "ruderich.org/simon/safcm/cmd/safcm/config" + "ruderich.org/simon/safcm/frontend" "ruderich.org/simon/safcm/rpc" ) @@ -44,7 +45,12 @@ func (s *Sync) hostSync(conn *rpc.Conn, detectedGroups []string) error { } // Display changes - changes := s.formatChanges(resp) + c := frontend.Changes{ + DryRun: s.config.DryRun, + Quiet: s.config.Quiet, + IsTTY: s.isTTY, + } + changes := c.FormatChanges(resp) if changes != "" { s.log(safcm.LogInfo, true, changes) } diff --git a/cmd/safcm/sync_changes.go b/frontend/changes.go similarity index 76% rename from cmd/safcm/sync_changes.go rename to frontend/changes.go index 2650bb4..351cdfa 100644 --- a/cmd/safcm/sync_changes.go +++ b/frontend/changes.go @@ -1,4 +1,4 @@ -// "sync" sub-command: format changes +// Frontend: Format changes // Copyright (C) 2021 Simon Ruderich // @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -package main +package frontend import ( "fmt" @@ -30,23 +30,29 @@ import ( // the remote helper is untrusted and must be either escaped with %q or by // calling EscapeControlCharacters(). -func (s *Sync) formatChanges(resp safcm.MsgSyncResp) string { +type Changes struct { + DryRun bool + Quiet bool + IsTTY bool +} + +func (c *Changes) FormatChanges(resp safcm.MsgSyncResp) string { var changes []string if len(resp.FileChanges) > 0 { changes = append(changes, - s.formatFileChanges(resp.FileChanges)) + c.FormatFileChanges(resp.FileChanges)) } if len(resp.PackageChanges) > 0 { changes = append(changes, - s.formatPackageChanges(resp.PackageChanges)) + c.FormatPackageChanges(resp.PackageChanges)) } if len(resp.ServiceChanges) > 0 { changes = append(changes, - s.formatServiceChanges(resp.ServiceChanges)) + c.FormatServiceChanges(resp.ServiceChanges)) } if len(resp.CommandChanges) > 0 { changes = append(changes, - s.formatCommandChanges(resp.CommandChanges)) + c.FormatCommandChanges(resp.CommandChanges)) } if len(changes) == 0 { // Notify user that the host was synced successfully @@ -64,30 +70,30 @@ func (s *Sync) formatChanges(resp safcm.MsgSyncResp) string { return "\n" + x } -func (s *Sync) formatFileChanges(changes []safcm.FileChange) string { +func (c *Changes) FormatFileChanges(changes []safcm.FileChange) string { var buf strings.Builder - if s.config.DryRun { + if c.DryRun { fmt.Fprintf(&buf, "will change %d file(s): (dry-run)\n", len(changes)) } else { fmt.Fprintf(&buf, "changed %d file(s):\n", len(changes)) } for _, x := range changes { - fmt.Fprintf(&buf, "%s:", s.formatTarget(x.Path)) + fmt.Fprintf(&buf, "%s:", c.FormatTarget(x.Path)) var info []string if x.Created { info = append(info, - ColorString(s.isTTY, ColorGreen, "created"), - formatFileType(x.New), - formatFileUserGroup(x.New), - formatFilePerm(x.New), + ColorString(c.IsTTY, ColorGreen, "created"), + FormatFileType(x.New), + FormatFileUserGroup(x.New), + FormatFilePerm(x.New), ) } else { if x.Old.Mode.Type() != x.New.Mode.Type() { info = append(info, fmt.Sprintf("%s -> %s", - formatFileType(x.Old), - formatFileType(x.New), + FormatFileType(x.Old), + FormatFileType(x.New), )) } if x.Old.User != x.New.User || @@ -95,15 +101,15 @@ func (s *Sync) formatFileChanges(changes []safcm.FileChange) string { x.Old.Group != x.New.Group || x.Old.Gid != x.New.Gid { info = append(info, fmt.Sprintf("%s -> %s", - formatFileUserGroup(x.Old), - formatFileUserGroup(x.New), + FormatFileUserGroup(x.Old), + FormatFileUserGroup(x.New), )) } if config.FileModeToFullPerm(x.Old.Mode) != config.FileModeToFullPerm(x.New.Mode) { info = append(info, fmt.Sprintf("%s -> %s", - formatFilePerm(x.Old), - formatFilePerm(x.New), + FormatFilePerm(x.Old), + FormatFilePerm(x.New), )) } } @@ -113,13 +119,14 @@ func (s *Sync) formatFileChanges(changes []safcm.FileChange) string { } if x.DataDiff != "" { - fmt.Fprintf(&buf, "\n%s", s.formatDiff(x.DataDiff)) + fmt.Fprintf(&buf, "\n%s", c.FormatDiff(x.DataDiff)) } fmt.Fprintf(&buf, "\n") } return buf.String() } -func formatFileType(info safcm.FileChangeInfo) string { + +func FormatFileType(info safcm.FileChangeInfo) string { switch info.Mode.Type() { case 0: // regular file return "file" @@ -131,18 +138,18 @@ func formatFileType(info safcm.FileChangeInfo) string { return fmt.Sprintf("invalid type %v", info.Mode.Type()) } } -func formatFileUserGroup(info safcm.FileChangeInfo) string { +func FormatFileUserGroup(info safcm.FileChangeInfo) string { return fmt.Sprintf("%s(%d) %s(%d)", EscapeControlCharacters(false, info.User), info.Uid, EscapeControlCharacters(false, info.Group), info.Gid) } -func formatFilePerm(info safcm.FileChangeInfo) string { +func FormatFilePerm(info safcm.FileChangeInfo) string { return fmt.Sprintf("%#o", config.FileModeToFullPerm(info.Mode)) } -func (s *Sync) formatPackageChanges(changes []safcm.PackageChange) string { +func (c *Changes) FormatPackageChanges(changes []safcm.PackageChange) string { var buf strings.Builder - if s.config.DryRun { + if c.DryRun { fmt.Fprintf(&buf, "will install %d package(s): (dry-run)\n", len(changes)) } else { @@ -150,14 +157,14 @@ func (s *Sync) formatPackageChanges(changes []safcm.PackageChange) string { } for _, x := range changes { // TODO: indicate if installation failed - fmt.Fprintf(&buf, "%s\n", s.formatTarget(x.Name)) + fmt.Fprintf(&buf, "%s\n", c.FormatTarget(x.Name)) } return buf.String() } -func (s *Sync) formatServiceChanges(changes []safcm.ServiceChange) string { +func (c *Changes) FormatServiceChanges(changes []safcm.ServiceChange) string { var buf strings.Builder - if s.config.DryRun { + if c.DryRun { fmt.Fprintf(&buf, "will modify %d service(s): (dry-run)\n", len(changes)) } else { @@ -172,13 +179,13 @@ func (s *Sync) formatServiceChanges(changes []safcm.ServiceChange) string { info = append(info, "enabled") } fmt.Fprintf(&buf, "%s: %s\n", - s.formatTarget(x.Name), + c.FormatTarget(x.Name), strings.Join(info, ", ")) } return buf.String() } -func (s *Sync) formatCommandChanges(changes []safcm.CommandChange) string { +func (c *Changes) FormatCommandChanges(changes []safcm.CommandChange) string { const indent = " > " // Quiet hides all successful, non-trigger commands which produce no @@ -188,7 +195,7 @@ func (s *Sync) formatCommandChanges(changes []safcm.CommandChange) string { // Instead, quiet shows them only when they produce output (e.g. // `ainsl`, `rm -v`) and thus modify the host's state. var noOutput int - if s.config.Quiet { + if c.Quiet { for _, x := range changes { if x.Trigger == "" && x.Error == "" && @@ -199,18 +206,18 @@ func (s *Sync) formatCommandChanges(changes []safcm.CommandChange) string { } var buf strings.Builder - if s.config.DryRun { + if c.DryRun { fmt.Fprintf(&buf, "will execute %d command(s)", len(changes)) } else { fmt.Fprintf(&buf, "executed %d command(s)", len(changes)) } - if noOutput > 0 && !s.config.DryRun { + if noOutput > 0 && !c.DryRun { fmt.Fprintf(&buf, ", %d with no output (hidden)", noOutput) } if noOutput != len(changes) { fmt.Fprintf(&buf, ":") } - if s.config.DryRun { + if c.DryRun { fmt.Fprintf(&buf, " (dry-run)") } fmt.Fprintf(&buf, "\n") @@ -220,7 +227,7 @@ func (s *Sync) formatCommandChanges(changes []safcm.CommandChange) string { continue } - fmt.Fprintf(&buf, "%s", s.formatTarget(x.Command)) + fmt.Fprintf(&buf, "%s", c.FormatTarget(x.Command)) if x.Trigger != "" { fmt.Fprintf(&buf, ", trigger for %q", x.Trigger) } @@ -231,34 +238,34 @@ func (s *Sync) formatCommandChanges(changes []safcm.CommandChange) string { // TODO: truncate very large outputs? x := indentBlock(x.Output, indent) fmt.Fprintf(&buf, ":\n%s", - EscapeControlCharacters(s.isTTY, x)) + EscapeControlCharacters(c.IsTTY, x)) } fmt.Fprintf(&buf, "\n") } return buf.String() } -func (s *Sync) formatTarget(x string) string { +func (c *Changes) FormatTarget(x string) string { x = fmt.Sprintf("%q", x) // escape! - return ColorString(s.isTTY, ColorCyan, x) + return ColorString(c.IsTTY, ColorCyan, x) } -func (s *Sync) formatDiff(diff string) string { +func (c *Changes) FormatDiff(diff string) string { const indent = " " diff = indentBlock(diff, indent) // Never color diff content as we want to color the whole diff diff = EscapeControlCharacters(false, diff) - if !s.isTTY { + if !c.IsTTY { return diff } var res []string for _, x := range strings.Split(diff, "\n") { if strings.HasPrefix(x, indent+"+") { - x = ColorString(s.isTTY, ColorGreen, x) + x = ColorString(c.IsTTY, ColorGreen, x) } else if strings.HasPrefix(x, indent+"-") { - x = ColorString(s.isTTY, ColorRed, x) + x = ColorString(c.IsTTY, ColorRed, x) } res = append(res, x) } diff --git a/cmd/safcm/sync_changes_test.go b/frontend/changes_test.go similarity index 96% rename from cmd/safcm/sync_changes_test.go rename to frontend/changes_test.go index 219373c..02a95c4 100644 --- a/cmd/safcm/sync_changes_test.go +++ b/frontend/changes_test.go @@ -13,14 +13,13 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -package main +package frontend import ( "io/fs" "testing" "ruderich.org/simon/safcm" - "ruderich.org/simon/safcm/cmd/safcm/config" "ruderich.org/simon/safcm/testutil" ) @@ -157,15 +156,13 @@ func TestFormatChanges(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - s := &Sync{ - config: &config.Config{ - DryRun: tc.dryRun, - Quiet: tc.quiet, - }, - isTTY: tc.isTTY, + c := Changes{ + DryRun: tc.dryRun, + Quiet: tc.quiet, + IsTTY: tc.isTTY, } - res := s.formatChanges(tc.resp) + res := c.FormatChanges(tc.resp) testutil.AssertEqual(t, "res", res, tc.exp) }) } @@ -656,14 +653,12 @@ func TestFormatFileChanges(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - s := &Sync{ - config: &config.Config{ - DryRun: tc.dryRun, - }, - isTTY: tc.isTTY, + c := Changes{ + DryRun: tc.dryRun, + IsTTY: tc.isTTY, } - res := s.formatFileChanges(tc.changes) + res := c.FormatFileChanges(tc.changes) testutil.AssertEqual(t, "res", res, tc.exp) }) } @@ -773,14 +768,12 @@ func TestFormatPackageChanges(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - s := &Sync{ - config: &config.Config{ - DryRun: tc.dryRun, - }, - isTTY: tc.isTTY, + c := Changes{ + DryRun: tc.dryRun, + IsTTY: tc.isTTY, } - res := s.formatPackageChanges(tc.changes) + res := c.FormatPackageChanges(tc.changes) testutil.AssertEqual(t, "res", res, tc.exp) }) } @@ -931,14 +924,12 @@ func TestFormatServiceChanges(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - s := &Sync{ - config: &config.Config{ - DryRun: tc.dryRun, - }, - isTTY: tc.isTTY, + c := Changes{ + DryRun: tc.dryRun, + IsTTY: tc.isTTY, } - res := s.formatServiceChanges(tc.changes) + res := c.FormatServiceChanges(tc.changes) testutil.AssertEqual(t, "res", res, tc.exp) }) } @@ -1230,15 +1221,13 @@ func TestFormatCommandChanges(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - s := &Sync{ - config: &config.Config{ - DryRun: tc.dryRun, - Quiet: tc.quiet, - }, - isTTY: tc.isTTY, + c := Changes{ + DryRun: tc.dryRun, + Quiet: tc.quiet, + IsTTY: tc.isTTY, } - res := s.formatCommandChanges(tc.changes) + res := c.FormatCommandChanges(tc.changes) testutil.AssertEqual(t, "res", res, tc.exp) }) } diff --git a/cmd/safcm/term.go b/frontend/term.go similarity index 97% rename from cmd/safcm/term.go rename to frontend/term.go index 47f7d62..f50f162 100644 --- a/cmd/safcm/term.go +++ b/frontend/term.go @@ -1,4 +1,4 @@ -// Functions for terminal output +// Frontend: Functions for terminal output // Copyright (C) 2021 Simon Ruderich // @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -package main +package frontend import ( "fmt" diff --git a/cmd/safcm/term_test.go b/frontend/term_test.go similarity index 99% rename from cmd/safcm/term_test.go rename to frontend/term_test.go index 479d7e8..7569a99 100644 --- a/cmd/safcm/term_test.go +++ b/frontend/term_test.go @@ -13,7 +13,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -package main +package frontend import ( "testing" -- 2.43.2