Files
tiny-forge/internal/docker/image.go
alexei.dolgolyov 652229c67f chore: fix build dependencies and frontend config
Migrate Docker SDK from github.com/docker/docker (+incompatible)
to github.com/moby/moby/client v0.3.0 + moby/moby/api v1.54.0
(proper Go modules). Adapt all container/image/network operations
to the new moby API (Filters, ContainerListOptions, PullResponse,
InspectResult, etc.). Add AsyncTriggerDeploy runDeploy method.

Fix SvelteKit build: disable prerender, set strict=false for SPA,
bump vite-plugin-svelte to v5 for vite 6 compat.

Add .dockerignore to exclude .git, node_modules, plans.
2026-03-28 13:13:45 +03:00

105 lines
3.0 KiB
Go

package docker
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"strings"
"github.com/moby/moby/api/types/registry"
"github.com/moby/moby/client"
)
// ImageInfo holds metadata extracted from a Docker image inspection.
type ImageInfo struct {
// ExposedPorts lists the ports declared via EXPOSE in the Dockerfile (e.g. ["8080/tcp"]).
ExposedPorts []string
// Healthcheck is the CMD string from the image's HEALTHCHECK instruction, if any.
Healthcheck string
// Labels are the key-value pairs defined in the image metadata.
Labels map[string]string
}
// PullImage pulls an image from a registry. If authConfig is non-empty, it is
// used as the base64-encoded JSON auth payload for private registries.
// The image reference should be in the form "repository:tag".
func (c *Client) PullImage(ctx context.Context, imageRef string, tag string, authConfig string) error {
ref := imageRef
if tag != "" {
ref = imageRef + ":" + tag
}
opts := client.ImagePullOptions{}
if authConfig != "" {
opts.RegistryAuth = authConfig
}
reader, err := c.api.ImagePull(ctx, ref, opts)
if err != nil {
return fmt.Errorf("pull image %s: %w", ref, err)
}
// Wait for the pull to complete.
if err := reader.Wait(ctx); err != nil {
return fmt.Errorf("wait for pull of %s: %w", ref, err)
}
return nil
}
// InspectImage retrieves metadata from a local image.
func (c *Client) InspectImage(ctx context.Context, imageRef string) (ImageInfo, error) {
inspectResult, err := c.api.ImageInspect(ctx, imageRef)
if err != nil {
return ImageInfo{}, fmt.Errorf("inspect image %s: %w", imageRef, err)
}
info := ImageInfo{}
// Extract labels from Config if available.
if inspectResult.Config != nil {
info.Labels = inspectResult.Config.Labels
// Extract exposed ports from OCI config (map[string]struct{}).
for port := range inspectResult.Config.ExposedPorts {
info.ExposedPorts = append(info.ExposedPorts, port)
}
// Extract healthcheck command.
if inspectResult.Config.Healthcheck != nil && len(inspectResult.Config.Healthcheck.Test) > 0 {
// The Test slice is ["CMD", "arg1", "arg2", ...] or ["CMD-SHELL", "cmd string"].
// Join all parts after the first element for a readable representation.
if len(inspectResult.Config.Healthcheck.Test) > 1 {
info.Healthcheck = joinArgs(inspectResult.Config.Healthcheck.Test[1:])
}
}
}
return info, nil
}
// EncodeRegistryAuth builds a base64-encoded JSON auth string suitable for
// Docker API calls. Pass empty strings for anonymous access.
func EncodeRegistryAuth(username, password, serverAddress string) (string, error) {
cfg := registry.AuthConfig{
Username: username,
Password: password,
ServerAddress: serverAddress,
}
data, err := json.Marshal(cfg)
if err != nil {
return "", fmt.Errorf("encode registry auth: %w", err)
}
return base64.URLEncoding.EncodeToString(data), nil
}
// joinArgs joins string arguments with spaces.
func joinArgs(args []string) string {
return strings.Join(args, " ")
}