Massive architectural rework

This commit massively overhauls the project's structure to simplify
development. Most parts are now correctly compartmentalized and
dependencies are passed in a sane way rather than global variables
galore xd.
This commit is contained in:
Zoe
2025-05-02 12:15:40 -05:00
parent f4bf2ff5a1
commit c891c24843
50 changed files with 2684 additions and 2410 deletions

View File

@@ -0,0 +1,172 @@
package docker
import (
"context"
"fmt"
"io"
"net/http"
"time"
dockerTypes "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/mount"
"github.com/docker/docker/pkg/namesgenerator"
"go.uber.org/zap"
)
type DockerContainer struct {
ID DockerID
Name string
Volumes []*DockerVolume
}
// Creates a container in the docker daemon and returns the descriptor for the container
func (d *DockerClient) CreateDockerContainer(ctx context.Context, imageName string, vols []*DockerVolume, environment []string, hosts []string, name *string) (*DockerContainer, error) {
for _, host := range hosts {
if host == ":" {
return nil, fmt.Errorf("invalid host %s", host)
}
}
if name == nil {
containerName := fmt.Sprintf("flux-%s", namesgenerator.GetRandomName(0))
name = &containerName
}
d.logger.Debugw("Creating container", zap.String("container_name", *name))
mounts := make([]mount.Mount, len(vols))
volumes := make(map[string]struct{}, len(vols))
for i, volume := range vols {
volumes[volume.VolumeID] = struct{}{}
mounts[i] = mount.Mount{
Type: mount.TypeVolume,
Source: volume.VolumeID,
Target: volume.Mountpoint,
ReadOnly: false,
}
}
resp, err := d.client.ContainerCreate(ctx, &container.Config{
Image: imageName,
Env: environment,
Volumes: volumes,
Labels: map[string]string{
"managed-by": "flux",
},
},
&container.HostConfig{
RestartPolicy: container.RestartPolicy{Name: container.RestartPolicyUnlessStopped},
NetworkMode: "bridge",
Mounts: mounts,
ExtraHosts: hosts,
},
nil,
nil,
*name,
)
if err != nil {
return nil, err
}
c := &DockerContainer{
ID: DockerID(resp.ID),
Name: *name,
Volumes: vols,
}
return c, nil
}
func (d *DockerClient) ContainerRemove(ctx context.Context, containerID DockerID, options container.RemoveOptions) error {
d.logger.Debugw("Removing container", zap.String("container_id", string(containerID)))
return d.client.ContainerRemove(ctx, string(containerID), options)
}
func (d *DockerClient) StartContainer(ctx context.Context, containerID DockerID) error {
return d.client.ContainerStart(ctx, string(containerID), container.StartOptions{})
}
// blocks until the container returns a 200 status code
func (d *DockerClient) ContainerWait(ctx context.Context, containerID DockerID, port uint16) error {
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
for {
select {
case <-ctx.Done():
return fmt.Errorf("container failed to become ready in time")
default:
containerJSON, err := d.ContainerInspect(ctx, containerID)
if err != nil {
return err
}
if containerJSON.State.Running {
resp, err := http.Get(fmt.Sprintf("http://%s:%d/", containerJSON.NetworkSettings.IPAddress, port))
if err == nil && resp.StatusCode == http.StatusOK {
return nil
}
}
time.Sleep(time.Second)
}
}
}
func (d *DockerClient) DeleteDockerContainer(ctx context.Context, containerID DockerID) error {
d.logger.Debugw("Removing container", zap.String("container_id", string(containerID)))
return d.client.ContainerRemove(ctx, string(containerID), container.RemoveOptions{})
}
func (d *DockerClient) GetContainerIp(containerID DockerID) (string, error) {
containerJSON, err := d.client.ContainerInspect(context.Background(), string(containerID))
if err != nil {
return "", err
}
ip := containerJSON.NetworkSettings.IPAddress
return ip, nil
}
type ContainerStatus struct {
// Can be "created", "running", "paused", "restarting", "removing", "exited", or "dead"
Status string
ExitCode int
}
func (d *DockerClient) GetContainerStatus(containerID DockerID) (*ContainerStatus, error) {
containerJSON, err := d.client.ContainerInspect(context.Background(), string(containerID))
if err != nil {
return nil, err
}
containerStatus := &ContainerStatus{
Status: containerJSON.State.Status,
ExitCode: containerJSON.State.ExitCode,
}
return containerStatus, nil
}
func (d *DockerClient) StopContainer(ctx context.Context, containerID DockerID) error {
d.logger.Debugw("Stopping container", zap.String("container_id", string(containerID[:12])))
return d.client.ContainerStop(ctx, string(containerID), container.StopOptions{})
}
func (d *DockerClient) ImagePull(ctx context.Context, imageName string, options image.PullOptions) (io.ReadCloser, error) {
d.logger.Debugw("Pulling image", zap.String("image", imageName))
return d.client.ImagePull(ctx, imageName, options)
}
func (d *DockerClient) ContainerInspect(ctx context.Context, containerID DockerID) (dockerTypes.ContainerJSON, error) {
d.logger.Debugw("Inspecting container", zap.String("container_id", string(containerID)))
return d.client.ContainerInspect(ctx, string(containerID))
}
func (d *DockerClient) ContainerStart(ctx context.Context, containerID string, options container.StartOptions) error {
d.logger.Debugw("Starting container", zap.String("container_id", containerID))
return d.client.ContainerStart(ctx, containerID, options)
}

29
internal/docker/docker.go Normal file
View File

@@ -0,0 +1,29 @@
package docker
import (
"github.com/docker/docker/client"
"go.uber.org/zap"
)
type DockerID string
// structure that holds the docker daemon information
type DockerClient struct {
client *client.Client
logger *zap.SugaredLogger
}
func NewDocker(rawDockerClient *client.Client, logger *zap.SugaredLogger) *DockerClient {
if rawDockerClient == nil {
var err error
rawDockerClient, err = client.NewClientWithOpts(client.FromEnv)
if err != nil {
logger.Fatalw("Failed to create docker client", zap.Error(err))
}
}
return &DockerClient{
client: rawDockerClient,
logger: logger,
}
}

37
internal/docker/volume.go Normal file
View File

@@ -0,0 +1,37 @@
package docker
import (
"context"
"fmt"
"github.com/docker/docker/api/types/volume"
"go.uber.org/zap"
)
type DockerVolume struct {
VolumeID string
Mountpoint string
}
func (d *DockerClient) CreateDockerVolume(ctx context.Context) (vol *DockerVolume, err error) {
dockerVolume, err := d.client.VolumeCreate(ctx, volume.CreateOptions{
Driver: "local",
DriverOpts: map[string]string{},
})
if err != nil {
return nil, fmt.Errorf("failed to create volume: %v", err)
}
d.logger.Debugw("Volume created", zap.String("volume_id", dockerVolume.Name), zap.String("mountpoint", dockerVolume.Mountpoint))
vol = &DockerVolume{
VolumeID: dockerVolume.Name,
}
return vol, nil
}
func (d *DockerClient) DeleteDockerVolume(ctx context.Context, volumeID string) error {
d.logger.Debugw("Removing volume", zap.String("volume_id", volumeID))
return d.client.VolumeRemove(ctx, volumeID, true)
}