Skip to content

Commit

Permalink
refactor(deployment): replace spinner manager with pin library for im…
Browse files Browse the repository at this point in the history
…proved progress tracking
  • Loading branch information
yarlson committed Feb 12, 2025
1 parent 77e8b98 commit 9fd0f8b
Show file tree
Hide file tree
Showing 10 changed files with 91 additions and 48 deletions.
23 changes: 16 additions & 7 deletions cmd/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import (
"os"
"path/filepath"

"github.com/yarlson/pin"

"github.com/spf13/cobra"

"github.com/yarlson/ftl/pkg/config"
"github.com/yarlson/ftl/pkg/console"
"github.com/yarlson/ftl/pkg/deployment"
"github.com/yarlson/ftl/pkg/imagesync"
"github.com/yarlson/ftl/pkg/runner/remote"
Expand All @@ -30,18 +31,22 @@ func init() {
}

func runDeploy(cmd *cobra.Command, args []string) {
pDeploy := pin.New("Deploying", pin.WithSpinnerColor(pin.ColorCyan))
cancelDeploy := pDeploy.Start(context.Background())
defer cancelDeploy()

cfg, err := parseConfig("ftl.yaml")
if err != nil {
console.Error("Failed to parse config file:", err)
pDeploy.Fail(fmt.Sprintf("Failed to parse config file: %v", err))
return
}

if err := deployToServer(cfg.Project.Name, cfg, cfg.Server); err != nil {
console.Error("Deployment failed:", err)
if err := deployToServer(cfg.Project.Name, cfg, cfg.Server, pDeploy); err != nil {
pDeploy.Fail(fmt.Sprintf("Deployment failed: %v", err))
return
}

console.Success("Deployment completed successfully")
pDeploy.Stop("Deployment completed successfully")
}

func parseConfig(filename string) (*config.Config, error) {
Expand All @@ -58,22 +63,25 @@ func parseConfig(filename string) (*config.Config, error) {
return cfg, nil
}

func deployToServer(project string, cfg *config.Config, server config.Server) error {
func deployToServer(project string, cfg *config.Config, server config.Server, spinner *pin.Pin) error {
hostname := server.Host

spinner.UpdateMessage("Connecting to server " + hostname + "...")
// Connect to server
runner, err := connectToServer(server)
if err != nil {
return fmt.Errorf("failed to connect to server %s: %w", hostname, err)
}
defer runner.Close()

spinner.UpdateMessage("Connected to server " + hostname + ". Creating temporary directory for docker sync...")
// Create temp directory for docker sync
localStore, err := os.MkdirTemp("", "dockersync-local")
if err != nil {
return fmt.Errorf("failed to create local store: %w", err)
}

spinner.UpdateMessage("Temporary directory created. Initializing image syncer and deployment...")
// Initialize image syncer and deployment
syncer := imagesync.NewImageSync(imagesync.Config{
LocalStore: localStore,
Expand All @@ -85,7 +93,8 @@ func deployToServer(project string, cfg *config.Config, server config.Server) er
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

if err := deploy.Deploy(ctx, project, cfg); err != nil {
spinner.UpdateMessage("Starting deployment process...")
if err := deploy.Deploy(ctx, project, cfg, spinner); err != nil {
return err
}

Expand Down
6 changes: 4 additions & 2 deletions cmd/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func init() {

func runSetup(cmd *cobra.Command, args []string) {
pConfig := pin.New("Parsing configuration", pin.WithSpinnerColor(pin.ColorCyan))
pConfig.Start(context.Background())
cancelConfig := pConfig.Start(context.Background())
cfg, err := parseConfig("ftl.yaml")
if err != nil {
Expand All @@ -39,6 +40,7 @@ func runSetup(cmd *cobra.Command, args []string) {
cancelConfig()

pDocker := pin.New("Checking Docker credentials", pin.WithSpinnerColor(pin.ColorCyan))
pDocker.Start(context.Background())
cancelDocker := pDocker.Start(context.Background())
dockerCreds, err := getDockerCredentials(cfg.Services)
if err != nil {
Expand All @@ -54,17 +56,17 @@ func runSetup(cmd *cobra.Command, args []string) {
console.Error("Failed to read password:", err)
return
}
console.ClearPreviousLine()
console.Success("Password set successfully")

pSetup := pin.New("Setting up server", pin.WithSpinnerColor(pin.ColorCyan))
pSetup.Start(context.Background())
cancelSetup := pSetup.Start(context.Background())
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
defer cancel()
if err := server.Setup(ctx, cfg, server.DockerCredentials{
Username: dockerCreds.Username,
Password: dockerCreds.Password,
}, newUserPassword); err != nil {
}, newUserPassword, pSetup); err != nil {
pSetup.Fail(fmt.Sprintf("Setup failed: %v", err))
cancelSetup()
return
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ require (
github.com/spf13/cobra v1.8.1
github.com/stretchr/testify v1.10.0
github.com/testcontainers/testcontainers-go v0.35.0
github.com/yarlson/pin v0.7.1
github.com/yarlson/pin v0.7.2
golang.org/x/crypto v0.33.0
golang.org/x/term v0.29.0
gopkg.in/yaml.v3 v3.0.1
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,8 @@ github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZ
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo=
github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI=
github.com/yarlson/pin v0.7.1 h1:TsW/5MQwx3MbayQXsn9KErhocxBB6kXfLjyqyhEoVsA=
github.com/yarlson/pin v0.7.1/go.mod h1:FC/d9PacAtwh05XzSznZWhA447uvimitjgDDl5YaVLE=
github.com/yarlson/pin v0.7.2 h1:KQu2HpJ7Pki/4S/uwEW4PVkbEP916wwmzkjSI+B6RP0=
github.com/yarlson/pin v0.7.2/go.mod h1:FC/d9PacAtwh05XzSznZWhA447uvimitjgDDl5YaVLE=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
Expand Down
4 changes: 0 additions & 4 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,16 @@ import (
"syscall"

"github.com/yarlson/ftl/cmd"
"github.com/yarlson/ftl/pkg/console"
)

func main() {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
console.Reset()
os.Exit(1)
}()

defer console.Reset()

if err := cmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
Expand Down
59 changes: 36 additions & 23 deletions pkg/console/console.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,42 @@ import (
"golang.org/x/term"
)

var (
colorReset = "\033[0m"
colorRed = "\033[91m" // Bright Red
colorGreen = "\033[92m" // Bright Green
colorYellow = "\033[93m" // Bright Yellow
// Color represents an ANSI color code.
type Color int

// Available colors.
const (
ColorReset Color = iota
ColorRed
ColorGreen
ColorYellow
)

var disableColor bool

// String returns the ANSI escape code for the given color.
// If colors are disabled, it returns an empty string.
func (c Color) String() string {
if disableColor {
return ""
}
switch c {
case ColorReset:
return "\033[0m"
case ColorRed:
return "\033[91m"
case ColorGreen:
return "\033[92m"
case ColorYellow:
return "\033[93m"
default:
return ""
}
}

func init() {
if _, exists := os.LookupEnv("NO_COLOR"); exists {
colorReset = ""
colorRed = ""
colorGreen = ""
colorYellow = ""
disableColor = true
}
}

Expand All @@ -34,25 +57,25 @@ func Info(a ...interface{}) {
// Success prints a success message.
func Success(a ...interface{}) {
message := fmt.Sprint(a...)
fmt.Printf("%s✓%s %s\n", colorGreen, colorReset, message)
fmt.Printf("%s✓%s %s\n", ColorGreen, ColorReset, message)
}

// Warning prints a warning message.
func Warning(a ...interface{}) {
message := fmt.Sprint(a...)
fmt.Printf("%s!%s %s\n", colorYellow, colorReset, message)
fmt.Printf("%s!%s %s\n", ColorYellow, ColorReset, message)
}

// Error prints an error message with a newline.
func Error(a ...interface{}) {
message := fmt.Sprint(a...)
fmt.Printf("%s✘%s %s\n", colorRed, colorReset, message)
fmt.Printf("%s✘%s %s\n", ColorRed, ColorReset, message)
}

// Input prints an input prompt.
func Input(a ...interface{}) {
message := fmt.Sprint(a...)
fmt.Printf("%s%s%s", colorYellow, message, colorReset)
fmt.Printf("%s%s%s", ColorYellow, message, ColorReset)
}

// ReadLine reads a line from standard input.
Expand All @@ -79,13 +102,3 @@ func ReadPassword() (string, error) {
func Print(a ...interface{}) {
fmt.Println(a...)
}

// Reset ensures the cursor is visible and terminal is in a normal state.
func Reset() {
_ = os.Stdout.Sync()
fmt.Print("\033[?25h")
}

func ClearPreviousLine() {
fmt.Print("\033[1A\033[K")
}
9 changes: 8 additions & 1 deletion pkg/deployment/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/yarlson/ftl/pkg/runner/local"

"github.com/yarlson/ftl/pkg/config"
"github.com/yarlson/pin"
)

const (
Expand Down Expand Up @@ -37,18 +38,21 @@ func NewDeployment(runner Runner, syncer ImageSyncer) *Deployment {
return &Deployment{runner: runner, syncer: syncer, localRunner: local.NewRunner()}
}

func (d *Deployment) Deploy(ctx context.Context, project string, cfg *config.Config) error {
func (d *Deployment) Deploy(ctx context.Context, project string, cfg *config.Config, spinner *pin.Pin) error {
spinner.UpdateMessage("Creating project network...")
// Create project network
if err := d.createNetwork(project); err != nil {
return fmt.Errorf("failed to create network: %w", err)
}

spinner.UpdateMessage("Creating volumes...")
// Create volumes
cfg.Volumes = append(cfg.Volumes, "certs")
if err := d.createVolumes(ctx, project, cfg.Volumes); err != nil {
return fmt.Errorf("failed to create volumes: %w", err)
}

spinner.UpdateMessage("Deploying dependencies...")
// Deploy dependencies
if err := d.deployDependencies(ctx, project, cfg.Dependencies); err != nil {
return fmt.Errorf("failed to deploy dependencies: %w", err)
Expand All @@ -59,18 +63,21 @@ func (d *Deployment) Deploy(ctx context.Context, project string, cfg *config.Con
defer tunnelCancel()

if hasLocalHooks(cfg) {
spinner.UpdateMessage("Starting tunnels for local hooks...")
if err := d.startTunnels(tunnelCtx, cfg); err != nil {
return fmt.Errorf("failed to start tunnels: %w", err)
}
}

spinner.UpdateMessage("Deploying services...")
// Deploy services
if err := d.deployServices(ctx, project, cfg.Services); err != nil {
return fmt.Errorf("failed to deploy services: %w", err)
}

tunnelCancel()

spinner.UpdateMessage("Starting proxy configuration...")
// Setup proxy
if err := d.startProxy(ctx, project, cfg); err != nil {
return fmt.Errorf("failed to start proxy: %w", err)
Expand Down
4 changes: 2 additions & 2 deletions pkg/deployment/deployment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ func (suite *DeploymentTestSuite) TestDeploy() {
defer cancel()

// Initial deployment
err := suite.deployment.Deploy(ctx, project, cfg)
err := suite.deployment.Deploy(ctx, project, cfg, nil)
suite.Require().NoError(err, "Initial deployment should succeed")

time.Sleep(5 * time.Second)
Expand Down Expand Up @@ -203,7 +203,7 @@ func (suite *DeploymentTestSuite) TestDeploy() {
cfg.Services[0].Image = "nginx:1.20"
suite.T().Logf("Updating service image to nginx:1.20")

err = suite.deployment.Deploy(ctx, project, cfg)
err = suite.deployment.Deploy(ctx, project, cfg, nil)
suite.Require().NoError(err, "Service update should succeed")

time.Sleep(2 * time.Second)
Expand Down
5 changes: 3 additions & 2 deletions pkg/deployment/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ package deployment
import (
"context"
"fmt"
"github.com/yarlson/ftl/pkg/config"
"github.com/yarlson/ftl/pkg/proxy"
"os"
"path/filepath"
"strings"

"github.com/yarlson/ftl/pkg/config"
"github.com/yarlson/ftl/pkg/proxy"
)

func (d *Deployment) startProxy(ctx context.Context, project string, cfg *config.Config) error {
Expand Down
Loading

0 comments on commit 9fd0f8b

Please sign in to comment.