184 lines
3.8 KiB
Go
184 lines
3.8 KiB
Go
package ociimg
|
|
|
|
import (
|
|
"archive/tar"
|
|
"errors"
|
|
"io/fs"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"code.icb4dc0.de/buildr/buildr/internal/ioutils"
|
|
)
|
|
|
|
func buildDirFromContent(cwd string, content map[string]string) (*dirNode, error) {
|
|
rootNode := newDirNode()
|
|
for origSource, target := range content {
|
|
source := origSource
|
|
if !filepath.IsAbs(source) {
|
|
source = filepath.Join(cwd, source)
|
|
}
|
|
|
|
info, err := os.Stat(source)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !info.IsDir() {
|
|
if strings.HasSuffix(target, "/") {
|
|
_, fileName := filepath.Split(source)
|
|
target = filepath.Join(target, fileName)
|
|
}
|
|
|
|
rootNode.addFile(source, target)
|
|
|
|
continue
|
|
}
|
|
|
|
walkErr := filepath.WalkDir(source, func(path string, d fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
relativeToSource, err := filepath.Rel(source, path)
|
|
if err != nil {
|
|
return err
|
|
} else if relativeToSource == "." {
|
|
return nil
|
|
}
|
|
|
|
if d.IsDir() {
|
|
rootNode.addDir(relativeToSource)
|
|
} else {
|
|
rootNode.addFile(path, filepath.Join(target, relativeToSource))
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
if walkErr != nil {
|
|
return nil, walkErr
|
|
}
|
|
}
|
|
|
|
return rootNode, nil
|
|
}
|
|
|
|
type fileToTar struct {
|
|
sourcePath string
|
|
targetFileName string
|
|
}
|
|
|
|
func newDirNode() *dirNode {
|
|
return &dirNode{
|
|
children: make(map[string]*dirNode),
|
|
}
|
|
}
|
|
|
|
type dirNode struct {
|
|
children map[string]*dirNode
|
|
files []fileToTar
|
|
}
|
|
|
|
func (n *dirNode) addDir(dirPath string) {
|
|
current := n
|
|
for _, segment := range strings.Split(dirPath, "/") {
|
|
if child, ok := n.children[segment]; ok {
|
|
current = child
|
|
} else {
|
|
newNode := newDirNode()
|
|
current.children[segment] = newNode
|
|
current = newNode
|
|
}
|
|
}
|
|
}
|
|
|
|
func (n *dirNode) addFile(sourcePath, targetPath string) {
|
|
if !filepath.IsAbs(targetPath) {
|
|
targetPath = filepath.Join("", targetPath)
|
|
}
|
|
dirPath, fileName := filepath.Split(targetPath)
|
|
current := n
|
|
for _, segment := range strings.Split(dirPath, "/") {
|
|
if segment == "" {
|
|
continue
|
|
}
|
|
if child, ok := current.children[segment]; ok {
|
|
current = child
|
|
} else {
|
|
newNode := &dirNode{
|
|
children: make(map[string]*dirNode),
|
|
}
|
|
current.children[segment] = newNode
|
|
current = newNode
|
|
}
|
|
}
|
|
|
|
current.files = append(current.files, fileToTar{
|
|
sourcePath: sourcePath,
|
|
targetFileName: fileName,
|
|
})
|
|
}
|
|
|
|
func (n *dirNode) writeToTar(writer *tar.Writer, parent string) error {
|
|
for segment, child := range n.children {
|
|
segmentPath := filepath.Join(parent, segment)
|
|
header := &tar.Header{
|
|
Name: segmentPath,
|
|
Typeflag: tar.TypeDir,
|
|
// Use a fixed Mode, so that this isn't sensitive to the directory and umask
|
|
// under which it was created. Additionally, windows can only set 0222,
|
|
// 0444, or 0666, none of which are executable.
|
|
Mode: 0o555,
|
|
ModTime: defaultCreationTime,
|
|
}
|
|
|
|
if err := writer.WriteHeader(header); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := child.writeToTar(writer, segmentPath); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
for i := range n.files {
|
|
fileSpec := n.files[i]
|
|
info, err := os.Stat(fileSpec.sourcePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
f, err := os.Open(fileSpec.sourcePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
header := &tar.Header{
|
|
Name: path.Join(parent, fileSpec.targetFileName),
|
|
Size: info.Size(),
|
|
Typeflag: tar.TypeReg,
|
|
// Use a fixed Mode, so that this isn't sensitive to the directory and umask
|
|
// under which it was created. Additionally, windows can only set 0222,
|
|
// 0444, or 0666, none of which are executable.
|
|
Mode: 0o555,
|
|
ModTime: defaultCreationTime,
|
|
}
|
|
|
|
if err = writer.WriteHeader(header); err != nil {
|
|
return errors.Join(err, f.Close())
|
|
}
|
|
|
|
if _, err = ioutils.CopyWithPooledBuffer(writer, f); err != nil {
|
|
return errors.Join(err, f.Close())
|
|
}
|
|
|
|
if err := f.Close(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|