Created
February 10, 2021 12:43
-
-
Save thebsdbox/706913e794b32819b0286a0766f6ff9c to your computer and use it in GitHub Desktop.
🤮
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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