tested initial dev deployment to local k3d Reviewed-on: https://codeberg.org/headshed/infctl-cli/pulls/2 Co-authored-by: jon brookes <jon@headshed.dev> Co-committed-by: jon brookes <jon@headshed.dev>
225 lines
5.2 KiB
Go
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("🎉 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
|
|
}
|