add kdialog for kubuntu key escrow (#24405)
This commit is contained in:
@@ -0,0 +1 @@
|
||||
* added support for kdialog linux key escrow prompts for compatibility with kubuntu systems
|
||||
@@ -39,7 +39,6 @@ import (
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/update"
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/update/filestore"
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/user"
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/zenity"
|
||||
"github.com/fleetdm/fleet/v4/pkg/certificate"
|
||||
"github.com/fleetdm/fleet/v4/pkg/file"
|
||||
retrypkg "github.com/fleetdm/fleet/v4/pkg/retry"
|
||||
@@ -938,7 +937,7 @@ func main() {
|
||||
orbitClient.RegisterConfigReceiver(update.ApplyWindowsMDMEnrollmentFetcherMiddleware(windowsMDMEnrollmentCommandFrequency, orbitHostInfo.HardwareUUID, orbitClient))
|
||||
orbitClient.RegisterConfigReceiver(update.ApplyWindowsMDMBitlockerFetcherMiddleware(windowsMDMBitlockerCommandFrequency, orbitClient))
|
||||
case "linux":
|
||||
orbitClient.RegisterConfigReceiver(luks.New(orbitClient, zenity.New()))
|
||||
orbitClient.RegisterConfigReceiver(luks.New(orbitClient))
|
||||
}
|
||||
|
||||
flagUpdateReceiver := update.NewFlagReceiver(orbitClient.TriggerOrbitRestart, update.FlagUpdateOptions{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package dialog
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
@@ -20,10 +19,10 @@ var (
|
||||
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)
|
||||
ShowEntry(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
|
||||
ShowInfo(opts InfoOptions) error
|
||||
// Progress displays a dialog that shows progress. It waits until the
|
||||
// context is cancelled.
|
||||
ShowProgress(opts ProgressOptions) (cancelFunc func() error, err error)
|
||||
|
||||
@@ -2,12 +2,16 @@
|
||||
// SYSTEM service on Windows) as the current login user.
|
||||
package execuser
|
||||
|
||||
import "io"
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
type eopts struct {
|
||||
env [][2]string
|
||||
args [][2]string
|
||||
stderrPath string //nolint:structcheck,unused
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
// Option allows configuring the application.
|
||||
@@ -27,6 +31,13 @@ func WithArg(name, value string) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// WithTimeout sets the timeout for the application. Currently only supported on Linux.
|
||||
func WithTimeout(duration time.Duration) Option {
|
||||
return func(a *eopts) {
|
||||
a.timeout = duration
|
||||
}
|
||||
}
|
||||
|
||||
// Run runs an application as the current login user.
|
||||
// It assumes the caller is running with high privileges (root on Unix, SYSTEM on Windows).
|
||||
//
|
||||
|
||||
@@ -64,7 +64,16 @@ func runWithOutput(path string, opts eopts) (output []byte, exitCode int, err er
|
||||
}
|
||||
}
|
||||
|
||||
cmd := exec.Command("sudo", args...)
|
||||
// Prefix with "timeout" and "sudo" if applicable
|
||||
var cmdArgs []string
|
||||
if opts.timeout > 0 {
|
||||
cmdArgs = append(cmdArgs, "timeout", fmt.Sprintf("%ds", int(opts.timeout.Seconds())))
|
||||
}
|
||||
cmdArgs = append(cmdArgs, "sudo")
|
||||
cmdArgs = append(cmdArgs, args...)
|
||||
|
||||
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...) // #nosec G204
|
||||
|
||||
log.Printf("cmd=%s", cmd.String())
|
||||
|
||||
output, err = cmd.Output()
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
package kdialog
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/dialog"
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/execuser"
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/platform"
|
||||
)
|
||||
|
||||
const kdialogProcessName = "kdialog"
|
||||
|
||||
type KDialog struct {
|
||||
cmdWithOutput func(timeout time.Duration, args ...string) ([]byte, int, error)
|
||||
cmdWithCancel func(args ...string) (cancelFunc func() error, err error)
|
||||
}
|
||||
|
||||
func New() *KDialog {
|
||||
return &KDialog{
|
||||
cmdWithOutput: execCmdWithOutput,
|
||||
cmdWithCancel: execCmdWithCancel,
|
||||
}
|
||||
}
|
||||
|
||||
func (k *KDialog) ShowEntry(opts dialog.EntryOptions) ([]byte, error) {
|
||||
args := []string{"--password"}
|
||||
if opts.Text != "" {
|
||||
args = append(args, opts.Text)
|
||||
}
|
||||
if opts.Title != "" {
|
||||
args = append(args, "--title", opts.Title)
|
||||
}
|
||||
|
||||
output, statusCode, err := k.cmdWithOutput(opts.TimeOut, args...)
|
||||
if err != nil {
|
||||
switch statusCode {
|
||||
case 1:
|
||||
return nil, dialog.ErrCanceled
|
||||
case 124:
|
||||
return nil, dialog.ErrTimeout
|
||||
default:
|
||||
return nil, errors.Join(dialog.ErrUnknown, err)
|
||||
}
|
||||
}
|
||||
|
||||
output = []byte(strings.TrimSuffix(string(output), "\n"))
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func (k *KDialog) ShowProgress(opts dialog.ProgressOptions) (func() error, error) {
|
||||
args := []string{"--msgbox"}
|
||||
if opts.Text != "" {
|
||||
args = append(args, opts.Text)
|
||||
}
|
||||
if opts.Title != "" {
|
||||
args = append(args, "--title", opts.Title)
|
||||
}
|
||||
|
||||
cancel, err := k.cmdWithCancel(args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cancel, nil
|
||||
}
|
||||
|
||||
func (k *KDialog) ShowInfo(opts dialog.InfoOptions) error {
|
||||
args := []string{"--msgbox"}
|
||||
if opts.Text != "" {
|
||||
args = append(args, opts.Text)
|
||||
}
|
||||
if opts.Title != "" {
|
||||
args = append(args, "--title", opts.Title)
|
||||
}
|
||||
|
||||
_, statusCode, err := k.cmdWithOutput(opts.TimeOut, args...)
|
||||
if err != nil {
|
||||
switch statusCode {
|
||||
case 124:
|
||||
return dialog.ErrTimeout
|
||||
default:
|
||||
return errors.Join(dialog.ErrUnknown, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func execCmdWithOutput(timeout time.Duration, args ...string) ([]byte, int, error) {
|
||||
var opts []execuser.Option
|
||||
for _, arg := range args {
|
||||
opts = append(opts, execuser.WithArg(arg, "")) // using empty value for positional args
|
||||
}
|
||||
|
||||
if timeout > 0 {
|
||||
opts = append(opts, execuser.WithTimeout(timeout))
|
||||
}
|
||||
|
||||
output, exitCode, err := execuser.RunWithOutput(kdialogProcessName, opts...)
|
||||
if err != nil {
|
||||
return nil, exitCode, err
|
||||
}
|
||||
|
||||
return output, exitCode, nil
|
||||
}
|
||||
|
||||
func execCmdWithCancel(args ...string) (func() error, error) {
|
||||
var opts []execuser.Option
|
||||
for _, arg := range args {
|
||||
opts = append(opts, execuser.WithArg(arg, "")) // using empty value for positional args
|
||||
}
|
||||
|
||||
_, err := execuser.Run(kdialogProcessName, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
killFunc := func() error {
|
||||
if _, err := platform.KillAllProcessByName(kdialogProcessName); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return killFunc, nil
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
package kdialog
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/dialog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type mockExecCmd struct {
|
||||
output []byte
|
||||
exitCode int
|
||||
capturedArgs []string
|
||||
err error
|
||||
}
|
||||
|
||||
func (m *mockExecCmd) runWithOutput(timeout time.Duration, args ...string) ([]byte, int, error) {
|
||||
m.capturedArgs = append(m.capturedArgs, args...)
|
||||
|
||||
if m.exitCode != 0 {
|
||||
return nil, m.exitCode, &exec.ExitError{}
|
||||
}
|
||||
|
||||
if m.err != nil {
|
||||
return nil, m.exitCode, m.err
|
||||
}
|
||||
|
||||
return m.output, m.exitCode, nil
|
||||
}
|
||||
|
||||
func (m *mockExecCmd) runWithCancel(args ...string) (cancelFunc func() error, err error) {
|
||||
m.capturedArgs = append(m.capturedArgs, args...)
|
||||
|
||||
if m.err != nil {
|
||||
return nil, m.err
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func TestShowEntryArgs(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
opts dialog.EntryOptions
|
||||
expectedArgs []string
|
||||
}{
|
||||
{
|
||||
name: "Basic Entry",
|
||||
opts: dialog.EntryOptions{
|
||||
Title: "A Title",
|
||||
Text: "Some text",
|
||||
},
|
||||
expectedArgs: []string{"--password", "Some text", "--title", "A Title"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mock := &mockExecCmd{
|
||||
output: []byte("some output"),
|
||||
}
|
||||
k := &KDialog{
|
||||
cmdWithOutput: mock.runWithOutput,
|
||||
}
|
||||
output, err := k.ShowEntry(tt.opts)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expectedArgs, mock.capturedArgs)
|
||||
assert.Equal(t, []byte("some output"), output)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestShowEntryError(t *testing.T) {
|
||||
mock := &mockExecCmd{
|
||||
exitCode: 1,
|
||||
}
|
||||
k := &KDialog{
|
||||
cmdWithOutput: mock.runWithOutput,
|
||||
}
|
||||
_, err := k.ShowEntry(dialog.EntryOptions{})
|
||||
assert.Error(t, err)
|
||||
assert.ErrorIs(t, err, dialog.ErrCanceled)
|
||||
|
||||
mock = &mockExecCmd{
|
||||
exitCode: 124,
|
||||
}
|
||||
k = &KDialog{
|
||||
cmdWithOutput: mock.runWithOutput,
|
||||
}
|
||||
_, err = k.ShowEntry(dialog.EntryOptions{})
|
||||
assert.Error(t, err)
|
||||
assert.ErrorIs(t, err, dialog.ErrTimeout)
|
||||
|
||||
mock = &mockExecCmd{
|
||||
exitCode: 2,
|
||||
}
|
||||
k = &KDialog{
|
||||
cmdWithOutput: mock.runWithOutput,
|
||||
}
|
||||
_, err = k.ShowEntry(dialog.EntryOptions{})
|
||||
assert.Error(t, err)
|
||||
assert.ErrorIs(t, err, dialog.ErrUnknown)
|
||||
}
|
||||
|
||||
func TestShowInfoArgs(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
opts dialog.InfoOptions
|
||||
expectedArgs []string
|
||||
}{
|
||||
{
|
||||
name: "Basic Info",
|
||||
opts: dialog.InfoOptions{
|
||||
Title: "A Title",
|
||||
Text: "Some text",
|
||||
},
|
||||
expectedArgs: []string{"--msgbox", "Some text", "--title", "A Title"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mock := &mockExecCmd{}
|
||||
k := &KDialog{
|
||||
cmdWithOutput: mock.runWithOutput,
|
||||
}
|
||||
err := k.ShowInfo(tt.opts)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expectedArgs, mock.capturedArgs)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestShowInfoError(t *testing.T) {
|
||||
testcases := []struct {
|
||||
name string
|
||||
exitCode int
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "Dialog Timed Out",
|
||||
exitCode: 124,
|
||||
expectedErr: dialog.ErrTimeout,
|
||||
},
|
||||
{
|
||||
name: "Unknown Error",
|
||||
exitCode: 99,
|
||||
expectedErr: dialog.ErrUnknown,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range testcases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mock := &mockExecCmd{
|
||||
exitCode: tt.exitCode,
|
||||
}
|
||||
k := &KDialog{
|
||||
cmdWithOutput: mock.runWithOutput,
|
||||
}
|
||||
err := k.ShowInfo(dialog.InfoOptions{})
|
||||
assert.ErrorIs(t, err, tt.expectedErr)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestShowProgressArgs(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
opts dialog.ProgressOptions
|
||||
expectedArgs []string
|
||||
}{
|
||||
{
|
||||
name: "Basic Progress",
|
||||
opts: dialog.ProgressOptions{
|
||||
Title: "A Title",
|
||||
Text: "Some text",
|
||||
},
|
||||
expectedArgs: []string{"--msgbox", "Some text", "--title", "A Title"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mock := &mockExecCmd{}
|
||||
k := &KDialog{
|
||||
cmdWithCancel: mock.runWithCancel,
|
||||
}
|
||||
_, err := k.ShowProgress(tt.opts)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expectedArgs, mock.capturedArgs)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ type KeyEscrower interface {
|
||||
|
||||
type LuksRunner struct {
|
||||
escrower KeyEscrower
|
||||
notifier dialog.Dialog
|
||||
notifier dialog.Dialog //nolint:structcheck,unused
|
||||
}
|
||||
|
||||
type LuksResponse struct {
|
||||
@@ -29,9 +29,8 @@ type LuksResponse struct {
|
||||
Err string
|
||||
}
|
||||
|
||||
func New(escrower KeyEscrower, notifier dialog.Dialog) *LuksRunner {
|
||||
func New(escrower KeyEscrower) *LuksRunner {
|
||||
return &LuksRunner{
|
||||
escrower: escrower,
|
||||
notifier: notifier,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/dialog"
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/kdialog"
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/lvm"
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/zenity"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/siderolabs/go-blockdevice/v2/encryption"
|
||||
@@ -35,6 +37,14 @@ const (
|
||||
|
||||
var ErrKeySlotFull = regexp.MustCompile(`Key slot \d+ is full`)
|
||||
|
||||
func isInstalled(toolName string) bool {
|
||||
path, err := exec.LookPath(toolName)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return path != ""
|
||||
}
|
||||
|
||||
func (lr *LuksRunner) Run(oc *fleet.OrbitConfig) error {
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -42,6 +52,19 @@ func (lr *LuksRunner) Run(oc *fleet.OrbitConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !isInstalled("cryptsetup") {
|
||||
return errors.New("cryptsetup is not installed")
|
||||
}
|
||||
|
||||
switch {
|
||||
case isInstalled("zenity"):
|
||||
lr.notifier = zenity.New()
|
||||
case isInstalled("kdialog"):
|
||||
lr.notifier = kdialog.New()
|
||||
default:
|
||||
return errors.New("No supported dialog tool found")
|
||||
}
|
||||
|
||||
devicePath, err := lvm.FindRootDisk()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to find LUKS Root Partition: %w", err)
|
||||
@@ -81,7 +104,7 @@ func (lr *LuksRunner) Run(oc *fleet.OrbitConfig) error {
|
||||
}
|
||||
|
||||
// Show error in dialog
|
||||
if err := lr.infoPrompt(ctx, infoTitle, infoFailedText); err != nil {
|
||||
if err := lr.infoPrompt(infoTitle, infoFailedText); err != nil {
|
||||
log.Info().Err(err).Msg("failed to show failed escrow key dialog")
|
||||
}
|
||||
|
||||
@@ -89,14 +112,14 @@ func (lr *LuksRunner) Run(oc *fleet.OrbitConfig) error {
|
||||
}
|
||||
|
||||
if response.Err != "" {
|
||||
if err := lr.infoPrompt(ctx, infoTitle, response.Err); err != nil {
|
||||
if err := lr.infoPrompt(infoTitle, response.Err); err != nil {
|
||||
log.Info().Err(err).Msg("failed to show response error dialog")
|
||||
}
|
||||
return fmt.Errorf("error getting linux escrow key: %s", response.Err)
|
||||
}
|
||||
|
||||
// Show success dialog
|
||||
if err := lr.infoPrompt(ctx, infoTitle, infoSuccessText); err != nil {
|
||||
if err := lr.infoPrompt(infoTitle, infoSuccessText); err != nil {
|
||||
log.Info().Err(err).Msg("failed to show success escrow key dialog")
|
||||
}
|
||||
|
||||
@@ -251,7 +274,7 @@ func generateRandomPassphrase() ([]byte, error) {
|
||||
}
|
||||
|
||||
func (lr *LuksRunner) entryPrompt(ctx context.Context, title, text string) ([]byte, error) {
|
||||
passphrase, err := lr.notifier.ShowEntry(ctx, dialog.EntryOptions{
|
||||
passphrase, err := lr.notifier.ShowEntry(dialog.EntryOptions{
|
||||
Title: title,
|
||||
Text: text,
|
||||
HideText: true,
|
||||
@@ -264,7 +287,7 @@ func (lr *LuksRunner) entryPrompt(ctx context.Context, title, text string) ([]by
|
||||
return nil, nil
|
||||
case errors.Is(err, dialog.ErrTimeout):
|
||||
log.Debug().Msg("key escrow dialog timed out")
|
||||
err := lr.infoPrompt(ctx, infoTitle, timeoutMessage)
|
||||
err := lr.infoPrompt(infoTitle, timeoutMessage)
|
||||
if err != nil {
|
||||
log.Info().Err(err).Msg("failed to show timeout dialog")
|
||||
}
|
||||
@@ -279,8 +302,8 @@ func (lr *LuksRunner) entryPrompt(ctx context.Context, title, text string) ([]by
|
||||
return passphrase, nil
|
||||
}
|
||||
|
||||
func (lr *LuksRunner) infoPrompt(ctx context.Context, title, text string) error {
|
||||
err := lr.notifier.ShowInfo(ctx, dialog.InfoOptions{
|
||||
func (lr *LuksRunner) infoPrompt(title, text string) error {
|
||||
err := lr.notifier.ShowInfo(dialog.InfoOptions{
|
||||
Title: title,
|
||||
Text: text,
|
||||
TimeOut: 1 * time.Minute,
|
||||
|
||||
+10
-11
@@ -2,19 +2,18 @@ package zenity
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/dialog"
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/execuser"
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
||||
)
|
||||
|
||||
const zenityProcessName = "zenity"
|
||||
|
||||
type Zenity struct {
|
||||
// cmdWithOutput can be set in tests to mock execution of the dialog.
|
||||
cmdWithOutput func(ctx context.Context, args ...string) ([]byte, int, error)
|
||||
cmdWithOutput func(args ...string) ([]byte, int, error)
|
||||
// cmdWithWait can be set in tests to mock execution of the dialog.
|
||||
cmdWithCancel func(args ...string) (func() error, error)
|
||||
}
|
||||
@@ -30,7 +29,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 dialog.EntryOptions) ([]byte, error) {
|
||||
func (z *Zenity) ShowEntry(opts dialog.EntryOptions) ([]byte, error) {
|
||||
args := []string{"--entry"}
|
||||
if opts.Title != "" {
|
||||
args = append(args, fmt.Sprintf("--title=%s", opts.Title))
|
||||
@@ -45,7 +44,7 @@ func (z *Zenity) ShowEntry(ctx context.Context, opts dialog.EntryOptions) ([]byt
|
||||
args = append(args, fmt.Sprintf("--timeout=%d", int(opts.TimeOut.Seconds())))
|
||||
}
|
||||
|
||||
output, statusCode, err := z.cmdWithOutput(ctx, args...)
|
||||
output, statusCode, err := z.cmdWithOutput(args...)
|
||||
if err != nil {
|
||||
switch statusCode {
|
||||
case 1:
|
||||
@@ -53,7 +52,7 @@ func (z *Zenity) ShowEntry(ctx context.Context, opts dialog.EntryOptions) ([]byt
|
||||
case 5:
|
||||
return nil, dialog.ErrTimeout
|
||||
default:
|
||||
return nil, ctxerr.Wrap(ctx, dialog.ErrUnknown, err.Error())
|
||||
return nil, errors.Join(dialog.ErrUnknown, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +60,7 @@ func (z *Zenity) ShowEntry(ctx context.Context, opts dialog.EntryOptions) ([]byt
|
||||
}
|
||||
|
||||
// ShowInfo displays an information dialog. It returns errors ErrTimeout or ErrUnknown.
|
||||
func (z *Zenity) ShowInfo(ctx context.Context, opts dialog.InfoOptions) error {
|
||||
func (z *Zenity) ShowInfo(opts dialog.InfoOptions) error {
|
||||
args := []string{"--info"}
|
||||
if opts.Title != "" {
|
||||
args = append(args, fmt.Sprintf("--title=%s", opts.Title))
|
||||
@@ -73,13 +72,13 @@ func (z *Zenity) ShowInfo(ctx context.Context, opts dialog.InfoOptions) error {
|
||||
args = append(args, fmt.Sprintf("--timeout=%d", int(opts.TimeOut.Seconds())))
|
||||
}
|
||||
|
||||
_, statusCode, err := z.cmdWithOutput(ctx, args...)
|
||||
_, statusCode, err := z.cmdWithOutput(args...)
|
||||
if err != nil {
|
||||
switch statusCode {
|
||||
case 5:
|
||||
return ctxerr.Wrap(ctx, dialog.ErrTimeout)
|
||||
return dialog.ErrTimeout
|
||||
default:
|
||||
return ctxerr.Wrap(ctx, dialog.ErrUnknown, err.Error())
|
||||
return errors.Join(dialog.ErrUnknown, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +113,7 @@ func (z *Zenity) ShowProgress(opts dialog.ProgressOptions) (func() error, error)
|
||||
return cancel, nil
|
||||
}
|
||||
|
||||
func execCmdWithOutput(ctx context.Context, args ...string) ([]byte, int, error) {
|
||||
func execCmdWithOutput(args ...string) ([]byte, int, error) {
|
||||
var opts []execuser.Option
|
||||
for _, arg := range args {
|
||||
opts = append(opts, execuser.WithArg(arg, "")) // Using empty value for positional args
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package zenity
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os/exec"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -18,7 +17,7 @@ type mockExecCmd struct {
|
||||
}
|
||||
|
||||
// MockCommandContext simulates exec.CommandContext and captures arguments
|
||||
func (m *mockExecCmd) runWithOutput(ctx context.Context, args ...string) ([]byte, int, error) {
|
||||
func (m *mockExecCmd) runWithOutput(args ...string) ([]byte, int, error) {
|
||||
m.capturedArgs = append(m.capturedArgs, args...)
|
||||
|
||||
if m.exitCode != 0 {
|
||||
@@ -35,8 +34,6 @@ func (m *mockExecCmd) runWithStdin(args ...string) (func() error, error) {
|
||||
}
|
||||
|
||||
func TestShowEntryArgs(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
opts dialog.EntryOptions
|
||||
@@ -70,7 +67,7 @@ func TestShowEntryArgs(t *testing.T) {
|
||||
z := &Zenity{
|
||||
cmdWithOutput: mock.runWithOutput,
|
||||
}
|
||||
output, err := z.ShowEntry(ctx, tt.opts)
|
||||
output, err := z.ShowEntry(tt.opts)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expectedArgs, mock.capturedArgs)
|
||||
assert.Equal(t, []byte("some output"), output)
|
||||
@@ -79,8 +76,6 @@ func TestShowEntryArgs(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestShowEntryError(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
exitCode int
|
||||
@@ -111,7 +106,7 @@ func TestShowEntryError(t *testing.T) {
|
||||
z := &Zenity{
|
||||
cmdWithOutput: mock.runWithOutput,
|
||||
}
|
||||
output, err := z.ShowEntry(ctx, dialog.EntryOptions{})
|
||||
output, err := z.ShowEntry(dialog.EntryOptions{})
|
||||
require.ErrorIs(t, err, tt.expectedErr)
|
||||
assert.Nil(t, output)
|
||||
})
|
||||
@@ -119,22 +114,18 @@ func TestShowEntryError(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestShowEntrySuccess(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
mock := &mockExecCmd{
|
||||
output: []byte("some output"),
|
||||
}
|
||||
z := &Zenity{
|
||||
cmdWithOutput: mock.runWithOutput,
|
||||
}
|
||||
output, err := z.ShowEntry(ctx, dialog.EntryOptions{})
|
||||
output, err := z.ShowEntry(dialog.EntryOptions{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []byte("some output"), output)
|
||||
}
|
||||
|
||||
func TestShowInfoArgs(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
opts dialog.InfoOptions
|
||||
@@ -162,7 +153,7 @@ func TestShowInfoArgs(t *testing.T) {
|
||||
z := &Zenity{
|
||||
cmdWithOutput: mock.runWithOutput,
|
||||
}
|
||||
err := z.ShowInfo(ctx, tt.opts)
|
||||
err := z.ShowInfo(tt.opts)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expectedArgs, mock.capturedArgs)
|
||||
})
|
||||
@@ -170,8 +161,6 @@ func TestShowInfoArgs(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestShowInfoError(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
exitCode int
|
||||
@@ -197,7 +186,7 @@ func TestShowInfoError(t *testing.T) {
|
||||
z := &Zenity{
|
||||
cmdWithOutput: mock.runWithOutput,
|
||||
}
|
||||
err := z.ShowInfo(ctx, dialog.InfoOptions{})
|
||||
err := z.ShowInfo(dialog.InfoOptions{})
|
||||
require.ErrorIs(t, err, tt.expectedErr)
|
||||
})
|
||||
}
|
||||
|
||||
+16
-5
@@ -4,19 +4,30 @@ package main
|
||||
// It will show an entry dialog, a progress dialog, and an info dialog
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/dialog"
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/kdialog"
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/zenity"
|
||||
)
|
||||
|
||||
func main() {
|
||||
prompt := zenity.New()
|
||||
ctx := context.Background()
|
||||
dialogTool := flag.String("dialog", "zenity", "Dialog to use: zenity or kdialog")
|
||||
flag.Parse()
|
||||
|
||||
output, err := prompt.ShowEntry(ctx, dialog.EntryOptions{
|
||||
var prompt dialog.Dialog
|
||||
|
||||
if *dialogTool == "zenity" {
|
||||
fmt.Println("Using zenity")
|
||||
prompt = zenity.New()
|
||||
} else {
|
||||
fmt.Println("Using kdialog")
|
||||
prompt = kdialog.New()
|
||||
}
|
||||
|
||||
output, err := prompt.ShowEntry(dialog.EntryOptions{
|
||||
Title: "Zenity Test Entry Title",
|
||||
Text: "Zenity Test Entry Text",
|
||||
HideText: true,
|
||||
@@ -42,7 +53,7 @@ func main() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = prompt.ShowInfo(ctx, dialog.InfoOptions{
|
||||
err = prompt.ShowInfo(dialog.InfoOptions{
|
||||
Title: "Zenity Test Info Title",
|
||||
Text: "Result: " + string(output),
|
||||
TimeOut: 10 * time.Second,
|
||||
|
||||
@@ -24,7 +24,7 @@ func main() {
|
||||
prompt := zenity.New()
|
||||
|
||||
// Prompt existing passphrase from the user.
|
||||
currentPassphrase, err := prompt.ShowEntry(context.Background(), dialog.EntryOptions{
|
||||
currentPassphrase, err := prompt.ShowEntry(dialog.EntryOptions{
|
||||
Title: "Enter Existing LUKS Passphrase",
|
||||
Text: "Enter your existing LUKS passphrase:",
|
||||
HideText: true,
|
||||
@@ -49,7 +49,7 @@ func main() {
|
||||
|
||||
if err := device.AddKey(context.Background(), devicePath, userKey, escrowKey); err != nil {
|
||||
if errors.Is(err, encryption.ErrEncryptionKeyRejected) {
|
||||
currentPassphrase, err = prompt.ShowEntry(context.Background(), dialog.EntryOptions{
|
||||
currentPassphrase, err = prompt.ShowEntry(dialog.EntryOptions{
|
||||
Title: "Enter Existing LUKS Passphrase",
|
||||
Text: "Bad password. Enter your existing LUKS passphrase:",
|
||||
HideText: true,
|
||||
|
||||
Reference in New Issue
Block a user