func maybeDecompress(rs io.ReadSeeker) (io.Reader, error) { // TODO(jonboulle): this is a bit redundant with detectValType typ, err := aci.DetectFileType(rs) if err != nil { return nil, err } if _, err := rs.Seek(0, 0); err != nil { return nil, err } var r io.Reader switch typ { case aci.TypeGzip: r, err = gzip.NewReader(rs) if err != nil { return nil, fmt.Errorf("error reading gzip: %v", err) } case aci.TypeBzip2: r = bzip2.NewReader(rs) case aci.TypeXz: r = aci.XzReader(rs) case aci.TypeTar: r = rs case aci.TypeUnknown: return nil, errors.New("unknown filetype") default: // should never happen panic(fmt.Sprintf("bad type returned from DetectFileType: %v", typ)) } return r, nil }
func detectValType(file *os.File) (string, error) { typ, err := aci.DetectFileType(file) if err != nil { return "", err } if _, err := file.Seek(0, 0); err != nil { return "", err } switch typ { case aci.TypeXz, aci.TypeGzip, aci.TypeBzip2, aci.TypeTar: return typeAppImage, nil case aci.TypeText: return typeManifest, nil default: return "", nil } }
// WriteACI takes an ACI encapsulated in an io.Reader, decompresses it if // necessary, and then stores it in the store under a key based on the image ID // (i.e. the hash of the uncompressed ACI) // latest defines if the aci has to be marked as the latest. For example an ACI // discovered without asking for a specific version (latest pattern). func (s Store) WriteACI(r io.Reader, latest bool) (string, error) { // Peek at the first 512 bytes of the reader to detect filetype br := bufio.NewReaderSize(r, 32768) hd, err := br.Peek(512) switch err { case nil: case io.EOF: // We may have still peeked enough to guess some types, so fall through default: return "", fmt.Errorf("error reading image header: %v", err) } typ, err := aci.DetectFileType(bytes.NewBuffer(hd)) if err != nil { return "", fmt.Errorf("error detecting image type: %v", err) } dr, err := decompress(br, typ) if err != nil { return "", fmt.Errorf("error decompressing image: %v", err) } // Write the decompressed image (tar) to a temporary file on disk, and // tee so we can generate the hash h := sha512.New() tr := io.TeeReader(dr, h) fh, err := s.TmpFile() if err != nil { return "", fmt.Errorf("error creating image: %v", err) } if _, err := io.Copy(fh, tr); err != nil { return "", fmt.Errorf("error copying image: %v", err) } im, err := aci.ManifestFromImage(fh) if err != nil { return "", fmt.Errorf("error extracting image manifest: %v", err) } if err := fh.Close(); err != nil { return "", fmt.Errorf("error closing image: %v", err) } // Import the uncompressed image into the store at the real key key := s.HashToKey(h) keyLock, err := lock.ExclusiveKeyLock(s.imageLockDir, key) if err != nil { return "", fmt.Errorf("error locking image: %v", err) } defer keyLock.Close() if err = s.stores[blobType].Import(fh.Name(), key, true); err != nil { return "", fmt.Errorf("error importing image: %v", err) } // Save the imagemanifest using the same key used for the image imj, err := json.Marshal(im) if err != nil { return "", fmt.Errorf("error marshalling image manifest: %v", err) } if err = s.stores[imageManifestType].Write(key, imj); err != nil { return "", fmt.Errorf("error importing image manifest: %v", err) } // Save aciinfo if err = s.db.Do(func(tx *sql.Tx) error { aciinfo := &ACIInfo{ BlobKey: key, AppName: im.Name.String(), ImportTime: time.Now(), Latest: latest, } return WriteACIInfo(tx, aciinfo) }); err != nil { return "", fmt.Errorf("error writing ACI Info: %v", err) } // The treestore for this ACI is not written here as ACIs downloaded as // dependencies of another ACI will be exploded also if never directly used. // Users of treestore should call s.RenderTreeStore before using it. return key, nil }