package app import ( "bufio" "bytes" "encoding/json" "fmt" "log/slog" "os" "os/exec" "time" "headshed/infctl-cli/config" ) type AppState struct { Config config.BaseConfig Project config.ProjectConfig } type PipelineStep struct { Name string Function string // Name of the function to call Params []string // Parameters for the function RetryCount int ShouldAbort bool } var functionMap = map[string]func([]string) error{ "k8sNamespaceExists": func(params []string) error { if len(params) != 1 { return fmt.Errorf("invalid parameters for k8sNamespaceExists") } return k8sNamespaceExists(params[0]) }, "RunCommand": func(params []string) error { if len(params) != 1 { return fmt.Errorf("invalid parameters for RunCommand") } return RunCommand(params[0]) }, // Add more functions as needed } func parseStepsFromJSON(filePath string) ([]PipelineStep, error) { data, err := os.ReadFile(filePath) if err != nil { return nil, fmt.Errorf("failed to read JSON file: %w", err) } var steps []PipelineStep if err := json.Unmarshal(data, &steps); err != nil { return nil, fmt.Errorf("failed to unmarshal JSON: %w", err) } return steps, nil } func (app *AppState) ToDoDeployment() []PipelineStep { slog.Info("ToDo deployment is not implemented yet") return []PipelineStep{} } // func (app *AppState) RunJsonDeployment() []PipelineStep { func (app *AppState) RunJsonDeployment() error { jsonFile := app.Project.DeploymentFile if jsonFile == "" { return fmt.Errorf("no config specified with [-f|-deployment-file]= => for all options see help with -h") } file, err := os.Open(jsonFile) if err != nil { slog.Error(fmt.Sprintf("Failed to open JSON file: %s", err)) return fmt.Errorf("failed to open JSON file: %w", err) } defer file.Close() // fmt.Printf("jsonFile is : %s\n", jsonFile) slog.Info(fmt.Sprintf("Using jsonFile: %s", jsonFile)) steps, err := parseStepsFromJSON(jsonFile) if err != nil { slog.Error(fmt.Sprintf("Failed to parse JSON file: %s", err)) return fmt.Errorf("failed to parse JSON file: %w", err) } for _, step := range steps { slog.Info(fmt.Sprintf("run json deployment => 🔄 %s", step.Name)) function, exists := functionMap[step.Function] if !exists { slog.Error(fmt.Sprintf("Unknown function: %s", step.Function)) continue } err := function(step.Params) if err != nil { var innerErr error if step.RetryCount > 0 { for i := 0; i < step.RetryCount; i++ { sleep := app.Config.RetryDelaySenconds slog.Info(fmt.Sprintf("Retrying step: %s (attempt %d/%d) after waiting for %d seconds...", step.Name, i+1, step.RetryCount, sleep)) time.Sleep(time.Duration(sleep) * time.Second) if innerErr = function(step.Params); innerErr == nil { slog.Info(fmt.Sprintf("✅ Step completed: %s\n", step.Name)) err = nil break } } if innerErr != nil { if !step.ShouldAbort { slog.Info(fmt.Sprintf("Not going to abort, step: %s", step.Name)) } else { return fmt.Errorf("critical failure at step: %s", step.Name) } } } if step.ShouldAbort { return fmt.Errorf("critical failure at step: %s", step.Name) } } else { slog.Info(fmt.Sprintf("✅ Step completed: %s", step.Name)) } } return nil } func (app *AppState) getPipeline() error { switch app.Project.DeploymentMode { case "api": return fmt.Errorf("api mode is not yet implemented") case "json": return app.RunJsonDeployment() default: // return app.RunJsonDeployment() return fmt.Errorf("unknown mode: %s", app.Project.DeploymentMode) } } func NewAppState(cust config.ProjectConfig, config config.BaseConfig) (*AppState, error) { return &AppState{ Config: config, Project: cust, }, nil } func RunCommand(command string) error { slog.Debug(fmt.Sprintf("🐞 Running command: %s", command)) cmd := exec.Command("sh", "-c", command) var stdout, stderr bytes.Buffer // Get pipes for real-time reading stdoutPipe, err := cmd.StdoutPipe() if err != nil { return fmt.Errorf("failed to create stdout pipe: %w", err) } stderrPipe, err := cmd.StderrPipe() if err != nil { return fmt.Errorf("failed to create stderr pipe: %w", err) } // Start the command if err := cmd.Start(); err != nil { return fmt.Errorf("failed to start command: %w", err) } // Read stdout line by line and log through slog go func() { scanner := bufio.NewScanner(stdoutPipe) for scanner.Scan() { line := scanner.Text() stdout.WriteString(line + "\n") slog.Info(line) } }() // Read stderr line by line and log through slog go func() { scanner := bufio.NewScanner(stderrPipe) for scanner.Scan() { line := scanner.Text() stderr.WriteString(line + "\n") slog.Info(line) } }() // Wait for command to complete err = cmd.Wait() if err != nil { slog.Error(fmt.Sprintf("❌ Command failed with error: %v", err)) slog.Debug(fmt.Sprintf("🐞 Stdout: %s\n", stdout.String())) slog.Debug(fmt.Sprintf("🐞 Stderr: %s\n", stderr.String())) return fmt.Errorf("failed to run script command: %w", err) } return nil } func (app *AppState) SetUpNewProject() error { return app.getPipeline() } func (app *AppState) CreateProjectAndRunPipeline() error { err := app.SetUpNewProject() if err != nil { return fmt.Errorf("Pipeline error: %w", err) } return nil }