mirror of
https://github.com/j3ssie/osmedeus.git
synced 2026-08-25 09:02:29 +02:00
- Add worker eval command for distributed function execution with Redis hooks registration - Add worker set command to update worker fields (alias, public-ip, ssh-enabled, ssh-keys-path) - Enhance worker status with JSON output, search filtering, and column selection (--columns, --exclude-columns, --search) - Add --keep-setting flag to install base/validate commands to preserve osm-settings.yaml after base installation - Fix binary installation in Nix: replace CopyInstalledBinaryToFolder with SymlinkInstalledBinaryToFolder - Add --clean-ws flag to db clean command for removing workspace data - Add HooksEnabled field to Run records when creating runs from CLI and API - Add comprehensive test coverage for hook execution (pre/post hooks, execution order, failure handling) - Add test coverage for worker commands (eval, set, status with JSON) and db clean operations - Improve usage documentation for worker subcommands and db operations
229 lines
7.2 KiB
Go
229 lines
7.2 KiB
Go
package core
|
|
|
|
import "strings"
|
|
|
|
// TagList is a comma-separated list of tags that parses to []string
|
|
type TagList []string
|
|
|
|
// UnmarshalYAML implements custom YAML unmarshaling for comma-separated tags
|
|
func (t *TagList) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|
var s string
|
|
if err := unmarshal(&s); err != nil {
|
|
return err
|
|
}
|
|
if s == "" {
|
|
*t = []string{}
|
|
return nil
|
|
}
|
|
parts := strings.Split(s, ",")
|
|
for i := range parts {
|
|
parts[i] = strings.TrimSpace(parts[i])
|
|
}
|
|
*t = parts
|
|
return nil
|
|
}
|
|
|
|
// WorkflowHooks defines pre/post execution steps for workflows
|
|
type WorkflowHooks struct {
|
|
PreScanSteps []Step `yaml:"pre_scan_steps,omitempty"`
|
|
PostScanSteps []Step `yaml:"post_scan_steps,omitempty"`
|
|
}
|
|
|
|
// WorkflowHelp contains usage documentation for a workflow
|
|
type WorkflowHelp struct {
|
|
ExampleTargets []string `yaml:"example_targets,omitempty"`
|
|
Usage string `yaml:"usage,omitempty"`
|
|
}
|
|
|
|
// Workflow represents either a Module or Flow
|
|
type Workflow struct {
|
|
Kind WorkflowKind `yaml:"kind"`
|
|
Name string `yaml:"name"`
|
|
Description string `yaml:"description"`
|
|
Tags TagList `yaml:"tags,omitempty"`
|
|
Hidden bool `yaml:"hidden,omitempty"`
|
|
Help *WorkflowHelp `yaml:"help,omitempty"`
|
|
Params []Param `yaml:"params"`
|
|
Triggers []Trigger `yaml:"triggers"`
|
|
Dependencies *Dependencies `yaml:"dependencies"`
|
|
Reports []Report `yaml:"reports"`
|
|
|
|
// Execution preferences (optional, can be overridden by CLI flags)
|
|
Preferences *Preferences `yaml:"preferences,omitempty"`
|
|
|
|
// Hooks for pre/post scan steps
|
|
Hooks *WorkflowHooks `yaml:"hooks,omitempty"`
|
|
|
|
// Runner configuration (module-kind only)
|
|
Runner RunnerType `yaml:"runner,omitempty"`
|
|
RunnerConfig *RunnerConfig `yaml:"runner_config,omitempty"`
|
|
|
|
// Module-specific fields
|
|
Steps []Step `yaml:"steps,omitempty"`
|
|
|
|
// Flow-specific fields
|
|
Modules []ModuleRef `yaml:"modules,omitempty"`
|
|
|
|
// Inheritance support
|
|
Extends string `yaml:"extends,omitempty"`
|
|
Override *WorkflowOverride `yaml:"override,omitempty"`
|
|
|
|
// Internal metadata
|
|
FilePath string `yaml:"-"`
|
|
Checksum string `yaml:"-"`
|
|
ResolvedFrom string `yaml:"-"` // Tracks the parent workflow name if extended
|
|
}
|
|
|
|
// RunnerConfig holds configuration for different runner types
|
|
type RunnerConfig struct {
|
|
// Docker configuration
|
|
Image string `yaml:"image,omitempty"` // Docker image e.g., "ubuntu:latest"
|
|
Env map[string]string `yaml:"env,omitempty"` // Environment variables
|
|
Volumes []string `yaml:"volumes,omitempty"` // Volume mounts e.g., "/host:/container"
|
|
Network string `yaml:"network,omitempty"` // Network mode e.g., "host", "bridge"
|
|
Persistent bool `yaml:"persistent,omitempty"` // true=reuse container, false=ephemeral
|
|
|
|
// SSH configuration
|
|
Host string `yaml:"host,omitempty"` // SSH hostname or IP
|
|
Port int `yaml:"port,omitempty"` // SSH port (default 22)
|
|
User string `yaml:"user,omitempty"` // SSH username
|
|
KeyFile string `yaml:"key_file,omitempty"` // Path to SSH private key
|
|
Password string `yaml:"password,omitempty"` // SSH password (prefer key_file)
|
|
|
|
// Common configuration
|
|
WorkDir string `yaml:"workdir,omitempty"` // Working directory on remote/container
|
|
}
|
|
|
|
// ModuleRef references a module in a flow or defines an inline module
|
|
type ModuleRef struct {
|
|
Name string `yaml:"name"`
|
|
Path string `yaml:"path,omitempty"` // Path to external module file (omit for inline)
|
|
Params map[string]string `yaml:"params"`
|
|
DependsOn []string `yaml:"depends_on"`
|
|
Condition string `yaml:"condition"`
|
|
OnSuccess []Action `yaml:"on_success"`
|
|
OnError []Action `yaml:"on_error"`
|
|
Decision *DecisionConfig `yaml:"decision"`
|
|
|
|
// Inline module fields (used when Path is empty)
|
|
InlinePath bool `yaml:"inline_path,omitempty"` // Marker to clarify this is an inline module (documentation only)
|
|
Steps []Step `yaml:"steps,omitempty"` // Inline steps (makes this an inline module)
|
|
Runner RunnerType `yaml:"runner,omitempty"` // Runner type for inline module
|
|
RunnerConfig *RunnerConfig `yaml:"runner_config,omitempty"` // Runner configuration for inline module
|
|
Description string `yaml:"description,omitempty"` // Description for inline module
|
|
}
|
|
|
|
// IsInline returns true if this is an inline module (has steps defined directly)
|
|
func (m *ModuleRef) IsInline() bool {
|
|
return len(m.Steps) > 0
|
|
}
|
|
|
|
// ToWorkflow converts an inline ModuleRef to a Workflow for execution
|
|
func (m *ModuleRef) ToWorkflow() *Workflow {
|
|
if !m.IsInline() {
|
|
return nil
|
|
}
|
|
return &Workflow{
|
|
Kind: KindModule,
|
|
Name: m.Name,
|
|
Description: m.Description,
|
|
Steps: m.Steps,
|
|
Runner: m.Runner,
|
|
RunnerConfig: m.RunnerConfig,
|
|
}
|
|
}
|
|
|
|
// IsModule returns true if the workflow is a module
|
|
func (w *Workflow) IsModule() bool {
|
|
return w.Kind == KindModule
|
|
}
|
|
|
|
// IsFlow returns true if the workflow is a flow
|
|
func (w *Workflow) IsFlow() bool {
|
|
return w.Kind == KindFlow
|
|
}
|
|
|
|
// GetRequiredParams returns all required parameters
|
|
func (w *Workflow) GetRequiredParams() []Param {
|
|
var required []Param
|
|
for _, p := range w.Params {
|
|
if p.Required {
|
|
required = append(required, p)
|
|
}
|
|
}
|
|
return required
|
|
}
|
|
|
|
// HasTriggers returns true if the workflow has any triggers defined
|
|
func (w *Workflow) HasTriggers() bool {
|
|
return len(w.Triggers) > 0
|
|
}
|
|
|
|
// IsManualExecutionAllowed checks if manual (CLI) execution is allowed
|
|
// Returns true if:
|
|
// - No triggers are defined (default behavior allows manual)
|
|
// - A manual trigger exists and is enabled
|
|
// - No manual trigger is explicitly defined (default is enabled)
|
|
func (w *Workflow) IsManualExecutionAllowed() bool {
|
|
// If no triggers defined, manual is allowed (default behavior)
|
|
if len(w.Triggers) == 0 {
|
|
return true
|
|
}
|
|
|
|
// Look for explicit manual trigger
|
|
for _, t := range w.Triggers {
|
|
if t.On == TriggerManual {
|
|
return t.Enabled // Use explicit setting
|
|
}
|
|
}
|
|
|
|
// No manual trigger defined among other triggers, default to true
|
|
return true
|
|
}
|
|
|
|
// GetEventTriggers returns all event-type triggers
|
|
func (w *Workflow) GetEventTriggers() []Trigger {
|
|
var triggers []Trigger
|
|
for _, t := range w.Triggers {
|
|
if t.On == TriggerEvent && t.Enabled {
|
|
triggers = append(triggers, t)
|
|
}
|
|
}
|
|
return triggers
|
|
}
|
|
|
|
// GetCronTriggers returns all enabled cron-type triggers
|
|
func (w *Workflow) GetCronTriggers() []Trigger {
|
|
var triggers []Trigger
|
|
for _, t := range w.Triggers {
|
|
if t.On == TriggerCron && t.Enabled {
|
|
triggers = append(triggers, t)
|
|
}
|
|
}
|
|
return triggers
|
|
}
|
|
|
|
// GetUsage returns the usage string from Help, or empty string if Help is nil
|
|
func (w *Workflow) GetUsage() string {
|
|
if w.Help == nil {
|
|
return ""
|
|
}
|
|
return w.Help.Usage
|
|
}
|
|
|
|
// GetExampleTargets returns example targets from Help, or nil if Help is nil
|
|
func (w *Workflow) GetExampleTargets() []string {
|
|
if w.Help == nil {
|
|
return nil
|
|
}
|
|
return w.Help.ExampleTargets
|
|
}
|
|
|
|
// HookCount returns the total number of hook steps (pre + post)
|
|
func (w *Workflow) HookCount() int {
|
|
if w.Hooks == nil {
|
|
return 0
|
|
}
|
|
return len(w.Hooks.PreScanSteps) + len(w.Hooks.PostScanSteps)
|
|
}
|