Skip to content

Instantly share code, notes, and snippets.

@thebsdbox
Created February 10, 2021 12:43
Show Gist options
  • Save thebsdbox/706913e794b32819b0286a0766f6ff9c to your computer and use it in GitHub Desktop.
Save thebsdbox/706913e794b32819b0286a0766f6ff9c to your computer and use it in GitHub Desktop.
🤮
package main
import (
"compress/gzip"
"crypto/tls"
"crypto/x509"
"encoding/json"
"io"
"io/ioutil"
"math"
"net"
"net/http"
"os"
"strings"
"time"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/google/go-containerregistry/pkg/v1/remote/transport"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"archive/tar"
)
// Below can be codified
/*
umoci init --layout newimage
umoci new --image newimage:new-tag
umoci unpack --rootless --image newimage:new-tag newbundle
cp <FILE> newbundle/rootfs/
umoci repack --image newimage:new-tag newbundle
tar -cvzf oci.tar.gz ./newimage/
docker import ./oci.tar.gz test:latest
*/
// MaxBackoff is the maximum backoff time per retry in seconds
var MaxBackoff float64 = 30
// DefaultRetries is the default number of retries
var DefaultRetries int = 3
// Spec for rootfs extraction
type Spec struct {
// Destination to extract to
Dest string
// User to chown files in rootfs to
User string
// Use the subuid associated with the given user for chowning
UseSubuid bool
subuid int
subgid int
}
// PullableImage contains metadata necessary for pulling images
type PullableImage struct {
// Name of image to pull
Name string
// Path to registry cert
Cert *string
// Number of attempts to retry pulling
Retries int
// Metadata for rootfs extraction
Spec Spec
https bool
}
//PulledImage dpes
type PulledImage struct {
// User specified requirements for rootfs
spec Spec
name string
img v1.Image
}
// Pull a v1.Image and initialize a PulledImage struct to include the v1.img
// and metadata for extracting to a rootfs
func (pullable *PullableImage) Pull() (*PulledImage, error) {
var err error
var img v1.Image
for i := 0; i < pullable.Retries; i++ {
img, err = pullable.pull()
if err == nil {
break
}
if strings.Contains(err.Error(), "http: server gave HTTP response to HTTPS client") {
log.Info("Retrying with HTTP")
pullable.https = false
}
// This is a v1 schema, give up early
if strings.Contains(err.Error(), "unsupported MediaType") {
err = errors.WithMessage(err, "Image is v1 schema and too old to support")
break
}
// Either we are unauthorized, or this is a bad registry/image name
if strings.Contains(err.Error(), "UNAUTHORIZED: authentication required") {
break
}
// If we get a i/o timeout, it's either intermittent network failure
// or an incorrect ip address etc. This means we've already failed 5
// retries internal to go-containerregistry, so fail
if strings.Contains(err.Error(), "i/o timeout") {
log.Warnf("Connection to server timed out %s", err)
break
}
switch err := errors.Cause(err).(type) {
case *transport.Error:
break
default:
log.Warnf("Unrecognized error: %s Trying again", err)
}
backoff := math.Pow(2, float64(i))
backoff = math.Min(backoff, MaxBackoff)
time.Sleep(time.Second * time.Duration(backoff))
}
// Failed to pull, return an error
if err != nil {
return nil, err
}
// Initialize the image
pulled := &PulledImage{
img: img,
name: pullable.Name,
spec: pullable.Spec,
}
return pulled, nil
}
// pull a v1.image
func (pullable *PullableImage) pull() (v1.Image, error) {
log.Debugf("Getting manifest for %s", pullable.Name)
ref, err := name.ParseReference(pullable.Name, name.WeakValidation)
if err != nil {
return nil, errors.WithStack(err)
}
registryName := ref.Context().RegistryStr()
var newReg name.Registry
if pullable.https {
newReg, err = name.NewRegistry(registryName, name.WeakValidation)
} else {
newReg, err = name.NewRegistry(registryName, name.Insecure)
}
if err != nil {
return nil, errors.WithStack(err)
}
if tag, ok := ref.(name.Tag); ok {
tag.Repository.Registry = newReg
ref = tag
}
if digest, ok := ref.(name.Digest); ok {
digest.Repository.Registry = newReg
ref = digest
}
transport := http.DefaultTransport.(*http.Transport)
transport.DialContext = (&net.Dialer{
Timeout: 10 * time.Second,
KeepAlive: 10 * time.Second,
DualStack: true,
}).DialContext
// A cert was provided
if pullable.Cert != nil {
rootCAs, err := x509.SystemCertPool()
if err != nil {
return nil, err
}
if rootCAs == nil {
rootCAs = x509.NewCertPool()
}
// Read in the cert file
certs, err := ioutil.ReadFile(*pullable.Cert)
if err != nil {
return nil, errors.Wrapf(err, "failed to read file %s to add to RootCAs", *pullable.Cert)
}
// Append our cert to the system pool
if ok := rootCAs.AppendCertsFromPEM(certs); !ok {
return nil, errors.Wrap(err, "Failed to append registry certificate")
}
// Trust the augmented cert pool in our client
config := &tls.Config{
RootCAs: rootCAs,
}
transport.TLSClientConfig = config
}
transportOption := remote.WithTransport(transport)
authnOption := remote.WithAuthFromKeychain(authn.NewMultiKeychain(authn.DefaultKeychain))
img, err := remote.Image(ref, transportOption, authnOption)
if err != nil {
return nil, errors.WithStack(err)
}
return img, nil
}
// ExtractTarGz - does the job
func ExtractTarGz(gzipStream io.Reader) {
uncompressedStream, err := gzip.NewReader(gzipStream)
if err != nil {
log.Fatal("ExtractTarGz: NewReader failed")
}
tarReader := tar.NewReader(uncompressedStream)
for true {
header, err := tarReader.Next()
if err == io.EOF {
break
}
if err != nil {
log.Fatalf("ExtractTarGz: Next() failed: %s", err.Error())
}
switch header.Typeflag {
case tar.TypeDir:
if err := os.Mkdir(header.Name, 0755); err != nil {
log.Warnf("ExtractTarGz: Mkdir() failed: %s", err.Error())
}
case tar.TypeReg:
outFile, err := os.Create(header.Name)
if err != nil {
log.Fatalf("ExtractTarGz: Create() failed: %s", err.Error())
}
if _, err := io.Copy(outFile, tarReader); err != nil {
log.Fatalf("ExtractTarGz: Copy() failed: %s", err.Error())
}
outFile.Close()
default:
log.Fatalf(
"ExtractTarGz: uknown type: %s in %s",
header.Typeflag,
header.Name)
}
}
}
func main() {
log.Infoln("Here we go")
var p PullableImage
p.Name = "code.fnnrn.me/tinkerbell/shack:test"
i, err := p.pull()
if err != nil {
log.Fatal(err)
}
l, err := i.Layers()
if err != nil {
log.Fatal(err)
}
log.Infof("Found [%d] layers", len(l))
//log.Infof("%v", d.Hex)
var indexManifest v1.IndexManifest
r, err := l[0].Uncompressed()
tr := tar.NewReader(r)
for {
hdr, err := tr.Next()
if err == io.EOF {
break // End of archive
}
if err != nil {
log.Fatal(err)
}
if strings.Contains(hdr.Name, "index.json") {
err := json.NewDecoder(tr).Decode(&indexManifest)
if err != nil {
log.Fatal(err)
}
}
}
sha := indexManifest.Manifests[0].Digest.Hex
log.Infof("Found manifest SHA %s", sha)
var indexSchema v1.Manifest
r, err = l[0].Uncompressed()
tr = tar.NewReader(r)
for {
hdr, err := tr.Next()
if err == io.EOF {
break // End of archive
}
if err != nil {
log.Fatal(err)
}
if strings.Contains(hdr.Name, sha) {
err := json.NewDecoder(tr).Decode(&indexSchema)
if err != nil {
log.Fatal(err)
}
}
}
sha = indexSchema.Layers[0].Digest.Hex
log.Infof("Found manifest SHA %s", sha)
r, err = l[0].Uncompressed()
tr = tar.NewReader(r)
for {
hdr, err := tr.Next()
if err == io.EOF {
break // End of archive
}
if err != nil {
log.Fatal(err)
}
if strings.Contains(hdr.Name, sha) {
ExtractTarGz(tr)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment