2025-07-09 13:19:43 +01:00
|
|
|
package app
|
|
|
|
|
|
|
|
|
|
import (
|
2025-08-16 18:00:28 +01:00
|
|
|
"bufio"
|
|
|
|
|
"bytes"
|
2025-07-09 13:19:43 +01:00
|
|
|
"encoding/json"
|
|
|
|
|
"fmt"
|
|
|
|
|
"log/slog"
|
|
|
|
|
"os"
|
2025-08-16 18:00:28 +01:00
|
|
|
"os/exec"
|
2025-07-09 13:19:43 +01:00
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"headshed/infctl-cli/config"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type AppState struct {
|
2025-08-16 18:00:28 +01:00
|
|
|
Config config.BaseConfig
|
|
|
|
|
Project config.ProjectConfig
|
2025-07-09 13:19:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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{}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-16 18:00:28 +01:00
|
|
|
// func (app *AppState) RunJsonDeployment() []PipelineStep {
|
|
|
|
|
func (app *AppState) RunJsonDeployment() error {
|
2025-07-09 13:19:43 +01:00
|
|
|
|
2025-08-16 18:00:28 +01:00
|
|
|
jsonFile := app.Project.DeploymentFile
|
2025-07-09 13:19:43 +01:00
|
|
|
if jsonFile == "" {
|
2025-08-16 18:00:28 +01:00
|
|
|
return fmt.Errorf("no config specified with [-f|-deployment-file]=<path_to_config_file> => for all options see help with -h")
|
2025-07-09 13:19:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
file, err := os.Open(jsonFile)
|
|
|
|
|
if err != nil {
|
|
|
|
|
slog.Error(fmt.Sprintf("Failed to open JSON file: %s", err))
|
2025-08-16 18:00:28 +01:00
|
|
|
return fmt.Errorf("failed to open JSON file: %w", err)
|
2025-07-09 13:19:43 +01:00
|
|
|
}
|
|
|
|
|
defer file.Close()
|
|
|
|
|
|
2025-08-16 18:00:28 +01:00
|
|
|
// fmt.Printf("jsonFile is : %s\n", jsonFile)
|
|
|
|
|
slog.Info(fmt.Sprintf("Using jsonFile: %s", jsonFile))
|
2025-07-09 13:19:43 +01:00
|
|
|
steps, err := parseStepsFromJSON(jsonFile)
|
|
|
|
|
if err != nil {
|
|
|
|
|
slog.Error(fmt.Sprintf("Failed to parse JSON file: %s", err))
|
2025-08-16 18:00:28 +01:00
|
|
|
return fmt.Errorf("failed to parse JSON file: %w", err)
|
2025-07-09 13:19:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, step := range steps {
|
2025-08-16 18:00:28 +01:00
|
|
|
slog.Info(fmt.Sprintf("run json deployment => 🔄 %s", step.Name))
|
2025-07-09 13:19:43 +01:00
|
|
|
function, exists := functionMap[step.Function]
|
|
|
|
|
if !exists {
|
|
|
|
|
slog.Error(fmt.Sprintf("Unknown function: %s", step.Function))
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err := function(step.Params)
|
2025-08-16 18:00:28 +01:00
|
|
|
|
2025-07-09 13:19:43 +01:00
|
|
|
if err != nil {
|
2025-08-16 18:00:28 +01:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-09 13:19:43 +01:00
|
|
|
if step.ShouldAbort {
|
2025-08-16 18:00:28 +01:00
|
|
|
return fmt.Errorf("critical failure at step: %s", step.Name)
|
2025-07-09 13:19:43 +01:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
slog.Info(fmt.Sprintf("✅ Step completed: %s", step.Name))
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-08-16 18:00:28 +01:00
|
|
|
return nil
|
2025-07-09 13:19:43 +01:00
|
|
|
}
|
|
|
|
|
|
2025-08-16 18:00:28 +01:00
|
|
|
func (app *AppState) getPipeline() error {
|
|
|
|
|
switch app.Project.DeploymentMode {
|
2025-07-09 13:19:43 +01:00
|
|
|
case "api":
|
2025-08-16 18:00:28 +01:00
|
|
|
return fmt.Errorf("api mode is not yet implemented")
|
2025-07-09 13:19:43 +01:00
|
|
|
case "json":
|
|
|
|
|
return app.RunJsonDeployment()
|
|
|
|
|
default:
|
2025-08-16 18:00:28 +01:00
|
|
|
// return app.RunJsonDeployment()
|
|
|
|
|
return fmt.Errorf("unknown mode: %s", app.Project.DeploymentMode)
|
2025-07-09 13:19:43 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-16 18:00:28 +01:00
|
|
|
func NewAppState(cust config.ProjectConfig, config config.BaseConfig) (*AppState, error) {
|
2025-07-09 13:19:43 +01:00
|
|
|
return &AppState{
|
2025-08-16 18:00:28 +01:00
|
|
|
Config: config,
|
|
|
|
|
Project: cust,
|
2025-07-09 13:19:43 +01:00
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-16 18:00:28 +01:00
|
|
|
func RunCommand(command string) error {
|
|
|
|
|
slog.Debug(fmt.Sprintf("🐞 Running command: %s", command))
|
|
|
|
|
cmd := exec.Command("sh", "-c", command)
|
2025-07-09 13:19:43 +01:00
|
|
|
|
2025-08-16 18:00:28 +01:00
|
|
|
var stdout, stderr bytes.Buffer
|
2025-07-09 13:19:43 +01:00
|
|
|
|
2025-08-16 18:00:28 +01:00
|
|
|
// 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)
|
|
|
|
|
}
|
2025-07-09 13:19:43 +01:00
|
|
|
|
2025-08-16 18:00:28 +01:00
|
|
|
// Start the command
|
|
|
|
|
if err := cmd.Start(); err != nil {
|
|
|
|
|
return fmt.Errorf("failed to start command: %w", err)
|
|
|
|
|
}
|
2025-07-09 13:19:43 +01:00
|
|
|
|
2025-08-16 18:00:28 +01:00
|
|
|
// 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)
|
2025-07-09 13:19:43 +01:00
|
|
|
}
|
2025-08-16 18:00:28 +01:00
|
|
|
}()
|
2025-07-09 13:19:43 +01:00
|
|
|
|
2025-08-16 18:00:28 +01:00
|
|
|
// 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)
|
2025-07-09 13:19:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-16 18:00:28 +01:00
|
|
|
func (app *AppState) SetUpNewProject() error {
|
|
|
|
|
return app.getPipeline()
|
|
|
|
|
}
|
2025-07-09 13:19:43 +01:00
|
|
|
|
2025-08-16 18:00:28 +01:00
|
|
|
func (app *AppState) CreateProjectAndRunPipeline() error {
|
|
|
|
|
err := app.SetUpNewProject()
|
2025-07-09 13:19:43 +01:00
|
|
|
if err != nil {
|
2025-08-16 18:00:28 +01:00
|
|
|
return fmt.Errorf("Pipeline error: %w", err)
|
2025-07-09 13:19:43 +01:00
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|