deploy.go

112 lines
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
package scaffold

import (
	"fmt"
	"log"

	"congo.gg/pkg/platform"
)

// DeployToInstance deploys services to a specific instance.
func DeployToInstance(
	p *platform.Platform,
	imagePaths map[string]string,
	cfg *InfraConfig,
	serverType string,
	inst *Instance,
	region platform.Region,
) error {
	spec := cfg.Servers[serverType]
	log.Printf("Deploying to %s (%s)...", inst.Name, inst.IP)

	server := inst.ToServer()

	// Run server setup script (idempotent, runs every deploy)
	if spec.Setup != "" {
		log.Printf("   Running server setup: %s", spec.Setup)
		if err := server.RunScript(spec.Setup); err != nil {
			return fmt.Errorf("server setup %s: %w", spec.Setup, err)
		}
	}

	for _, vol := range spec.Volumes {
		if err := ensureVolume(p, server, &vol, region); err != nil {
			return fmt.Errorf("volume %s: %w", vol.Name, err)
		}
	}

	SyncSecrets(server, cfg.Services)

	imageNames := uploadImages(server, spec.Services, cfg.Services, imagePaths)

	swapContainers(server, spec.Services, cfg.Services)
	createNetworks(server, spec.Services, cfg.Services)

	for _, svcName := range spec.Services {
		svcSpec := cfg.Services[svcName]

		// Run service setup script on host before starting container
		if svcSpec.Setup != "" {
			log.Printf("   Running service setup for %s: %s", svcName, svcSpec.Setup)
			if err := server.RunScript(svcSpec.Setup); err != nil {
				return fmt.Errorf("service setup %s: %w", svcName, err)
			}
		}

		if err := startService(server, svcName, &svcSpec, imageNames[svcName], spec.Volumes); err != nil {
			return fmt.Errorf("start %s: %w", svcName, err)
		}
	}

	log.Printf("Deployed to %s", inst.Name)
	return nil
}

func uploadImages(server *platform.Server, serviceNames []string, services map[string]ServiceSpec, imagePaths map[string]string) map[string]string {
	imageNames := make(map[string]string)
	for _, svcName := range serviceNames {
		svcSpec := services[svcName]
		if svcSpec.Source == "" {
			continue
		}
		localPath := imagePaths[svcName]
		if localPath == "" {
			imageName, err := server.Build(svcName, svcSpec.Source)
			if err != nil {
				log.Printf("   build %s on server: %v", svcName, err)
				continue
			}
			imageNames[svcName] = imageName
		} else {
			imageName, err := server.Upload(localPath)
			if err != nil {
				log.Printf("   upload %s: %v", svcName, err)
				continue
			}
			imageNames[svcName] = imageName
		}
	}
	return imageNames
}

func swapContainers(server *platform.Server, serviceNames []string, services map[string]ServiceSpec) {
	for _, svcName := range serviceNames {
		if services[svcName].Source != "" {
			server.Backup(svcName)
		} else {
			server.Stop(svcName)
		}
	}
}

func createNetworks(server *platform.Server, serviceNames []string, services map[string]ServiceSpec) {
	networks := make(map[string]bool)
	for _, svcName := range serviceNames {
		if n := services[svcName].Network; n != "" {
			networks[n] = true
		}
	}
	for network := range networks {
		server.SSH("docker", "network", "create", network, "--driver", "bridge")
	}
}