CLI to get binaries from a docker image with multi arch support

have a look at the README.md to see how it works.

Bootstrap a CLI program to copy binaries from multi arch images

Signed-off-by: Gianluca Arbezzano <gianarb92@gmail.com>
This commit is contained in:
Gianluca Arbezzano
2020-11-16 17:42:54 +01:00
parent 4790ba4871
commit 45f593449a
8 changed files with 487 additions and 447 deletions

View File

@ -0,0 +1,76 @@
package docker
import (
"context"
"github.com/containers/image/v5/copy"
"github.com/containers/image/v5/manifest"
"github.com/containers/image/v5/signature"
"github.com/containers/image/v5/transports/alltransports"
"github.com/containers/image/v5/types"
)
type Image struct {
src types.ImageSource
ref types.ImageReference
sys *types.SystemContext
}
func ImageFromName(ctx context.Context, sys *types.SystemContext, name string) (*Image, error) {
imageRef, err := alltransports.ParseImageName(name)
if err != nil {
return nil, err
}
src, err := imageRef.NewImageSource(ctx, sys)
if err != nil {
return nil, err
}
return &Image{
src: src,
ref: imageRef,
sys: sys,
}, nil
}
func (img *Image) GetManifest(ctx context.Context) ([]byte, string, error) {
rawManifest, _, err := img.src.GetManifest(ctx, nil)
if err != nil {
return nil, "", err
}
return rawManifest, manifest.GuessMIMEType(rawManifest), nil
}
func (img *Image) Copy(ctx context.Context, dst string) error {
destRef, err := alltransports.ParseImageName(dst)
if err != nil {
return err
}
pc, err := signature.NewPolicyContext(&signature.Policy{
Default: []signature.PolicyRequirement{
signature.NewPRInsecureAcceptAnything(),
},
})
if err != nil {
return err
}
_, err = copy.Image(ctx, pc, destRef, img.ref, &copy.Options{SourceCtx: img.sys})
if err != nil {
return err
}
return nil
}
type SchemaV2List struct {
MediaType string `json:"mediaType"`
SchemaVersion int `json:"schemaVersion"`
Manifests []struct {
MediaType string `json:"mediaType"`
Digest string `json:"digest"`
Size int `json:"size"`
Platform struct {
Architecture string `json:"architecture"`
Os string `json:"os"`
Variant string `json:"variant"`
} `json:"platform,omitempty"`
} `json:"manifests"`
}

View File

@ -0,0 +1,137 @@
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"path"
"github.com/containers/image/manifest"
"github.com/containers/image/v5/types"
"github.com/tinkerbell/sandbox/cmd/getbinariesfromquay/docker"
"github.com/tinkerbell/sandbox/cmd/getbinariesfromquay/tar"
)
type Config struct {
ProgramName string
OutDirectory string
Image string
Binary string
}
var config = Config{}
func init() {
flag.StringVar(&config.ProgramName, "program", "hegel", "The name of the program you are extracing binaries for. (eg tink-worker, hegel, tink-server, tink, boots)")
flag.StringVar(&config.OutDirectory, "out", "./out", "The directory that will be used to store the release binaries")
flag.StringVar(&config.Image, "image", "docker://quay.io/tinkerbell/hegel", "The image you want to download binaries from. It has to be a multi stage image.")
flag.StringVar(&config.Binary, "binary-to-copy", "/usr/bin/hegel", "The location of the binary you want to copy from inside the image.")
}
func main() {
flag.Parse()
println("Extracing binary: " + config.Binary)
println("From Image: " + config.Image)
imageR := config.Image
binaryToCopy := config.Binary
baseDir, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
programName := config.ProgramName
outDir := path.Join(baseDir, config.OutDirectory)
releaseDir := path.Join(outDir, "release")
err = os.MkdirAll(releaseDir, 0755)
if err != nil {
log.Fatal(err)
}
println("Binaries will be located in: " + releaseDir)
imgsDir := path.Join(outDir, "imgs")
err = os.MkdirAll(imgsDir, 0755)
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
img, err := docker.ImageFromName(ctx, &types.SystemContext{}, imageR)
if err != nil {
log.Fatal(err)
}
rawManifest, mt, err := img.GetManifest(ctx)
if err != nil {
log.Fatal(err)
}
if mt != manifest.DockerV2ListMediaType {
log.Fatal("manifest not supported, it is not a multi arch image")
}
archList := docker.SchemaV2List{}
err = json.Unmarshal(rawManifest, &archList)
if err != nil {
log.Fatal(err)
}
for _, arch := range archList.Manifests {
imgDir := fmt.Sprintf("%s-%s-%s", programName, arch.Platform.Os, arch.Platform.Architecture)
println("Extracting ", imgDir)
syss := &types.SystemContext{
ArchitectureChoice: arch.Platform.Architecture,
OSChoice: arch.Platform.Os,
}
if arch.Platform.Variant != "" {
syss.VariantChoice = arch.Platform.Variant
imgDir = imgDir + "-" + arch.Platform.Variant
}
archImg, err := docker.ImageFromName(ctx, syss, imageR)
if err != nil {
log.Fatal(err)
}
err = archImg.Copy(ctx, fmt.Sprintf("dir:%s", path.Join(imgsDir, imgDir)))
if err != nil {
log.Fatal(err)
}
err = copyBinaryFromLastLayer(path.Join(imgsDir, imgDir), path.Join(releaseDir, imgDir), binaryToCopy)
if err != nil {
log.Fatal(err)
}
}
}
func copyBinaryFromLastLayer(src, dest, binaryPath string) error {
b, err := ioutil.ReadFile(path.Join(src, "manifest.json"))
if err != nil {
return err
}
man, err := manifest.FromBlob(b, manifest.DockerV2Schema2MediaType)
if err != nil {
return err
}
last := man.LayerInfos()[len(man.LayerInfos())-1]
layerTar, err := os.Open(path.Join(src, last.Digest.String()[7:]))
if err != nil {
return err
}
err = tar.Untar(src, layerTar)
if err != nil {
return err
}
input, err := ioutil.ReadFile(path.Join(src, binaryPath))
if err != nil {
return err
}
err = ioutil.WriteFile(dest, input, 0755)
if err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,48 @@
package tar
import (
"archive/tar"
"compress/gzip"
"io"
"os"
"path/filepath"
)
func Untar(dst string, r io.Reader) error {
gzr, err := gzip.NewReader(r)
if err != nil {
return err
}
defer gzr.Close()
tr := tar.NewReader(gzr)
for {
header, err := tr.Next()
switch {
case err == io.EOF:
return nil
case err != nil:
return err
case header == nil:
continue
}
target := filepath.Join(dst, header.Name)
switch header.Typeflag {
case tar.TypeDir:
if _, err := os.Stat(target); err != nil {
if err := os.MkdirAll(target, 0755); err != nil {
return err
}
}
case tar.TypeReg:
f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
if err != nil {
return err
}
if _, err := io.Copy(f, tr); err != nil {
return err
}
f.Close()
}
}
}