mirror of
https://github.com/hauler-dev/hauler.git
synced 2026-02-14 18:09:51 +00:00
166 lines
3.1 KiB
Go
166 lines
3.1 KiB
Go
package getter
|
|
|
|
import (
|
|
"archive/tar"
|
|
"compress/gzip"
|
|
"context"
|
|
"io"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/opencontainers/go-digest"
|
|
"github.com/pkg/errors"
|
|
|
|
"hauler.dev/go/hauler/pkg/artifacts"
|
|
"hauler.dev/go/hauler/pkg/consts"
|
|
)
|
|
|
|
type directory struct {
|
|
*File
|
|
}
|
|
|
|
func NewDirectory() *directory {
|
|
return &directory{File: NewFile()}
|
|
}
|
|
|
|
func (d directory) Open(ctx context.Context, u *url.URL) (io.ReadCloser, error) {
|
|
tmpfile, err := os.CreateTemp("", "hauler")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
digester := digest.Canonical.Digester()
|
|
zw := gzip.NewWriter(io.MultiWriter(tmpfile, digester.Hash()))
|
|
defer zw.Close()
|
|
|
|
tarDigester := digest.Canonical.Digester()
|
|
if err := tarDir(d.path(u), d.Name(u), io.MultiWriter(zw, tarDigester.Hash()), false); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := zw.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := tmpfile.Sync(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
fi, err := os.Open(tmpfile.Name())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// rc := &closer{
|
|
// t: io.TeeReader(tmpfile, fi),
|
|
// closes: []func() error{fi.Close, tmpfile.Close, zw.Close},
|
|
// }
|
|
return fi, nil
|
|
}
|
|
|
|
func (d directory) Detect(u *url.URL) bool {
|
|
if len(d.path(u)) == 0 {
|
|
return false
|
|
}
|
|
|
|
fi, err := os.Stat(d.path(u))
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return fi.IsDir()
|
|
}
|
|
|
|
func (d directory) Config(u *url.URL) artifacts.Config {
|
|
c := &directoryConfig{
|
|
config{Reference: u.String()},
|
|
}
|
|
return artifacts.ToConfig(c, artifacts.WithConfigMediaType(consts.FileDirectoryConfigMediaType))
|
|
}
|
|
|
|
type directoryConfig struct {
|
|
config `json:",inline,omitempty"`
|
|
}
|
|
|
|
func tarDir(root string, prefix string, w io.Writer, stripTimes bool) error {
|
|
tw := tar.NewWriter(w)
|
|
defer tw.Close()
|
|
if err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Rename path
|
|
name, err := filepath.Rel(root, path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
name = filepath.Join(prefix, name)
|
|
name = filepath.ToSlash(name)
|
|
|
|
// Generate header
|
|
var link string
|
|
mode := info.Mode()
|
|
if mode&os.ModeSymlink != 0 {
|
|
if link, err = os.Readlink(path); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
header, err := tar.FileInfoHeader(info, link)
|
|
if err != nil {
|
|
return errors.Wrap(err, path)
|
|
}
|
|
header.Name = name
|
|
header.Uid = 0
|
|
header.Gid = 0
|
|
header.Uname = ""
|
|
header.Gname = ""
|
|
|
|
if stripTimes {
|
|
header.ModTime = time.Time{}
|
|
header.AccessTime = time.Time{}
|
|
header.ChangeTime = time.Time{}
|
|
}
|
|
|
|
// Write file
|
|
if err := tw.WriteHeader(header); err != nil {
|
|
return errors.Wrap(err, "tar")
|
|
}
|
|
if mode.IsRegular() {
|
|
file, err := os.Open(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer file.Close()
|
|
if _, err := io.Copy(tw, file); err != nil {
|
|
return errors.Wrap(err, path)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type closer struct {
|
|
t io.Reader
|
|
closes []func() error
|
|
}
|
|
|
|
func (c *closer) Read(p []byte) (n int, err error) {
|
|
return c.t.Read(p)
|
|
}
|
|
|
|
func (c *closer) Close() error {
|
|
var err error
|
|
for _, c := range c.closes {
|
|
lastErr := c()
|
|
if err == nil {
|
|
err = lastErr
|
|
}
|
|
}
|
|
return err
|
|
}
|