infctl-cli/app/app.go
jon brookes 924954d0ff changed app to use json config for pipeline steps
readme command line usage - to specify pipeline file name
readme updated to include reasoning behind the project

use native golang sqlite

RunScriptCommand named in functionMap
removed unused functions
removed unused functions
run script and pipeline example
renamed functions to drop the word script and add pipeline verb
2025-07-14 16:34:15 +01:00

225 lines
5.2 KiB
Go

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=<path_to_config_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("🎉 Customer 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
}