package stacks import ( "context" _ "embed" "fmt" "os" "path/filepath" "github.com/pulumi/pulumi/sdk/v3/go/auto" "github.com/pulumi/pulumi/sdk/v3/go/auto/optdestroy" "github.com/pulumi/pulumi/sdk/v3/go/auto/optpreview" "github.com/pulumi/pulumi/sdk/v3/go/auto/optup" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" "gitlab.com/dananglin/helix/internal/config" "gitlab.com/dananglin/helix/internal/docker" ) // DockerStack is a stack for managing the containers // for the forge platform. type DockerStack struct { Name string Stack auto.Stack } //go:embed templates/traefik/Dockerfile.tmpl var templateTraefikDockerfile string //go:embed templates/traefik/traefik.yaml.tmpl var templateTraefikStaticConfig string //go:embed templates/traefik/dynamic_dashboard.yaml.tmpl var templateTraefikDynamicDashboardConfig string //go:embed templates/traefik/entrypoint.sh.tmpl var templateTraefikEntrypoint string // newContainerStack creates the ContainerStack value. func newDockerStack(ctx context.Context, project, stack string, c config.Config) (*DockerStack, error) { deployFunc := deployDockerStack(project, c.Docker, c.Services) s, err := createOrSelectStack(ctx, project, stack, deployFunc) if err != nil { return nil, fmt.Errorf("unable to initialise the '%s' stack...\n%w", stack, err) } d := DockerStack{ Name: stack, Stack: s, } return &d, nil } // Preview the proposed changes to the container stack. func (c *DockerStack) Preview(ctx context.Context) error { streamer := optpreview.ProgressStreams(os.Stdout) _, err := c.Stack.Preview(ctx, streamer) if err != nil { return fmt.Errorf("unable to preview the '%s' stack...\n%w", c.Name, err) } return nil } // Update the container stack. func (c *DockerStack) Update(ctx context.Context) error { streamer := optup.ProgressStreams(os.Stdout) _, err := c.Stack.Up(ctx, streamer) if err != nil { return fmt.Errorf("unable to update '%s' stack...\n%w", c.Name, err) } return nil } // Destroy the container stack. func (c *DockerStack) Destroy(ctx context.Context) error { streamer := optdestroy.ProgressStreams(os.Stdout) _, err := c.Stack.Destroy(ctx, streamer) if err != nil { return fmt.Errorf("unable to destroy '%s' stack...\n%w", c.Name, err) } return nil } // deployDockerStack returns a Pulumi run function // that is used to deploy the docker stack. func deployDockerStack(project string, dockerConfig config.DockerConfig, services config.ServicesConfig) pulumi.RunFunc { groupID := 2239 services.Traefik.GroupId = groupID return func(ctx *pulumi.Context) error { // TODO: Create the provider when we start playing with remote hosts // Create the docker network networkConfig := docker.DockerNetworkConfig{ Name: pulumi.String(dockerConfig.Network.Name), Subnet: pulumi.String(dockerConfig.Network.Subnet), Driver: pulumi.String(dockerConfig.Network.Driver), } network, err := docker.CreateNetwork(ctx, networkConfig) if err != nil { return err } // Create the shared volume sharedVolumeInput := docker.DockerVolumeInput{ Name: pulumi.String(dockerConfig.SharedVolume.Name), UniqueLabel: "shared", } sharedVolume, err := docker.CreateVolume(ctx, sharedVolumeInput) if err != nil { return err } base_cache, err := os.UserCacheDir() if err != nil { return fmt.Errorf("unable to get the base cache directory...\n%w", err) } // Create the Traefik service traefikContextDir := filepath.Join(base_cache, "helix", project, "traefik") if err := os.MkdirAll(traefikContextDir, 0700); err != nil { return fmt.Errorf("unable to make the cache directory for traefik...\n%w", err) } if err := generateFile(services.Traefik, templateTraefikDockerfile, "traefikDocker", filepath.Join(traefikContextDir, "Dockerfile")); err != nil { return fmt.Errorf("unable to generate the Traefik Dockerfile from template...\n%w", err) } if err := generateFile(services.Traefik, templateTraefikStaticConfig, "traefikStaticConf", filepath.Join(traefikContextDir, "traefik.yml")); err != nil { return fmt.Errorf("unable to generate the Traefik static configuration from template...\n%w", err) } if err := generateFile(services.Traefik, templateTraefikDynamicDashboardConfig, "traefikDashboardConf", filepath.Join(traefikContextDir, "dynamic_dashboard.yaml")); err != nil { return fmt.Errorf("unable to generate the Traefik dashboard configuration from template...\n%w", err) } if err := generateFile(services.Traefik, templateTraefikEntrypoint, "traefikEntrypoint", filepath.Join(traefikContextDir, "entrypoint.sh")); err != nil { return fmt.Errorf("unable to generate the Traefik entrypoint script from template...\n%w", err) } c := docker.DockerImageInput{ BuildContext: pulumi.String(traefikContextDir), Dockerfile: pulumi.String(filepath.Join(traefikContextDir, "Dockerfile")), ImageName: pulumi.String("helix-traefik"), ImageTag: pulumi.String(services.Traefik.Version), UniqueLabel: "traefik-image", } traefikImage, err := docker.CreateImage(ctx, c) if err != nil { return err } traefikContainerInput := docker.DockerContainerInput{ Image: traefikImage.ImageName, Ipv4Address: pulumi.String(services.Traefik.ContainerIp), Name: pulumi.String("helix-traefik"), Network: network.Name, UniqueLabel: "traefik-container", DockerVolumes: []docker.DockerVolume{ { Name: sharedVolume.Name, MountPath: pulumi.String("/helix/shared"), }, }, } if err = docker.CreateContainer(ctx, traefikContainerInput); err != nil { return err } return nil } }