Files
ipsw/internal/commands/docker/docker.go

139 lines
3.4 KiB
Go

package docker
import (
"context"
"fmt"
"io"
"os"
"slices"
"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/api/types/network"
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/stdcopy"
"github.com/google/uuid"
specs "github.com/opencontainers/image-spec/specs-go/v1"
)
type Client struct {
ID string `yaml:"id,omitempty" json:"id,omitempty"`
Image string `yaml:"image,omitempty" json:"image,omitempty"`
Entrypoint []string `yaml:"entrypoint,omitempty" json:"entrypoint,omitempty"`
Cmd []string `yaml:"cmd,omitempty" json:"cmd,omitempty"`
Env []string `yaml:"env,omitempty" json:"env,omitempty"`
Mounts []HostMounts `yaml:"mounts,omitempty" json:"mounts,omitempty"`
}
type HostMounts struct {
Source string
Target string
ReadOnly bool
}
func NewClient(id, image string, entry []string, cmd []string, env []string, mounts []HostMounts) *Client {
return &Client{
ID: id,
Image: image,
Entrypoint: entry,
Cmd: cmd,
Env: env,
Mounts: mounts,
}
}
func (c *Client) Run(ctx context.Context) error {
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
return fmt.Errorf("failed to create docker client: %w", err)
}
images, err := cli.ImageList(ctx, image.ListOptions{})
if err != nil {
return fmt.Errorf("failed to list images: %w", err)
}
found := false
for _, image := range images {
if slices.Contains(image.RepoTags, c.Image) {
found = true
break
}
}
if !found {
reader, err := cli.ImagePull(ctx, c.Image, image.PullOptions{
// Platform: "linux/amd64",
})
if err != nil {
return err
}
defer reader.Close()
io.Copy(os.Stdout, reader)
}
var mounts []mount.Mount
for _, m := range c.Mounts {
mounts = append(mounts, mount.Mount{
Type: mount.TypeBind,
Source: m.Source,
Target: m.Target,
ReadOnly: m.ReadOnly,
BindOptions: &mount.BindOptions{
CreateMountpoint: true,
},
})
}
resp, err := cli.ContainerCreate(ctx,
&container.Config{
Image: c.Image,
Entrypoint: c.Entrypoint,
Cmd: c.Cmd,
Env: c.Env,
Tty: true,
AttachStdout: true,
AttachStderr: true,
NetworkDisabled: true,
}, &container.HostConfig{
AutoRemove: true,
// Resources: container.Resources{Memory: 1 << 30, MemorySwap: 1 << 30},
Mounts: mounts,
}, &network.NetworkingConfig{}, &specs.Platform{
OS: "linux",
Architecture: "amd64",
}, "ipsw-idapro-"+uuid.New().String())
if err != nil {
return fmt.Errorf("failed to create container: %w", err)
}
if err := cli.ContainerStart(ctx, resp.ID, container.StartOptions{}); err != nil {
return fmt.Errorf("failed to start container: %w", err)
}
go func() {
out, err := cli.ContainerLogs(ctx, resp.ID, container.LogsOptions{
ShowStdout: true,
ShowStderr: true,
Follow: true,
Timestamps: false,
})
if err != nil {
panic(fmt.Errorf("failed to get container logs: %w", err))
}
stdcopy.StdCopy(os.Stdout, os.Stderr, out)
}()
statusCh, errCh := cli.ContainerWait(ctx, resp.ID, container.WaitConditionNotRunning)
select {
case err := <-errCh:
if err != nil {
return fmt.Errorf("error waiting for container: %w", err)
}
case <-statusCh:
}
return nil
}