package services import ( "context" "errors" "fmt" "time" "localenv/utils" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/client" ) const NetworkName = "localenv" var ( RetryServiceAttempts uint64 = 0 SwarmServiceReplicas uint64 = 1 RetryServiceDelay = 20 * time.Second ErrNoRunningContainer = errors.New("no running container") ) type ServiceOption func(spec *swarm.ServiceSpec) func WithIONetwork() ServiceOption { return WithNetwork(NetworkName) } func WithNetwork(name string) ServiceOption { return func(spec *swarm.ServiceSpec) { spec.Networks = append(spec.Networks, swarm.NetworkAttachmentConfig{ Target: name, Aliases: []string{name}, }, ) } } func WithHostEndpoint(port uint32) ServiceOption { return func(spec *swarm.ServiceSpec) { spec.EndpointSpec = &swarm.EndpointSpec{ Mode: "vip", Ports: []swarm.PortConfig{ { Protocol: "tcp", TargetPort: port, PublishedPort: port, PublishMode: "host", }, }, } } } func WithRestartPolicy() ServiceOption { return func(spec *swarm.ServiceSpec) { spec.TaskTemplate.RestartPolicy = &swarm.RestartPolicy{ Condition: swarm.RestartPolicyConditionOnFailure, MaxAttempts: &RetryServiceAttempts, Delay: &RetryServiceDelay, } } } func WithPostgres(name string) ServiceOption { return func(spec *swarm.ServiceSpec) { if spec.TaskTemplate.ContainerSpec == nil { return } spec.TaskTemplate.ContainerSpec.Env = append( spec.TaskTemplate.ContainerSpec.Env, "DB_TYPE=postgres", fmt.Sprintf("POSTGRES_HOSTNAME=pg-%s", name), fmt.Sprintf("POSTGRES_PORT=%d", PostgresServicePort), fmt.Sprintf("POSTGRES_DB=%s", name), "POSTGRES_USER=test", "POSTGRES_PASSWORD=test", ) } } func WithRabbitMQ() ServiceOption { return func(spec *swarm.ServiceSpec) { if spec.TaskTemplate.ContainerSpec == nil { return } spec.TaskTemplate.ContainerSpec.Env = append( spec.TaskTemplate.ContainerSpec.Env, fmt.Sprintf("RABBITMQ_ENDPOINT=amqp://%s:%d", RabbitMQServiceName, RabbitMQServicePort), "RABBITMQ_USERNAME=intercloud", "RABBITMQ_PASSWORD=intercloud", ) } } type Servicer interface { Deploy(ctx context.Context, cli *client.Client) error } type Service struct { spec swarm.ServiceSpec name string } func (p *Service) GetBaseServiceSpec(serviceName, hostname, imageName, port string, command []string) swarm.ServiceSpec { spec := swarm.ServiceSpec{ Annotations: swarm.Annotations{ Name: serviceName, }, TaskTemplate: swarm.TaskSpec{ ContainerSpec: &swarm.ContainerSpec{ Hostname: hostname, Image: imageName, Env: []string{ fmt.Sprintf("PORT=%s", port), }, Command: command, }, }, Mode: swarm.ServiceMode{ Replicated: &swarm.ReplicatedService{ Replicas: &SwarmServiceReplicas, }, }, } return spec } func Deploy(ctx context.Context, cli *client.Client, spec *swarm.ServiceSpec, dependencies []Servicer) error { err := utils.CheckServiceHealth(ctx, cli, spec.Annotations.Name) if err == nil { return nil } if !errors.Is(err, utils.ErrServiceNotFound) { return err } for _, deps := range dependencies { if err := deps.Deploy(ctx, cli); err != nil { return err } } if err := utils.CreateService(ctx, cli, spec); err != nil { return err } return nil }