2022-04-25 12:23:57 -05:00

144 lines
3.0 KiB
Go

package epinject
import (
"archive/tar"
"bytes"
"context"
"crypto/rand"
"fmt"
"io"
"os"
"path/filepath"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/client"
"github.com/hashicorp/go-hclog"
"github.com/oklog/ulid"
)
func dockerClient(ctx context.Context) (*client.Client, error) {
cli, err := client.NewClientWithOpts(client.FromEnv)
if err != nil {
return nil, err
}
cli.NegotiateAPIVersion(ctx)
return cli, nil
}
// To remove the entrypoint and reset it back to the docker default, return
// this value for Entrypoint. The docker commit API recognizes this value specially
// to reset the entrypoint.
var DockerDefaultEntrypoint = []string{""}
type NewEntrypoint struct {
NewImage string
Entrypoint []string
InjectFiles map[string]InjectFile
}
type InjectFile struct {
Reader io.Reader
Info os.FileInfo
}
func AlterEntrypoint(ctx context.Context, image string, f func(cur []string) (*NewEntrypoint, error)) (string, error) {
dc, err := dockerClient(ctx)
if err != nil {
return "", err
}
L := hclog.FromContext(ctx)
L.Debug("altering entrypoint of docker image", "image", image)
info, _, err := dc.ImageInspectWithRaw(ctx, image)
if err != nil {
return "", err
}
icfg := info.Config
L.Debug("extracted existing entrypoint", "image", image, "entrypoint", icfg.Entrypoint)
newEp, err := f(icfg.Entrypoint)
if err != nil {
return "", err
}
if newEp.Entrypoint != nil {
icfg.Entrypoint = newEp.Entrypoint
}
if newEp.NewImage == "" {
newEp.NewImage = image
}
u, err := ulid.New(ulid.Now(), rand.Reader)
if err != nil {
return "", err
}
name := "epinject-" + u.String()
var (
cfg = container.Config{Image: image}
hostCfg = container.HostConfig{}
networkCfg = network.NetworkingConfig{}
)
body, err := dc.ContainerCreate(ctx, &cfg, &hostCfg, &networkCfg, name)
if err != nil {
return "", err
}
// Force a cleanup of our temporary container
defer dc.ContainerRemove(ctx, body.ID, types.ContainerRemoveOptions{Force: true})
var buf bytes.Buffer
for container, f := range newEp.InjectFiles {
buf.Reset()
tw := tar.NewWriter(&buf)
hdr, err := tar.FileInfoHeader(f.Info, "")
if err != nil {
return "", err
}
hdr.Name = filepath.Base(container)
tw.WriteHeader(hdr)
io.Copy(tw, f.Reader)
err = dc.CopyToContainer(ctx, body.ID, filepath.Dir(container), &buf, types.CopyToContainerOptions{})
if err != nil {
return "", err
}
L.Debug("injected file into new image", "container", container)
}
if newEp.NewImage == image {
L.Debug("overwriting existing image with new image", "image", image)
} else {
L.Debug("creating new image", "image", newEp.NewImage)
}
idr, err := dc.ContainerCommit(ctx, body.ID, types.ContainerCommitOptions{
Reference: newEp.NewImage,
Comment: fmt.Sprintf("Alter image '%s' to modify entrypoint", image),
Config: icfg,
})
if err != nil {
return "", err
}
return idr.ID, nil
}