package app import ( "database/sql" "encoding/json" "fmt" "log" "log/slog" "os" "time" "headshed/infctl-cli/config" "headshed/infctl-cli/database" ) type AppState struct { Config config.BaseConfig Customer config.CustomerConfig DB *sql.DB } 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 { jsonFile := app.Config.DeploymentFile if jsonFile == "" { log.Fatal("no config specified with --deployment-file=") } file, err := os.Open(jsonFile) if err != nil { slog.Error(fmt.Sprintf("Failed to open JSON file: %s", err)) } defer file.Close() steps, err := parseStepsFromJSON(jsonFile) if err != nil { slog.Error(fmt.Sprintf("Failed to parse JSON file: %s", err)) } for _, step := range steps { slog.Info(fmt.Sprintf("🔄 Running step: %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 { slog.Error(fmt.Sprintf("❌ Step failed: %s, error: %v", step.Name, err)) if step.ShouldAbort { log.Fatalf("🚨Critical failure at step: %s", step.Name) } } else { slog.Info(fmt.Sprintf("✅ Step completed: %s", step.Name)) } } return steps } func (app *AppState) getPipeline() []PipelineStep { switch app.Config.DeploymentType { case "api": return app.ToDoDeployment() case "json": return app.RunJsonDeployment() default: return app.RunJsonDeployment() } } func NewAppState(cust config.CustomerConfig, config config.BaseConfig, dbPath string) (*AppState, error) { db, err := database.NewDatabase(dbPath) if err != nil { return nil, err } return &AppState{ Config: config, Customer: cust, DB: db, }, nil } func (app *AppState) runPipeline(steps []PipelineStep) error { for _, step := range steps { slog.Info(fmt.Sprintf("🔄 Running step: %s\n", step.Name)) // Look up the function in the functionMap function, exists := functionMap[step.Function] if !exists { slog.Error(fmt.Sprintf("❌ Unknown function: %s", step.Function)) if step.ShouldAbort { return fmt.Errorf("🚨critical failure: unknown function %s", step.Function) } continue } // Execute the function with the provided parameters err := function(step.Params) if err != nil { slog.Error(fmt.Sprintf("❌ Step failed: %s, error: %v", step.Name, err)) // Retry logic if step.RetryCount > 0 { for i := 0; i < step.RetryCount; i++ { slog.Info("Waiting for 20 seconds before retrying...") time.Sleep(20 * time.Second) if innerErr := function(step.Params); innerErr == nil { slog.Info(fmt.Sprintf("✅ Step completed: %s\n", step.Name)) err = nil break } else { err = innerErr } } } // Handle failure after retries if err != nil { if step.ShouldAbort { return fmt.Errorf("🚨critical failure at step: %s", step.Name) } continue } } slog.Info(fmt.Sprintf("✅ Step completed: %s\n", step.Name)) } return nil } func (app *AppState) SetUpNewCustomer() error { /* | -------------------------- | main pipeline | -------------------------- */ steps := app.getPipeline() app.runPipeline(steps) slog.Info(fmt.Sprintln("🎉 Pipeline setup complete!")) return nil } func (app *AppState) CreatePipeline() error { isNew, err := database.CheckProjectName(app.DB, app.Customer.Project) if err != nil { return fmt.Errorf("failed to check project name: %w", err) } if isNew { port, err := database.GetNextPortNumber(app.DB) if err != nil { return fmt.Errorf("failed to get next port number: %w", err) } err = database.AddProjectName(app.DB, app.Customer.Project, port) if err != nil { return fmt.Errorf("failed to add project name: %w", err) } slog.Info(fmt.Sprintln("Project name added:", app.Customer.Project)) fmt.Printf("Port number assigned: %d\n", port) app.Config.Port = port } else { slog.Info(fmt.Sprintln("Project name already exists:", app.Customer.Project)) } err = app.SetUpNewCustomer() if err != nil { return fmt.Errorf("failed to set up new customer: %w", err) } return nil }