buildr/modules/packaging/ociimg/tar.go
Peter 1261932bdc
All checks were successful
continuous-integration/drone/push Build is passing
refactor: apply golangci-lint findings
2023-06-22 19:16:00 +02:00

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
}