From 9e093fa55b6c89a535d9ed39a48e68d72bf870f8 Mon Sep 17 00:00:00 2001 From: plastikfan Date: Fri, 19 Jan 2024 12:04:55 +0000 Subject: [PATCH] feat(proxy): create magick execution agent (#124) feat(cfg): add executable to advanced config (#124) feat(command): move creation of executor to entry point (#124) feat(proxy): use execution agent (#124) --- src/app/command/bootstrap.go | 24 +--- src/app/command/bootstrap_test.go | 3 - src/app/command/magick-cmd_test.go | 3 - src/app/command/root-cmd_test.go | 3 - src/app/command/shrink-cmd.go | 2 - src/app/command/shrink-cmd_test.go | 3 - src/app/mocks/mocks-config.go | 109 +++++++++++++----- src/app/proxy/config_test.go | 3 - .../{execution-step.go => controller-step.go} | 17 ++- src/app/proxy/controller.go | 14 +-- src/app/proxy/controller_test.go | 6 - src/app/proxy/enter-root.go | 14 +-- src/app/proxy/enter-shrink.go | 9 +- src/app/proxy/entry-base.go | 3 +- src/app/proxy/execution-agent-magick.go | 21 ++++ src/app/proxy/execution-agent.go | 57 +++++++++ src/app/{command => proxy}/executor.go | 4 +- src/app/proxy/image-defs.go | 20 ++++ src/app/proxy/proxy-defs.go | 2 +- src/cfg/config-defaults.go | 9 +- src/cfg/config-readers.go | 21 ++++ src/cfg/config.go | 9 +- src/cfg/ms-config.go | 33 ++++-- src/internal/helpers/mock-config-data.go | 14 +-- test/data/configuration/pixa-test.yml | 6 +- 25 files changed, 279 insertions(+), 130 deletions(-) rename src/app/proxy/{execution-step.go => controller-step.go} (65%) create mode 100644 src/app/proxy/execution-agent-magick.go create mode 100644 src/app/proxy/execution-agent.go rename src/app/{command => proxy}/executor.go (93%) diff --git a/src/app/command/bootstrap.go b/src/app/command/bootstrap.go index 57f1e95..9c459d6 100644 --- a/src/app/command/bootstrap.go +++ b/src/app/command/bootstrap.go @@ -91,7 +91,6 @@ type Bootstrap struct { type ConfigureOptionsInfo struct { Detector LocaleDetector - Program proxy.Executor Config ConfigInfo } @@ -105,9 +104,6 @@ func (b *Bootstrap) Root(options ...ConfigureOptionFn) *cobra.Command { b.OptionsInfo = ConfigureOptionsInfo{ Detector: &Jabber{}, - Program: &ProgramExecutor{ // 💥 TEMPORARILY OVERRIDDEN WITH DUMMY - Name: "magick", - }, Config: ConfigInfo{ Name: ApplicationName, ConfigType: "yaml", @@ -123,26 +119,12 @@ func (b *Bootstrap) Root(options ...ConfigureOptionFn) *cobra.Command { }, } - if _, err := b.OptionsInfo.Program.Look(); err != nil { - b.OptionsInfo.Program = &DummyExecutor{ - Name: b.OptionsInfo.Program.ProgName(), - } - } - for _, fo := range options { fo(&b.OptionsInfo) } b.configure() - // JUST TEMPORARY: make the executor the dummy for safety - // - b.OptionsInfo.Program = &DummyExecutor{ - Name: "magick", - } - - fmt.Printf("===> 💥💥💥 USING DUMMY EXECUTOR !!!!\n") - b.Container = assistant.NewCobraContainer( &cobra.Command{ Use: "main", @@ -175,7 +157,11 @@ func (b *Bootstrap) Root(options ...ConfigureOptionFn) *cobra.Command { // ---> execute root core // - return proxy.EnterRoot(inputs, b.OptionsInfo.Program, b.OptionsInfo.Config.Viper) + + return proxy.EnterRoot( + inputs, + b.OptionsInfo.Config.Viper, + ) }, }, ) diff --git a/src/app/command/bootstrap_test.go b/src/app/command/bootstrap_test.go index 3a93f63..7e111b4 100644 --- a/src/app/command/bootstrap_test.go +++ b/src/app/command/bootstrap_test.go @@ -93,9 +93,6 @@ var _ = Describe("Bootstrap", Ordered, func() { } rootCmd := bootstrap.Root(func(co *command.ConfigureOptionsInfo) { co.Detector = &DetectorStub{} - co.Program = &ExecutorStub{ - Name: "magick", - } co.Config.Name = helpers.PixaConfigTestFilename co.Config.ConfigPath = configPath co.Config.Viper = &configuration.GlobalViperConfig{} diff --git a/src/app/command/magick-cmd_test.go b/src/app/command/magick-cmd_test.go index 3a545f0..1d126bf 100644 --- a/src/app/command/magick-cmd_test.go +++ b/src/app/command/magick-cmd_test.go @@ -70,9 +70,6 @@ var _ = Describe("MagickCmd", Ordered, func() { Args: []string{"mag"}, Root: bootstrap.Root(func(co *command.ConfigureOptionsInfo) { co.Detector = &DetectorStub{} - co.Program = &helpers.ExecutorStub{ - Name: helpers.ProgName, - } co.Config.Name = helpers.PixaConfigTestFilename co.Config.ConfigPath = configPath co.Config.Viper = &configuration.GlobalViperConfig{} diff --git a/src/app/command/root-cmd_test.go b/src/app/command/root-cmd_test.go index d0b3edc..44bad8f 100644 --- a/src/app/command/root-cmd_test.go +++ b/src/app/command/root-cmd_test.go @@ -43,9 +43,6 @@ var _ = Describe("RootCmd", Ordered, func() { tester = helpers.CommandTester{ Root: bootstrap.Root(func(co *command.ConfigureOptionsInfo) { co.Detector = &DetectorStub{} - co.Program = &ExecutorStub{ - Name: "magick", - } co.Config.Name = helpers.PixaConfigTestFilename co.Config.ConfigPath = configPath }), diff --git a/src/app/command/shrink-cmd.go b/src/app/command/shrink-cmd.go index 0d46230..1bfbb07 100644 --- a/src/app/command/shrink-cmd.go +++ b/src/app/command/shrink-cmd.go @@ -53,7 +53,6 @@ var shrinkShortFlags = cobrass.KnownByCollection{ "files": "F", // family: filter "files-gb": "G", // family: filter "files-rx": "X", // family: filter - "folders": "F", // family: filter "folders-gb": "Z", // family: filter "folders-rx": "Y", // family: filter "profile": "P", // family: profile @@ -136,7 +135,6 @@ func (b *Bootstrap) buildShrinkCommand(container *assistant.CobraContainer) *cob appErr = proxy.EnterShrink( &proxy.ShrinkParams{ Inputs: inputs, - Program: b.OptionsInfo.Program, Config: b.OptionsInfo.Config.Viper, ProfilesCFG: b.ProfilesCFG, SchemesCFG: b.SchemesCFG, diff --git a/src/app/command/shrink-cmd_test.go b/src/app/command/shrink-cmd_test.go index cd5b71e..b3d6303 100644 --- a/src/app/command/shrink-cmd_test.go +++ b/src/app/command/shrink-cmd_test.go @@ -89,9 +89,6 @@ func expectValidShrinkCmdInvocation(vfs storage.VirtualFS, entry *shrinkTE, root Args: append(args, entry.args...), Root: bootstrap.Root(func(co *command.ConfigureOptionsInfo) { co.Detector = &DetectorStub{} - co.Program = &ExecutorStub{ - Name: helpers.ProgName, - } co.Config.Name = helpers.PixaConfigTestFilename co.Config.ConfigPath = entry.configPath diff --git a/src/app/mocks/mocks-config.go b/src/app/mocks/mocks-config.go index d175312..7ec53a5 100644 --- a/src/app/mocks/mocks-config.go +++ b/src/app/mocks/mocks-config.go @@ -375,6 +375,72 @@ func (mr *MockExtensionsConfigMockRecorder) Transforms() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Transforms", reflect.TypeOf((*MockExtensionsConfig)(nil).Transforms)) } +// MockExecutableConfig is a mock of ExecutableConfig interface. +type MockExecutableConfig struct { + ctrl *gomock.Controller + recorder *MockExecutableConfigMockRecorder +} + +// MockExecutableConfigMockRecorder is the mock recorder for MockExecutableConfig. +type MockExecutableConfigMockRecorder struct { + mock *MockExecutableConfig +} + +// NewMockExecutableConfig creates a new mock instance. +func NewMockExecutableConfig(ctrl *gomock.Controller) *MockExecutableConfig { + mock := &MockExecutableConfig{ctrl: ctrl} + mock.recorder = &MockExecutableConfigMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockExecutableConfig) EXPECT() *MockExecutableConfigMockRecorder { + return m.recorder +} + +// NoRetries mocks base method. +func (m *MockExecutableConfig) NoRetries() uint { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NoRetries") + ret0, _ := ret[0].(uint) + return ret0 +} + +// NoRetries indicates an expected call of NoRetries. +func (mr *MockExecutableConfigMockRecorder) NoRetries() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NoRetries", reflect.TypeOf((*MockExecutableConfig)(nil).NoRetries)) +} + +// ProgramTimeout mocks base method. +func (m *MockExecutableConfig) ProgramTimeout() (time.Duration, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ProgramTimeout") + ret0, _ := ret[0].(time.Duration) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ProgramTimeout indicates an expected call of ProgramTimeout. +func (mr *MockExecutableConfigMockRecorder) ProgramTimeout() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ProgramTimeout", reflect.TypeOf((*MockExecutableConfig)(nil).ProgramTimeout)) +} + +// Symbol mocks base method. +func (m *MockExecutableConfig) Symbol() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Symbol") + ret0, _ := ret[0].(string) + return ret0 +} + +// Symbol indicates an expected call of Symbol. +func (mr *MockExecutableConfigMockRecorder) Symbol() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Symbol", reflect.TypeOf((*MockExecutableConfig)(nil).Symbol)) +} + // MockAdvancedConfig is a mock of AdvancedConfig interface. type MockAdvancedConfig struct { ctrl *gomock.Controller @@ -426,6 +492,20 @@ func (mr *MockAdvancedConfigMockRecorder) AdhocLabel() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AdhocLabel", reflect.TypeOf((*MockAdvancedConfig)(nil).AdhocLabel)) } +// Executable mocks base method. +func (m *MockAdvancedConfig) Executable() cfg.ExecutableConfig { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Executable") + ret0, _ := ret[0].(cfg.ExecutableConfig) + return ret0 +} + +// Executable indicates an expected call of Executable. +func (mr *MockAdvancedConfigMockRecorder) Executable() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Executable", reflect.TypeOf((*MockAdvancedConfig)(nil).Executable)) +} + // Extensions mocks base method. func (m *MockAdvancedConfig) Extensions() cfg.ExtensionsConfig { m.ctrl.T.Helper() @@ -468,35 +548,6 @@ func (mr *MockAdvancedConfigMockRecorder) LegacyLabel() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LegacyLabel", reflect.TypeOf((*MockAdvancedConfig)(nil).LegacyLabel)) } -// NoRetries mocks base method. -func (m *MockAdvancedConfig) NoRetries() uint { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "NoRetries") - ret0, _ := ret[0].(uint) - return ret0 -} - -// NoRetries indicates an expected call of NoRetries. -func (mr *MockAdvancedConfigMockRecorder) NoRetries() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NoRetries", reflect.TypeOf((*MockAdvancedConfig)(nil).NoRetries)) -} - -// ProgramTimeout mocks base method. -func (m *MockAdvancedConfig) ProgramTimeout() (time.Duration, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ProgramTimeout") - ret0, _ := ret[0].(time.Duration) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ProgramTimeout indicates an expected call of ProgramTimeout. -func (mr *MockAdvancedConfigMockRecorder) ProgramTimeout() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ProgramTimeout", reflect.TypeOf((*MockAdvancedConfig)(nil).ProgramTimeout)) -} - // TrashLabel mocks base method. func (m *MockAdvancedConfig) TrashLabel() string { m.ctrl.T.Helper() diff --git a/src/app/proxy/config_test.go b/src/app/proxy/config_test.go index ec8fa41..9738036 100644 --- a/src/app/proxy/config_test.go +++ b/src/app/proxy/config_test.go @@ -55,9 +55,6 @@ func expectValidShrinkCmdInvocation(vfs storage.VirtualFS, entry *configTE, Args: append(options, entry.args...), Root: bootstrap.Root(func(co *command.ConfigureOptionsInfo) { co.Detector = &helpers.DetectorStub{} - co.Program = &helpers.ExecutorStub{ - Name: helpers.ProgName, - } co.Config.Name = helpers.PixaConfigTestFilename co.Config.ConfigPath = configPath co.Config.Viper = &configuration.GlobalViperConfig{} diff --git a/src/app/proxy/execution-step.go b/src/app/proxy/controller-step.go similarity index 65% rename from src/app/proxy/execution-step.go rename to src/app/proxy/controller-step.go index ba034d6..17b0238 100644 --- a/src/app/proxy/execution-step.go +++ b/src/app/proxy/controller-step.go @@ -20,11 +20,11 @@ type ( Sequence []Step ) -// executionStep knows how to combine parameters together so that the program -// can be invoked correctly; but it does not know how to compose the input -// and output file names; this is the responsibility of the controller, which uses +// controllerStep uses the agent to combine parameters together so that the program +// can be invoked correctly; but it does not know how to compose the input and +// output file names; this is the responsibility of the controller, which uses // the path-finder to accomplish that task. -type executionStep struct { +type controllerStep struct { shared *SharedControllerInfo thirdPartyCL clif.ThirdPartyCommandLine profile string @@ -34,17 +34,16 @@ type executionStep struct { } // Run -func (s *executionStep) Run(pi *pathInfo) error { +func (s *controllerStep) Run(pi *pathInfo) error { folder, file := s.shared.finder.Result(pi) - result := filepath.Join(folder, file) - input := []string{pi.runStep.Source} + destination := filepath.Join(folder, file) // if transparent, then we need to ask the fm to move the // existing file out of the way. But shouldn't that already have happened // during setup? See, which mean setup in not working properly in // this scenario. - return s.shared.program.Execute( - clif.Expand(input, s.thirdPartyCL, result)..., + return s.shared.agent.Invoke( + s.thirdPartyCL, pi.runStep.Source, destination, ) } diff --git a/src/app/proxy/controller.go b/src/app/proxy/controller.go index 6ed5159..068d6e4 100644 --- a/src/app/proxy/controller.go +++ b/src/app/proxy/controller.go @@ -49,10 +49,10 @@ func (c *controller) profileSequence( pi *pathInfo, ) Sequence { changed := c.shared.Inputs.ParamSet.Native.ThirdPartySet.LongChangedCL - cl := c.composeProfileCL(pi.profile, changed) - step := &executionStep{ + combined := c.composeProfileCL(pi.profile, changed) + step := &controllerStep{ shared: c.shared, - thirdPartyCL: cl, + thirdPartyCL: combined, sourcePath: pi.item.Path, profile: pi.profile, outputPath: c.shared.Inputs.ParamSet.Native.OutputPath, @@ -69,10 +69,10 @@ func (c *controller) schemeSequence( sequence := make(Sequence, 0, len(schemeCfg.Profiles())) for _, current := range schemeCfg.Profiles() { - cl := c.composeProfileCL(current, changed) - step := &executionStep{ + combined := c.composeProfileCL(current, changed) + step := &controllerStep{ shared: c.shared, - thirdPartyCL: cl, + thirdPartyCL: combined, sourcePath: pi.item.Path, profile: current, outputPath: c.shared.Inputs.ParamSet.Native.OutputPath, @@ -88,7 +88,7 @@ func (c *controller) adhocSequence( pi *pathInfo, ) Sequence { changed := c.shared.Inputs.ParamSet.Native.ThirdPartySet.LongChangedCL - step := &executionStep{ + step := &controllerStep{ shared: c.shared, thirdPartyCL: changed, sourcePath: pi.item.Path, diff --git a/src/app/proxy/controller_test.go b/src/app/proxy/controller_test.go index 2d802c6..365f923 100644 --- a/src/app/proxy/controller_test.go +++ b/src/app/proxy/controller_test.go @@ -129,9 +129,6 @@ var _ = Describe("SamplerController", Ordered, func() { Args: args, Root: bootstrap.Root(func(co *command.ConfigureOptionsInfo) { co.Detector = &helpers.DetectorStub{} - co.Program = &helpers.ExecutorStub{ - Name: helpers.ProgName, - } co.Config.Name = helpers.PixaConfigTestFilename co.Config.ConfigPath = configPath co.Config.Viper = &configuration.GlobalViperConfig{} @@ -613,9 +610,6 @@ var _ = Describe("end to end", Ordered, func() { Args: args, Root: bootstrap.Root(func(co *command.ConfigureOptionsInfo) { co.Detector = &helpers.DetectorStub{} - co.Program = &helpers.ExecutorStub{ - Name: helpers.ProgName, - } co.Config.Name = "pixa" co.Config.ConfigPath = configPath }), diff --git a/src/app/proxy/enter-root.go b/src/app/proxy/enter-root.go index 501d840..8da8779 100644 --- a/src/app/proxy/enter-root.go +++ b/src/app/proxy/enter-root.go @@ -14,12 +14,6 @@ type configProfile struct { args []string } -type Executor interface { - ProgName() string - Look() (string, error) - Execute(args ...string) error -} - const ( DefaultJobsChSize = 10 ) @@ -50,7 +44,7 @@ func (e *RootEntry) principalFn(item *nav.TraverseItem) error { item.Path, ) - return e.Program.Execute("--version") + return nil } func (e *RootEntry) ConfigureOptions(o *nav.TraverseOptions) { @@ -112,16 +106,14 @@ func composeWith(inputs *RootCommandInputs) nav.CreateNewRunnerWith { func EnterRoot( inputs *RootCommandInputs, - program Executor, config configuration.ViperConfig, ) error { fmt.Printf("---> 📁📁📁 Directory: '%v'\n", inputs.ParamSet.Native.Directory) entry := &RootEntry{ EntryBase: EntryBase{ - Inputs: inputs, - Program: program, - Config: config, + Inputs: inputs, + Config: config, }, } diff --git a/src/app/proxy/enter-shrink.go b/src/app/proxy/enter-shrink.go index 4df2541..79068df 100644 --- a/src/app/proxy/enter-shrink.go +++ b/src/app/proxy/enter-shrink.go @@ -156,7 +156,6 @@ func (e *ShrinkEntry) run(_ configuration.ViperConfig) error { type ShrinkParams struct { Inputs *ShrinkCommandInputs - Program Executor Config configuration.ViperConfig ProfilesCFG cfg.ProfilesConfig SchemesCFG cfg.SchemesConfig @@ -169,6 +168,10 @@ type ShrinkParams struct { func EnterShrink( params *ShrinkParams, ) error { + agent := newAgent( + params.AdvancedCFG, + params.Inputs.ParamSet.Native.KnownBy, + ) finder := newPathFinder(params.Inputs, params.AdvancedCFG, params.SchemesCFG) fileManager := &FileManager{ vfs: params.Vfs, @@ -177,7 +180,7 @@ func EnterShrink( entry := &ShrinkEntry{ EntryBase: EntryBase{ Inputs: params.Inputs.Root, - Program: params.Program, + Agent: agent, Config: params.Config, ProfilesCFG: params.ProfilesCFG, SchemesCFG: params.SchemesCFG, @@ -191,7 +194,7 @@ func EnterShrink( config: params.AdvancedCFG, }, Registry: NewControllerRegistry(&SharedControllerInfo{ - program: params.Program, + agent: agent, profiles: params.ProfilesCFG, schemes: params.SchemesCFG, sampler: params.SamplerCFG, diff --git a/src/app/proxy/entry-base.go b/src/app/proxy/entry-base.go index a06d1a3..03f59d7 100644 --- a/src/app/proxy/entry-base.go +++ b/src/app/proxy/entry-base.go @@ -40,7 +40,7 @@ type EntryBase struct { // with the rest going into cobrass.clif // Inputs *RootCommandInputs - Program Executor + Agent ExecutionAgent Config configuration.ViperConfig Options *nav.TraverseOptions Registry *ControllerRegistry @@ -146,7 +146,6 @@ func (e *EntryBase) ConfigureOptions(o *nav.TraverseOptions) { // if e.Registry == nil { e.Registry = NewControllerRegistry(&SharedControllerInfo{ - program: e.Program, profiles: e.ProfilesCFG, sampler: e.SamplerCFG, }) diff --git a/src/app/proxy/execution-agent-magick.go b/src/app/proxy/execution-agent-magick.go new file mode 100644 index 0000000..51abd84 --- /dev/null +++ b/src/app/proxy/execution-agent-magick.go @@ -0,0 +1,21 @@ +package proxy + +import "github.com/snivilised/cobrass/src/clif" + +type magickAgent struct { + baseAgent +} + +func (a *magickAgent) IsInstalled() bool { + _, err := a.program.Look() + + return err == nil +} + +func (a *magickAgent) Invoke(thirdPartyCL clif.ThirdPartyCommandLine, source, destination string) error { + before := []string{source} + + return a.program.Execute( + clif.Expand(before, thirdPartyCL, destination)..., + ) +} diff --git a/src/app/proxy/execution-agent.go b/src/app/proxy/execution-agent.go new file mode 100644 index 0000000..687cbd5 --- /dev/null +++ b/src/app/proxy/execution-agent.go @@ -0,0 +1,57 @@ +package proxy + +import ( + "fmt" + + "github.com/snivilised/cobrass/src/clif" + "github.com/snivilised/pixa/src/cfg" +) + +type baseAgent struct { + knownBy clif.KnownByCollection + program Executor +} + +func newAgent( + advanced cfg.AdvancedConfig, + knownBy clif.KnownByCollection, +) ExecutionAgent { + var ( + agent ExecutionAgent + // dummy uses the same agent as magick + // + dummy = &magickAgent{ + baseAgent{ + knownBy: knownBy, + program: &DummyExecutor{ + Name: advanced.Executable().Symbol(), + }, + }, + } + ) + + switch advanced.Executable().Symbol() { + case "magick": + agent = &magickAgent{ + baseAgent{ + knownBy: knownBy, + program: &ProgramExecutor{ + Name: advanced.Executable().Symbol(), + }, + }, + } + + if !agent.IsInstalled() { + fmt.Printf("===> 💥💥💥 REVERTING TO DUMMY EXECUTOR !!!!\n") + + agent = dummy + } + + case "dummy": + fmt.Printf("===> 🚫 USING DUMMY EXECUTOR !!!!\n") + + agent = dummy + } + + return agent +} diff --git a/src/app/command/executor.go b/src/app/proxy/executor.go similarity index 93% rename from src/app/command/executor.go rename to src/app/proxy/executor.go index 4c59994..b61689b 100644 --- a/src/app/command/executor.go +++ b/src/app/proxy/executor.go @@ -1,5 +1,4 @@ -// #nosec G204 -package command +package proxy import ( "fmt" @@ -25,6 +24,7 @@ func (e *ProgramExecutor) Execute(args ...string) error { strings.Join(args, " "), ) + // #nosec G204 // prog(e.Name) is pre-vetted cmd := exec.Command(e.Name, args...) err := cmd.Start() diff --git a/src/app/proxy/image-defs.go b/src/app/proxy/image-defs.go index daaf6e7..be46fd8 100644 --- a/src/app/proxy/image-defs.go +++ b/src/app/proxy/image-defs.go @@ -137,3 +137,23 @@ type ShrinkCommandInputs struct { ParamSet *assistant.ParamSet[ShrinkParameterSet] PolyFam *assistant.ParamSet[store.PolyFilterParameterSet] } + +type ExecutionAgent interface { + // IsInstalled determines whether the underlying program is installed + IsInstalled() bool + + // Invoke returns the command line args required for the executor to + // run correctly. Only the source and destination are required because + // they are the dynamic args that change for each invocation. All the + // other flags that come directly from the command line/config possibly + // via a profile are static, which means the concrete agent will already + // have those and merely has to formulate the complete command line in + // the correct order required by the third party program. + Invoke(thirdPartyCL clif.ThirdPartyCommandLine, source, destination string) error +} + +type Executor interface { + ProgName() string + Look() (string, error) + Execute(args ...string) error +} diff --git a/src/app/proxy/proxy-defs.go b/src/app/proxy/proxy-defs.go index f82bc0a..a142462 100644 --- a/src/app/proxy/proxy-defs.go +++ b/src/app/proxy/proxy-defs.go @@ -6,7 +6,7 @@ import ( ) type SharedControllerInfo struct { - program Executor + agent ExecutionAgent profiles cfg.ProfilesConfig schemes cfg.SchemesConfig sampler cfg.SamplerConfig diff --git a/src/cfg/config-defaults.go b/src/cfg/config-defaults.go index 3f92c71..0ae92a5 100644 --- a/src/cfg/config-defaults.go +++ b/src/cfg/config-defaults.go @@ -86,9 +86,7 @@ func init() { } DefaultAdvancedConfig = &MsAdvancedConfig{ - Abort: false, - Timeout: "10s", - NoProgramRetries: defaultNoProgramRetries, + Abort: false, LabelsCFG: MsLabelsConfig{ Adhoc: "ADHOC", Journal: "journal", @@ -99,6 +97,11 @@ func init() { FileSuffixes: "jpg,jpeg,png", TransformsCSV: "lower", }, + ExecutableCFG: MsExecutableConfig{ + ProgramName: "magick", + Timeout: "10s", + NoProgramRetries: defaultNoProgramRetries, + }, } userHomeDir, err := os.UserHomeDir() diff --git a/src/cfg/config-readers.go b/src/cfg/config-readers.go index b23d151..cbada7a 100644 --- a/src/cfg/config-readers.go +++ b/src/cfg/config-readers.go @@ -89,6 +89,11 @@ var ( "xcf", "xpm", "xwd", "yuv", } + + permittedPrograms = []string{ + "dummy", + "magick", + } ) type MsAdvancedConfigReader struct{} @@ -113,6 +118,18 @@ func (r *MsAdvancedConfigReader) validateSuffixes(suffixes []string, from string return err } +func (r *MsAdvancedConfigReader) validateProgramName(name string) error { + var ( + err error + ) + + if !slices.Contains(permittedPrograms, name) { + err = fmt.Errorf("invalid program name found: '%v'", name) + } + + return err +} + func (r *MsAdvancedConfigReader) Read(viper configuration.ViperConfig) (AdvancedConfig, error) { var ( advancedCFG MsAdvancedConfig @@ -139,6 +156,10 @@ func (r *MsAdvancedConfigReader) Read(viper configuration.ViperConfig) (Advanced err = r.validateSuffixes(suffixes, "extensions.suffixes") } + if err == nil { + err = r.validateProgramName(advancedCFG.ExecutableCFG.ProgramName) + } + return &advancedCFG, err } diff --git a/src/cfg/config.go b/src/cfg/config.go index f926b5d..6e5a910 100644 --- a/src/cfg/config.go +++ b/src/cfg/config.go @@ -46,15 +46,20 @@ type ( Map() map[string]string } - AdvancedConfig interface { - AbortOnError() bool + ExecutableConfig interface { + Symbol() string ProgramTimeout() (duration time.Duration, err error) NoRetries() uint + } + + AdvancedConfig interface { + AbortOnError() bool AdhocLabel() string JournalLabel() string LegacyLabel() string TrashLabel() string Extensions() ExtensionsConfig + Executable() ExecutableConfig } AdvancedConfigReader interface { diff --git a/src/cfg/ms-config.go b/src/cfg/ms-config.go index 7f34c56..4ff9728 100644 --- a/src/cfg/ms-config.go +++ b/src/cfg/ms-config.go @@ -101,26 +101,35 @@ func (c *MsExtensionsConfig) Map() map[string]string { return c.Remap } -type MsAdvancedConfig struct { - Abort bool `mapstructure:"abort-on-error"` - Timeout string `mapstructure:"program-timeout"` - NoProgramRetries uint `mapstructure:"no-program-retries"` - LabelsCFG MsLabelsConfig `mapstructure:"labels"` - ExtensionsCFG MsExtensionsConfig `mapstructure:"extensions"` +type MsExecutableConfig struct { + ProgramName string `mapstructure:"program-name"` + Timeout string `mapstructure:"timeout"` + NoProgramRetries uint `mapstructure:"no-retries"` } -func (c *MsAdvancedConfig) AbortOnError() bool { - return c.Abort +func (c *MsExecutableConfig) Symbol() string { + return c.ProgramName } -func (c *MsAdvancedConfig) ProgramTimeout() (duration time.Duration, err error) { +func (c *MsExecutableConfig) ProgramTimeout() (duration time.Duration, err error) { return time.ParseDuration(c.Timeout) } -func (c *MsAdvancedConfig) NoRetries() uint { +func (c *MsExecutableConfig) NoRetries() uint { return c.NoProgramRetries } +type MsAdvancedConfig struct { + Abort bool `mapstructure:"abort-on-error"` + LabelsCFG MsLabelsConfig `mapstructure:"labels"` + ExtensionsCFG MsExtensionsConfig `mapstructure:"extensions"` + ExecutableCFG MsExecutableConfig `mapstructure:"executable"` +} + +func (c *MsAdvancedConfig) AbortOnError() bool { + return c.Abort +} + func (c *MsAdvancedConfig) AdhocLabel() string { return c.LabelsCFG.Adhoc } @@ -141,6 +150,10 @@ func (c *MsAdvancedConfig) Extensions() ExtensionsConfig { return &c.ExtensionsCFG } +func (c *MsAdvancedConfig) Executable() ExecutableConfig { + return &c.ExecutableCFG +} + type MsLoggingConfig struct { LogPath string `mapstructure:"log-path"` MaxSize uint `mapstructure:"max-size-in-mb"` diff --git a/src/internal/helpers/mock-config-data.go b/src/internal/helpers/mock-config-data.go index 52d4157..64f90eb 100644 --- a/src/internal/helpers/mock-config-data.go +++ b/src/internal/helpers/mock-config-data.go @@ -5,9 +5,6 @@ import ( "github.com/snivilised/pixa/src/cfg" ) -// need to re-think this mock data as this is currently sub-optimal -// because of the unintended duplication of functionality - const ( noSampleFiles = 2 noSampleFolders = 1 @@ -113,12 +110,10 @@ func init() { } AdvancedConfigData = &cfg.MsAdvancedConfig{ - Abort: false, - Timeout: "10s", - NoProgramRetries: noRetries, + Abort: false, LabelsCFG: cfg.MsLabelsConfig{ Adhoc: "ADHOC", - Journal: ".$journal.txt", + Journal: "journal", Legacy: ".LEGACY", Trash: "TRASH", }, @@ -129,6 +124,11 @@ func init() { "jpeg": "jpg", }, }, + ExecutableCFG: cfg.MsExecutableConfig{ + ProgramName: "dummy", + Timeout: "10s", + NoProgramRetries: noRetries, + }, } LoggingConfigData = &cfg.MsLoggingConfig{ diff --git a/test/data/configuration/pixa-test.yml b/test/data/configuration/pixa-test.yml index 76fe639..edf0f43 100644 --- a/test/data/configuration/pixa-test.yml +++ b/test/data/configuration/pixa-test.yml @@ -26,8 +26,6 @@ sampler: folders: 1 advanced: abort-on-error: false - program-timeout: "20s" - no-program-retries: 0 labels: adhoc: ADHOC legacy: .LEGACY @@ -38,6 +36,10 @@ advanced: transforms-csv: lower map: jpeg: jpg + executable: + program-name: dummy + timeout: "20s" + no-retries: 0 logging: log-path: "~/snivilised/pixa/pixa.log" max-size-in-mb: 10