-
Notifications
You must be signed in to change notification settings - Fork 1
feat: Add PostgreSQL 18 driver + driver refactor #33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Unbreathable
wants to merge
9
commits into
main
Choose a base branch
from
feat/postgres-18
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
5f00260
feat: Add PostgreSQL 18 driver + upgrade real project example
Unbreathable a4c4edc
fix: Name the default database properly
Unbreathable 255b080
fix: Make sure error messages are properly done
Unbreathable a079c4a
fix: Make sure database is named properly
Unbreathable 5b1eee1
fix: typo
Unbreathable 7e1ccf9
chore: Make sure the path in the go.mod is correct
Unbreathable f10f886
fix: Make sure tables are properly truncated / dropped
Unbreathable 1d6abc8
fix: Only use postgres connection in drivers
Unbreathable 269227a
fix: typo
Unbreathable File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,5 +2,6 @@ go 1.25.7 | |
|
|
||
| use ( | ||
| . | ||
| ./pkg/databases/postgres | ||
| ./pkg/databases/postgres-legacy | ||
| ) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,191 @@ | ||
| package mservices | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "log" | ||
| "net/netip" | ||
| "strings" | ||
|
|
||
| "github.com/Liphium/magic/v3/mconfig" | ||
| "github.com/moby/moby/api/types/container" | ||
| "github.com/moby/moby/api/types/mount" | ||
| "github.com/moby/moby/api/types/network" | ||
| "github.com/moby/moby/client" | ||
| ) | ||
|
|
||
| // ContainerVolume describes a single named volume that should be mounted into | ||
| // the container. The volume name is derived from the container name so it | ||
| // survives container re-creation. | ||
| type ContainerVolume struct { | ||
| // Suffix appended to the container name to form the Docker volume name, | ||
| // e.g. "data" -> "<containerName>-data". | ||
| NameSuffix string | ||
| // Absolute path inside the container where the volume is mounted. | ||
| Target string | ||
| } | ||
|
|
||
| // ManagedContainerOptions holds everything needed to create (or re-create) a | ||
| // managed Docker container in a reproducible way. | ||
| type ManagedContainerOptions struct { | ||
| // Docker image to use, e.g. "postgres:17". | ||
| Image string | ||
| // Environment variables passed into the container. | ||
| Env []string | ||
| // Ports to expose. Each entry maps one container port (inside of the container) to one host port (chosen by Magic). | ||
| Ports []string | ||
| // Named volumes to attach. Existing mounts are reused across re-creations. | ||
| Volumes []ContainerVolume | ||
| } | ||
|
|
||
| // CreateContainer finds and removes any existing container with the | ||
| // given name, then creates a fresh one from the provided options. | ||
| // | ||
| // Existing Docker volumes are always preserved so that data survives a | ||
| // container re-creation. Returns the ID of the newly created container. | ||
| func CreateContainer(ctx context.Context, log *log.Logger, c *client.Client, a mconfig.ContainerAllocation, opts ManagedContainerOptions) (string, error) { | ||
| if opts.Image == "" { | ||
| return "", fmt.Errorf("please specify a proper image") | ||
| } | ||
|
|
||
| existingMounts, err := removeExistingContainer(ctx, log, c, a, opts) | ||
| if err != nil { | ||
| return "", err | ||
| } | ||
|
|
||
| mounts, err := buildMounts(a, opts.Volumes, existingMounts) | ||
| if err != nil { | ||
| return "", err | ||
| } | ||
|
|
||
| exposedPorts, portBindings, err := buildPortBindings(a, opts.Ports) | ||
| if err != nil { | ||
| return "", err | ||
| } | ||
|
|
||
| resp, err := c.ContainerCreate(ctx, client.ContainerCreateOptions{ | ||
| Config: &container.Config{ | ||
| Image: opts.Image, | ||
| Env: opts.Env, | ||
| ExposedPorts: exposedPorts, | ||
| }, | ||
| HostConfig: &container.HostConfig{ | ||
| PortBindings: portBindings, | ||
| Mounts: mounts, | ||
| }, | ||
| Name: a.Name, | ||
| }) | ||
| if err != nil { | ||
| return "", fmt.Errorf("couldn't create container %q: %s", a.Name, err) | ||
| } | ||
|
|
||
| return resp.ID, nil | ||
| } | ||
|
|
||
| // removeExistingContainer looks for an existing container with the allocation's | ||
| // name, recovers its mounts, and removes it. Returns a map of volume NameSuffix | ||
| // -> mount so the new container can reuse the same volumes. | ||
| func removeExistingContainer(ctx context.Context, log *log.Logger, c *client.Client, a mconfig.ContainerAllocation, opts ManagedContainerOptions) (map[string]mount.Mount, error) { | ||
| f := make(client.Filters) | ||
| f.Add("name", a.Name) | ||
| summary, err := c.ContainerList(ctx, client.ContainerListOptions{ | ||
| Filters: f, | ||
| All: true, | ||
| }) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("couldn't list containers: %s", err) | ||
| } | ||
|
|
||
| existingMounts := map[string]mount.Mount{} | ||
|
|
||
| for _, ct := range summary.Items { | ||
| for _, n := range ct.Names { | ||
| if !strings.HasSuffix(n, a.Name) { | ||
| continue | ||
| } | ||
|
|
||
| log.Println("Found existing container, recovering mounts...") | ||
| if err := recoverMounts(ctx, c, ct.ID, opts.Volumes, existingMounts); err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| log.Println("Removing old container...") | ||
| if _, err := c.ContainerRemove(ctx, ct.ID, client.ContainerRemoveOptions{ | ||
| RemoveVolumes: false, | ||
| Force: true, | ||
| }); err != nil { | ||
| return nil, fmt.Errorf("couldn't remove existing container: %s", err) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return existingMounts, nil | ||
| } | ||
|
|
||
| // recoverMounts inspects a container and indexes its mounts by the matching | ||
| // ContainerVolume.NameSuffix into the provided map. | ||
| func recoverMounts(ctx context.Context, c *client.Client, containerID string, volumes []ContainerVolume, out map[string]mount.Mount) error { | ||
| resp, err := c.ContainerInspect(ctx, containerID, client.ContainerInspectOptions{}) | ||
| if err != nil { | ||
| return fmt.Errorf("couldn't inspect container: %s", err) | ||
| } | ||
|
|
||
| for _, m := range resp.Container.HostConfig.Mounts { | ||
| for _, vol := range volumes { | ||
| if m.Target == vol.Target { | ||
| out[vol.NameSuffix] = m | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // buildMounts constructs the mount list for the new container. Any volume whose | ||
| // target was found in existingMounts is reused as-is; otherwise a fresh named | ||
| // volume is created using "<containerName>-<nameSuffix>". | ||
| func buildMounts(a mconfig.ContainerAllocation, volumes []ContainerVolume, existingMounts map[string]mount.Mount) ([]mount.Mount, error) { | ||
| mounts := make([]mount.Mount, 0, len(volumes)) | ||
|
|
||
| for _, vol := range volumes { | ||
| if existing, ok := existingMounts[vol.NameSuffix]; ok { | ||
| mounts = append(mounts, existing) | ||
| } else { | ||
| mounts = append(mounts, mount.Mount{ | ||
| Type: mount.TypeVolume, | ||
| Source: fmt.Sprintf("%s-%s", a.Name, vol.NameSuffix), | ||
| Target: vol.Target, | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| return mounts, nil | ||
| } | ||
|
|
||
| // buildPortBindings converts the ports to what Docker actually needs. | ||
| func buildPortBindings(a mconfig.ContainerAllocation, ports []string) (network.PortSet, network.PortMap, error) { | ||
| exposedPorts := network.PortSet{} | ||
| portBindings := network.PortMap{} | ||
|
|
||
| // Make sure the amount of ports is correct | ||
| if len(a.Ports) != len(ports) { | ||
| return nil, nil, fmt.Errorf("expected %d ports, received only %d", len(ports), len(a.Ports)) | ||
| } | ||
|
|
||
| for i, port := range ports { | ||
| p, err := network.ParsePort(port) | ||
| if err != nil { | ||
| return nil, nil, fmt.Errorf("couldn't parse container port %q: %s", port, err) | ||
| } | ||
|
|
||
| exposedPorts[p] = struct{}{} | ||
| portBindings[p] = []network.PortBinding{ | ||
| { | ||
| HostIP: netip.MustParseAddr("127.0.0.1"), | ||
| HostPort: fmt.Sprintf("%d", a.Ports[i]), | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| return exposedPorts, portBindings, nil | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| package mservices | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
|
|
||
| "github.com/moby/moby/client" | ||
| ) | ||
|
|
||
| // Simply execute a command inside of a container. | ||
| func ExecuteCommand(ctx context.Context, c *client.Client, id string, cmd []string) (client.ExecInspectResult, error) { | ||
| execIDResp, err := c.ExecCreate(ctx, id, client.ExecCreateOptions{ | ||
| Cmd: cmd, | ||
| AttachStdout: true, | ||
| AttachStderr: true, | ||
| }) | ||
| if err != nil { | ||
| return client.ExecInspectResult{}, fmt.Errorf("couldn't create exec: %s", err) | ||
| } | ||
| execStartCheck := client.ExecStartOptions{Detach: false, TTY: false} | ||
| if _, err := c.ExecStart(ctx, execIDResp.ID, execStartCheck); err != nil { | ||
| return client.ExecInspectResult{}, fmt.Errorf("couldn't start exec: %s", err) | ||
| } | ||
| return c.ExecInspect(ctx, execIDResp.ID, client.ExecInspectOptions{}) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.