update: Added Longhorn installation process and updated memory allocation for VMs

update: Added 'git' and 'vagrant' to required tools in pre-flight checks

fix: configured k3s install to use internal nic for flanel network

update: Added Longhorn installation process and updated memory allocation for VMs

update: Added 'git' and 'vagrant' to required tools in pre-flight checks

fix: configured k3s install to use internal nic for flanel network

fix: corrected JSON formatting for config json

update: reduce VM memory allocation to 2GB, add Longhorn installation scripts and prerequisites, and implement checks for existing pods

fix: merge issues

fix: merge issues

update: Added Longhorn installation process and updated memory allocation for VMs

update: Added 'git' and 'vagrant' to required tools in pre-flight checks

fix: configured k3s install to use internal nic for flanel network

update: Added Longhorn installation process and updated memory allocation for VMs

update: Added 'git' and 'vagrant' to required tools in pre-flight checks

fix: configured k3s install to use internal nic for flanel network

fix: corrected JSON formatting for config json

update: reduce VM memory allocation to 2GB, add Longhorn installation scripts and prerequisites, and implement checks for existing pods

update: improve error logging in RunJsonDeployment and RunCommand functions

update: add jq installation to provision script

update: add version flag

bump version

fix: improve error messages for config file reading

feat: add Windows gitbash installation support and improve binary download process

clean up tmp code

fix: increase timeout for some slower windows clients

feat: add Ingress and Service configurations for nginx deployment, and implement MetalLB  and Traeik installation scripts

refactor: remove obsolete Traefik installation script

feat: add environment checks and configurations for Vagrant setup, including dnsmasq  MetalLB  and ingress

feat: add deployment and installation scripts for infmon-cli, including Kubernetes configurations

feat: refactor customer project creation and add success/failure job scripts

refactor: rename customer references to project in configuration and application logic

feat: enhance JSON deployment handling with retry logic and command execution improvements

feat: enhance RunJsonDeployment with error handling and retry logic; add tests for configuration reading

feat: add automatic creation of base and config JSON files from examples if they do not exist

refactor: remove database package and related functionality; update app state initialization and error handling

refactor: update deployment handling to use ProjectConfig; improve error messages and logging

feat: enhance RunJsonDeployment retry logic with configurable delay; improve logging for retries

feat: implement LoadConfigs function for improved configuration loading; add logger setup

refactor: remove unused fields from BaseConfig and ProjectConfig structs for cleaner configuration management

refactor: clean up tests by removing obsolete functions and simplifying test cases

chore: update version to v0.0.5 in install script

feat: implement default configuration creation for BaseConfig and ProjectConfig; enhance validation logic

fix: enhance configuration parsing and loading; streamline flag handling and error reporting

refactor: remove obsolete configuration download logic from installation script
This commit is contained in:
jon brookes 2025-08-16 18:00:28 +01:00
parent d839fd5687
commit 11b1f1b637
61 changed files with 1573 additions and 761 deletions

View file

@ -1,22 +1,21 @@
package app
import (
"database/sql"
"bufio"
"bytes"
"encoding/json"
"fmt"
"log"
"log/slog"
"os"
"os/exec"
"time"
"headshed/infctl-cli/config"
"headshed/infctl-cli/database"
)
type AppState struct {
Config config.BaseConfig
Customer config.CustomerConfig
DB *sql.DB
Config config.BaseConfig
Project config.ProjectConfig
}
type PipelineStep struct {
@ -62,27 +61,31 @@ func (app *AppState) ToDoDeployment() []PipelineStep {
return []PipelineStep{}
}
func (app *AppState) RunJsonDeployment() []PipelineStep {
// func (app *AppState) RunJsonDeployment() []PipelineStep {
func (app *AppState) RunJsonDeployment() error {
jsonFile := app.Config.DeploymentFile
jsonFile := app.Project.DeploymentFile
if jsonFile == "" {
log.Fatal("no config specified with --deployment-file=<path_to_config_file>")
return fmt.Errorf("no config specified with [-f|-deployment-file]=<path_to_config_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("🔄 Running step: %s", step.Name))
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))
@ -90,136 +93,121 @@ func (app *AppState) RunJsonDeployment() []PipelineStep {
}
err := function(step.Params)
if err != nil {
slog.Error(fmt.Sprintf("❌ Step failed: %s, error: %v", step.Name, err))
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 {
log.Fatalf("🚨Critical failure at step: %s", step.Name)
return fmt.Errorf("critical failure at step: %s", step.Name)
}
} else {
slog.Info(fmt.Sprintf("✅ Step completed: %s", step.Name))
}
}
return steps
return nil
}
func (app *AppState) getPipeline() []PipelineStep {
switch app.Config.DeploymentType {
func (app *AppState) getPipeline() error {
switch app.Project.DeploymentMode {
case "api":
return app.ToDoDeployment()
return fmt.Errorf("api mode is not yet implemented")
case "json":
return app.RunJsonDeployment()
default:
return app.RunJsonDeployment()
// return app.RunJsonDeployment()
return fmt.Errorf("unknown mode: %s", app.Project.DeploymentMode)
}
}
func NewAppState(cust config.CustomerConfig, config config.BaseConfig, dbPath string) (*AppState, error) {
db, err := database.NewDatabase(dbPath)
if err != nil {
return nil, err
}
func NewAppState(cust config.ProjectConfig, config config.BaseConfig) (*AppState, error) {
return &AppState{
Config: config,
Customer: cust,
DB: db,
Config: config,
Project: cust,
}, nil
}
func (app *AppState) runPipeline(steps []PipelineStep) error {
for _, step := range steps {
slog.Info(fmt.Sprintf("🔄 Running step: %s\n", step.Name))
func RunCommand(command string) error {
slog.Debug(fmt.Sprintf("🐞 Running command: %s", command))
cmd := exec.Command("sh", "-c", command)
// 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
}
var stdout, stderr bytes.Buffer
// 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)
// Get pipes for real-time reading
stdoutPipe, err := cmd.StdoutPipe()
if err != nil {
return fmt.Errorf("failed to check project name: %w", err)
return fmt.Errorf("failed to create stdout pipe: %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()
stderrPipe, err := cmd.StderrPipe()
if err != nil {
return fmt.Errorf("failed to set up new customer: %w", err)
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
}