localenv/deployer/deployer.go
2023-08-05 22:10:48 +02:00

212 lines
4.2 KiB
Go

package deployer
import (
"context"
"errors"
"strings"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
"github.com/docker/go-connections/nat"
"github.com/rs/zerolog/log"
localcli "localenv/client"
"localenv/services"
)
const (
SwarmImageName string = "localenv-swarm:latest"
SwarmContainerName string = "localenv-swarm"
)
var (
ErrSwarmNotFound = errors.New("unable to find the swarm")
ErrSwarmClientInit = errors.New("swarm client is not initialized")
ImagesDeps = []string{
services.PostgresImageName,
services.RabbitMQImageName,
services.NginxImageName,
services.MailhogImageName,
}
)
type Deployer struct {
swarm Swarm
cli *client.Client
}
func NewDeployer(ctx context.Context, cli *client.Client) (Deployer, error) {
var deployer Deployer
deployer.cli = cli
if err := deployer.init(ctx); err != nil {
return deployer, err
}
return deployer, nil
}
func (d Deployer) getSwarm(ctx context.Context) (string, error) {
filterArgs := filters.NewArgs()
filterArgs.Add("name", SwarmContainerName)
filterArgs.Add("ancestor", SwarmImageName)
options := types.ContainerListOptions{
Filters: filterArgs,
All: true,
}
containers, err := d.cli.ContainerList(ctx, options)
if err != nil {
return "", err
}
if len(containers) == 0 {
return "", ErrSwarmNotFound
}
return containers[0].ID, nil
}
func (d Deployer) createSwarm(ctx context.Context) (string, error) {
containerConfig := container.Config{
Image: SwarmImageName,
ExposedPorts: nat.PortSet{
"4523/tcp": struct{}{},
"4443/tcp": struct{}{},
"15672/tcp": struct{}{},
},
}
hostConfig := container.HostConfig{
Runtime: "sysbox-runc",
PortBindings: nat.PortMap{
// swarm dockerd
"4523/tcp": []nat.PortBinding{
{
HostIP: "0.0.0.0",
HostPort: "4523",
},
},
// nginx ssl
"4443/tcp": []nat.PortBinding{
{
HostIP: "0.0.0.0",
HostPort: "4443",
},
},
// rabbitmq admin interface
"15672/tcp": []nat.PortBinding{
{
HostIP: "0.0.0.0",
HostPort: "15672",
},
},
},
NetworkMode: "bridge",
}
resp, err := d.cli.ContainerCreate(ctx, &containerConfig, &hostConfig, nil, nil, SwarmContainerName)
if err != nil {
return "", err
}
log.Info().Msg("swarm created successfully")
return resp.ID, nil
}
func (d Deployer) initSwarm(ctx context.Context) error {
req := swarm.InitRequest{
ListenAddr: "0.0.0.0:2377",
AdvertiseAddr: "127.0.0.1",
}
if _, err := d.swarm.cli.SwarmInit(ctx, req); err != nil {
if strings.Contains(err.Error(), "part of a swarm") {
log.Info().Msg("swarm already initialized")
return nil
}
return err
}
return nil
}
func (d Deployer) StopSwarm(ctx context.Context) error {
if d.swarm.ID == "" {
log.Warn().Msg("no swarm registered, can't stop")
return nil
}
options := container.StopOptions{}
if err := d.cli.ContainerStop(ctx, d.swarm.ID, options); err != nil {
return err
}
log.Info().Msg("swarm stopped successfully")
return nil
}
// TODO(rmanach): get a child context instead
func (d *Deployer) init(ctx context.Context) error {
swarmID, err := d.getSwarm(ctx)
if err != nil {
if !errors.Is(err, ErrSwarmNotFound) {
return err
}
}
s := Swarm{swarmID, nil}
if swarmID == "" {
id, err := d.createSwarm(ctx)
if err != nil {
return err
}
s.ID = id
}
if err := d.cli.ContainerStart(ctx, s.ID, types.ContainerStartOptions{}); err != nil {
return err
}
cli, errInit := localcli.GetSwarmClient()
if errInit != nil {
if err := d.StopSwarm(ctx); err != nil {
log.Err(err).Msg("unable to stop the swarm")
}
return errInit
}
s.cli = cli
d.swarm = s
if err := d.initSwarm(ctx); err != nil {
return err
}
log.Info().Msg("deployer successfully initialized")
return nil
}
func (d *Deployer) Deploy(ctx context.Context) error {
if err := d.swarm.deployServices(ctx); err != nil {
return err
}
return nil
}
func (d *Deployer) GetSwarmCLI() (*client.Client, error) {
if d.swarm.cli == nil {
return nil, ErrSwarmClientInit
}
return d.swarm.cli, nil
}