pkg/template/archive/create.go (116 lines of code) (raw):
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package archive
import (
"archive/tar"
"compress/gzip"
"errors"
"fmt"
"io"
"io/fs"
"github.com/gobwas/glob"
"go.uber.org/zap"
"github.com/elastic/harp/pkg/sdk/log"
"github.com/elastic/harp/pkg/sdk/types"
)
// Create an archive from given options to the given writer.
//
//nolint:gocyclo,funlen // to refactor
func Create(fileSystem fs.FS, w io.Writer, opts ...CreateOption) error {
// Check arguments
if types.IsNil(fileSystem) {
return errors.New("fileSystem is nil")
}
if types.IsNil(w) {
return errors.New("output writer is nil")
}
// Prepare arguments
dopts := &createOptions{
rootPath: ".",
includeGlobs: []string{"**"},
excludeGlobs: []string{},
}
for _, o := range opts {
o(dopts)
}
// Ensure that the root path is valid
if !fs.ValidPath(dopts.rootPath) {
return fmt.Errorf("root path '%s' is not a valid path", dopts.rootPath)
}
// Ensure the root actually exists before trying to tar it
rootFi, err := fs.Stat(fileSystem, dopts.rootPath)
if err != nil {
return fmt.Errorf("unable to tar files: %w", err)
}
if !rootFi.IsDir() {
return errors.New("root path must be a directory")
}
// Compile inclusion filters
var includes []glob.Glob
for _, f := range dopts.includeGlobs {
// Try to compile glob filter.
filter, err := glob.Compile(f)
if err != nil {
return fmt.Errorf("unable to compile glob filter '%s' for inclusion: %w", f, err)
}
// Add to explusion filters.
includes = append(includes, filter)
}
// Compile explusion filters
var excludes []glob.Glob
for _, f := range dopts.excludeGlobs {
// Try to compile glob filter.
filter, err := glob.Compile(f)
if err != nil {
return fmt.Errorf("unable to compile glob filter '%s' for exclusion: %w", f, err)
}
// Add to explusion filters.
excludes = append(excludes, filter)
}
// Create writer chain.
zr := gzip.NewWriter(w)
tw := tar.NewWriter(zr)
// walk through every file in the folder
if errWalk := fs.WalkDir(fileSystem, dopts.rootPath, func(file string, dirEntry fs.DirEntry, errIn error) error {
// return on any error
if errIn != nil {
return errIn
}
// ignore invalid file path
if !fs.ValidPath(file) {
log.Bg().Debug("ignoring invalid path file ...", zap.String("file", file))
return nil
}
// Process inclusions
keep := false
for _, f := range includes {
if f.Match(file) {
keep = true
}
}
for _, f := range excludes {
if f.Match(file) {
keep = false
}
}
if !keep {
// Ignore this file.
log.Bg().Debug("ignoring file ...", zap.String("file", file))
return nil
}
// Get FileInfo
fi, err := dirEntry.Info()
if err != nil {
return fmt.Errorf("unable to retrieve fileInfo for '%s': %w", file, err)
}
log.Bg().Info("Add file to archive ...", zap.String("file", file))
// generate tar header
header, err := tar.FileInfoHeader(fi, file)
if err != nil {
return fmt.Errorf("unable to create TAR File header: %w", err)
}
// must provide real name
header.Name = file
header.Size = fi.Size()
header.Mode = int64(fi.Mode())
header.ModTime = fi.ModTime()
// write header
if err := tw.WriteHeader(header); err != nil {
return err
}
// if not a dir, write file content
if !fi.IsDir() {
data, err := fileSystem.Open(file)
if err != nil {
return err
}
if _, err := io.Copy(tw, io.LimitReader(data, 25*1024*1024)); err != nil {
return err
}
}
// No error
return nil
}); errWalk != nil {
return fmt.Errorf("fail to walk folders for archive compression: %w", errWalk)
}
// produce tar
if err := tw.Close(); err != nil {
return err
}
// produce gzip
if err := zr.Close(); err != nil {
return err
}
// No error
return nil
}