From 61e15160fe72953cb3772c1f79d6c6d570b8684d Mon Sep 17 00:00:00 2001 From: mostlikelee Date: Mon, 11 Nov 2024 15:36:50 -0700 Subject: [PATCH] abstract to dialog --- orbit/pkg/dialog/dialog.go | 55 ++++++++++++++++++++++++++++++++ orbit/pkg/zenity/zenity.go | 51 ++++++----------------------- orbit/pkg/zenity/zenity_test.go | 29 +++++++++-------- tools/{zenity => dialog}/main.go | 5 +-- 4 files changed, 82 insertions(+), 58 deletions(-) create mode 100644 orbit/pkg/dialog/dialog.go rename tools/{zenity => dialog}/main.go (77%) diff --git a/orbit/pkg/dialog/dialog.go b/orbit/pkg/dialog/dialog.go new file mode 100644 index 000000000000..bd899b06dacb --- /dev/null +++ b/orbit/pkg/dialog/dialog.go @@ -0,0 +1,55 @@ +package dialog + +// Dialog represents a UI dialog that can be displayed to the end user +// on a host + +import ( + "context" + "errors" + "time" +) + +var ( + // ErrCanceled is returned when the dialog is canceled by the cancel button. + ErrCanceled = errors.New("dialog canceled") + // ErrTimeout is returned when the dialog is automatically closed due to a timeout. + ErrTimeout = errors.New("dialog timed out") + // ErrUnknown is returned when an unknown error occurs. + ErrUnknown = errors.New("unknown error") +) + +type Dialog interface { + // ShowEntry displays a dialog that accepts end user input. It returns the entered + // text or errors ErrCanceled, ErrTimeout, or ErrUnknown. + ShowEntry(ctx context.Context, opts EntryOptions) ([]byte, error) + // ShowInfo displays a dialog that displays information. It returns an error if the dialog + // could not be displayed. + ShowInfo(ctx context.Context, opts InfoOptions) error +} + +// EntryOptions represents options for a dialog that accepts end user input. +type EntryOptions struct { + // Title sets the title of the dialog. + Title string + + // Text sets the text of the dialog. + Text string + + // HideText hides the text entered by the user. + HideText bool + + // TimeOut sets the time in seconds before the dialog is automatically closed. + TimeOut time.Duration +} + +// InfoOptions represents options for a dialog that displays information. +type InfoOptions struct { + // Title sets the title of the dialog. + Title string + + // Text sets the text of the dialog. + Text string + + // Timeout sets the time in seconds before the dialog is automatically closed. + TimeOut time.Duration +} diff --git a/orbit/pkg/zenity/zenity.go b/orbit/pkg/zenity/zenity.go index eee3affb6831..c4b565677b6d 100644 --- a/orbit/pkg/zenity/zenity.go +++ b/orbit/pkg/zenity/zenity.go @@ -2,10 +2,9 @@ package zenity import ( "context" - "errors" "fmt" - "time" + "github.com/fleetdm/fleet/v4/orbit/pkg/dialog" "github.com/fleetdm/fleet/v4/orbit/pkg/execuser" "github.com/fleetdm/fleet/v4/server/contexts/ctxerr" ) @@ -15,40 +14,8 @@ type Zenity struct { execCmdFn func(ctx context.Context, args ...string) ([]byte, int, error) } -var ( - ErrCanceled = errors.New("dialog canceled") - ErrTimeout = errors.New("dialog timed out") - ErrUnknown = errors.New("unknown error") -) - -// EntryOptions represents options for the Entry dialog. -type EntryOptions struct { - // Title sets the title of the dialog. - Title string - - // Text sets the text of the dialog. - Text string - - // HideText hides the text entered by the user. - HideText bool - - // TimeOut sets the time in seconds before the dialog is automatically closed. - TimeOut time.Duration -} - -// InfoOptions represents options for the Info dialog. -type InfoOptions struct { - // Title sets the title of the dialog. - Title string - - // Text sets the text of the dialog. - Text string - - // Timeout sets the time in seconds before the dialog is automatically closed. - TimeOut time.Duration -} - // NewZenity creates a new Zenity dialog instance for zenity v4 on Linux. +// Zenity implements the Dialog interface. func New() *Zenity { return &Zenity{ execCmdFn: execCmd, @@ -57,7 +24,7 @@ func New() *Zenity { // ShowEntry displays an dialog that accepts end user input. It returns the entered // text or errors ErrCanceled, ErrTimeout, or ErrUnknown. -func (z *Zenity) ShowEntry(ctx context.Context, opts EntryOptions) ([]byte, error) { +func (z *Zenity) ShowEntry(ctx context.Context, opts dialog.EntryOptions) ([]byte, error) { args := []string{"--entry"} if opts.Title != "" { args = append(args, fmt.Sprintf("--title=%s", opts.Title)) @@ -76,11 +43,11 @@ func (z *Zenity) ShowEntry(ctx context.Context, opts EntryOptions) ([]byte, erro if err != nil { switch statusCode { case 1: - return nil, ctxerr.Wrap(ctx, ErrCanceled) + return nil, ctxerr.Wrap(ctx, dialog.ErrCanceled) case 5: - return nil, ctxerr.Wrap(ctx, ErrTimeout) + return nil, ctxerr.Wrap(ctx, dialog.ErrTimeout) default: - return nil, ctxerr.Wrap(ctx, ErrUnknown, err.Error()) + return nil, ctxerr.Wrap(ctx, dialog.ErrUnknown, err.Error()) } } @@ -88,7 +55,7 @@ func (z *Zenity) ShowEntry(ctx context.Context, opts EntryOptions) ([]byte, erro } // ShowInfo displays an information dialog. It returns errors ErrTimeout or ErrUnknown. -func (z *Zenity) ShowInfo(ctx context.Context, opts InfoOptions) error { +func (z *Zenity) ShowInfo(ctx context.Context, opts dialog.InfoOptions) error { args := []string{"--info"} if opts.Title != "" { args = append(args, fmt.Sprintf("--title=%s", opts.Title)) @@ -104,9 +71,9 @@ func (z *Zenity) ShowInfo(ctx context.Context, opts InfoOptions) error { if err != nil { switch statusCode { case 5: - return ctxerr.Wrap(ctx, ErrTimeout) + return ctxerr.Wrap(ctx, dialog.ErrTimeout) default: - return ctxerr.Wrap(ctx, ErrUnknown, err.Error()) + return ctxerr.Wrap(ctx, dialog.ErrUnknown, err.Error()) } } diff --git a/orbit/pkg/zenity/zenity_test.go b/orbit/pkg/zenity/zenity_test.go index 782856aa5aa1..9312d4412219 100644 --- a/orbit/pkg/zenity/zenity_test.go +++ b/orbit/pkg/zenity/zenity_test.go @@ -6,6 +6,7 @@ import ( "testing" "time" + "github.com/fleetdm/fleet/v4/orbit/pkg/dialog" "github.com/stretchr/testify/require" "github.com/tj/assert" ) @@ -32,12 +33,12 @@ func TestShowEntryArgs(t *testing.T) { testCases := []struct { name string - opts EntryOptions + opts dialog.EntryOptions expectedArgs []string }{ { name: "Basic Entry", - opts: EntryOptions{ + opts: dialog.EntryOptions{ Title: "A Title", Text: "Some text", }, @@ -45,7 +46,7 @@ func TestShowEntryArgs(t *testing.T) { }, { name: "All Options", - opts: EntryOptions{ + opts: dialog.EntryOptions{ Title: "Another Title", Text: "Some more text", HideText: true, @@ -82,17 +83,17 @@ func TestShowEntryError(t *testing.T) { { name: "Dialog Cancelled", exitCode: 1, - expectedErr: ErrCanceled, + expectedErr: dialog.ErrCanceled, }, { name: "Dialog Timed Out", exitCode: 5, - expectedErr: ErrTimeout, + expectedErr: dialog.ErrTimeout, }, { name: "Unknown Error", exitCode: 99, - expectedErr: ErrUnknown, + expectedErr: dialog.ErrUnknown, }, } @@ -104,7 +105,7 @@ func TestShowEntryError(t *testing.T) { z := &Zenity{ execCmdFn: mock.run, } - output, err := z.ShowEntry(ctx, EntryOptions{}) + output, err := z.ShowEntry(ctx, dialog.EntryOptions{}) require.ErrorIs(t, err, tt.expectedErr) assert.Nil(t, output) }) @@ -120,7 +121,7 @@ func TestShowEntrySuccess(t *testing.T) { z := &Zenity{ execCmdFn: mock.run, } - output, err := z.ShowEntry(ctx, EntryOptions{}) + output, err := z.ShowEntry(ctx, dialog.EntryOptions{}) assert.NoError(t, err) assert.Equal(t, []byte("some output"), output) } @@ -130,17 +131,17 @@ func TestShowInfoArgs(t *testing.T) { testCases := []struct { name string - opts InfoOptions + opts dialog.InfoOptions expectedArgs []string }{ { name: "Basic Entry", - opts: InfoOptions{}, + opts: dialog.InfoOptions{}, expectedArgs: []string{"--info"}, }, { name: "All Options", - opts: InfoOptions{ + opts: dialog.InfoOptions{ Title: "Another Title", Text: "Some more text", TimeOut: 1 * time.Minute, @@ -173,12 +174,12 @@ func TestShowInfoError(t *testing.T) { { name: "Dialog Timed Out", exitCode: 5, - expectedErr: ErrTimeout, + expectedErr: dialog.ErrTimeout, }, { name: "Unknown Error", exitCode: 99, - expectedErr: ErrUnknown, + expectedErr: dialog.ErrUnknown, }, } @@ -190,7 +191,7 @@ func TestShowInfoError(t *testing.T) { z := &Zenity{ execCmdFn: mock.run, } - err := z.ShowInfo(ctx, InfoOptions{}) + err := z.ShowInfo(ctx, dialog.InfoOptions{}) require.ErrorIs(t, err, tt.expectedErr) }) } diff --git a/tools/zenity/main.go b/tools/dialog/main.go similarity index 77% rename from tools/zenity/main.go rename to tools/dialog/main.go index cafe5b9bc5b9..1117c2b2b262 100644 --- a/tools/zenity/main.go +++ b/tools/dialog/main.go @@ -5,6 +5,7 @@ import ( "fmt" "time" + "github.com/fleetdm/fleet/v4/orbit/pkg/dialog" "github.com/fleetdm/fleet/v4/orbit/pkg/zenity" ) @@ -12,7 +13,7 @@ func main() { prompt := zenity.New() ctx := context.Background() - output, err := prompt.ShowEntry(ctx, zenity.EntryOptions{ + output, err := prompt.ShowEntry(ctx, dialog.EntryOptions{ Title: "Zenity Test Entry Title", Text: "Zenity Test Entry Text", HideText: true, @@ -23,7 +24,7 @@ func main() { panic(err) } - err = prompt.ShowInfo(ctx, zenity.InfoOptions{ + err = prompt.ShowInfo(ctx, dialog.InfoOptions{ Title: "Zenity Test Info Title", Text: "Result: " + string(output), TimeOut: 10 * time.Second,