github.com/devster/tarreleaser@v0.0.0-20221207180803-c608f4eb8918/pkg/pipe/archive/archive.go (about)

     1  package archive
     2  
     3  import (
     4  	"compress/gzip"
     5  	"fmt"
     6  	"github.com/apex/log"
     7  	"github.com/campoy/unique"
     8  	"github.com/devster/tarreleaser/pkg/archive/targz"
     9  	"github.com/devster/tarreleaser/pkg/context"
    10  	"github.com/devster/tarreleaser/pkg/static"
    11  	"github.com/devster/tarreleaser/pkg/tmpl"
    12  	"github.com/dustin/go-humanize"
    13  	"github.com/mattn/go-zglob"
    14  	"github.com/pkg/errors"
    15  	"os"
    16  	"path/filepath"
    17  	"strings"
    18  )
    19  
    20  type Pipe struct{}
    21  
    22  func (Pipe) String() string {
    23  	return "archiving"
    24  }
    25  
    26  func (Pipe) Default(ctx *context.Context) error {
    27  	if ctx.Config.Archive.Name == "" {
    28  		ctx.Config.Archive.Name = "release.tar.gz"
    29  	}
    30  
    31  	if ctx.Config.Archive.CompressionLevel == 0 {
    32  		ctx.Config.Archive.CompressionLevel = gzip.DefaultCompression
    33  	}
    34  
    35  	if len(ctx.Config.Archive.IncludeFiles) == 0 && len(ctx.Config.Archive.ExcludeFiles) == 0 {
    36  		ctx.Config.Archive.ExcludeFiles = []string{
    37  			".git",
    38  		}
    39  	}
    40  
    41  	// Exclude the dist directory
    42  	ctx.Config.Archive.ExcludeFiles = append(ctx.Config.Archive.ExcludeFiles, ctx.Config.Dist)
    43  
    44  	if len(ctx.Config.Archive.IncludeFiles) == 0 {
    45  		ctx.Config.Archive.IncludeFiles = []string{
    46  			"./**/*",
    47  		}
    48  	}
    49  
    50  	// Release info file defaults
    51  	if ctx.Config.Archive.InfoFile.Name != "" || ctx.Config.Archive.InfoFile.Content != "" {
    52  		if ctx.Config.Archive.InfoFile.Name == "" {
    53  			ctx.Config.Archive.InfoFile.Name = "release.txt"
    54  		}
    55  
    56  		if ctx.Config.Archive.InfoFile.Content == "" {
    57  			ctx.Config.Archive.InfoFile.Content = static.DefaultReleaseFileContent
    58  		}
    59  	}
    60  
    61  	return nil
    62  }
    63  
    64  func (Pipe) Run(ctx *context.Context) error {
    65  	ctx.Config.Archive.IncludeFiles = betterGlob(ctx.Config.Archive.IncludeFiles)
    66  	ctx.Config.Archive.ExcludeFiles = betterGlob(ctx.Config.Archive.ExcludeFiles)
    67  
    68  	t := tmpl.New(ctx)
    69  
    70  	archiveName, err := t.Apply(ctx.Config.Archive.Name)
    71  	if err != nil {
    72  		return err
    73  	}
    74  	archivePath := filepath.Join(ctx.Config.Dist, archiveName)
    75  	archiveFile, err := os.Create(archivePath)
    76  	if err != nil {
    77  		return errors.Wrapf(err, "failed to create archive file: %s", archivePath)
    78  	}
    79  	defer archiveFile.Close()
    80  
    81  	wrap, err := t.Apply(ctx.Config.Archive.WrapInDirectory)
    82  	if err != nil {
    83  		return err
    84  	}
    85  
    86  	log.WithFields(log.Fields{
    87  		"archive":  archivePath,
    88  		"gzip_lvl": ctx.Config.Archive.CompressionLevel,
    89  		"wrap":     wrap,
    90  	}).Info("creating archive")
    91  
    92  	a, err := targz.New(archiveFile, ctx.Config.Archive.CompressionLevel)
    93  	if err != nil {
    94  		return errors.Wrap(err, "failed to create archive")
    95  	}
    96  	defer a.Close()
    97  
    98  	files, err := findFiles(ctx)
    99  	if err != nil {
   100  		return fmt.Errorf("failed to find files to archive: %s", err.Error())
   101  	}
   102  
   103  	// add files to archive
   104  	for _, f := range files {
   105  		name := filepath.Join(wrap, f)
   106  		log.Debugf("adding file: %s", f)
   107  
   108  		if err = a.Add(name, f); err != nil {
   109  			return fmt.Errorf("failed to add %s to the archive: %s", f, err.Error())
   110  		}
   111  	}
   112  
   113  	// add empty dirs to archive
   114  	for dir, mode := range ctx.Config.Archive.EmptyDirs {
   115  		dir = filepath.Join(wrap, dir)
   116  		// we add a trailing slash so tar.Header will know this is a directory
   117  		dir = fmt.Sprintf("%s/", strings.TrimSuffix(dir, "/"))
   118  		log.Debugf("adding empty dir with mode %v: %s", mode, dir)
   119  
   120  		if err = a.AddFromString(dir, "", mode); err != nil {
   121  			return fmt.Errorf("failed to add empty dir %s to the archive: %s", dir, err.Error())
   122  		}
   123  	}
   124  
   125  	// add release info file to archive
   126  	if err := addReleaseInfoFile(a, ctx, wrap); err != nil {
   127  		return errors.Wrap(err, "failed to add release file info")
   128  	}
   129  
   130  	archiveInfo, err := archiveFile.Stat()
   131  	if err != nil {
   132  		return errors.Wrap(err, "unable to retrieve stat on archive file")
   133  	}
   134  
   135  	log.WithFields(log.Fields{
   136  		"files": len(files),
   137  		"size":  humanize.Bytes(uint64(archiveInfo.Size())),
   138  	}).Info("archive created with success")
   139  
   140  	ctx.Archive.Path = archivePath
   141  	ctx.Archive.Name = archiveName
   142  
   143  	return nil
   144  }
   145  
   146  func findFiles(ctx *context.Context) (result []string, err error) {
   147  	for _, glob := range ctx.Config.Archive.IncludeFiles {
   148  		files, err := zglob.Glob(glob)
   149  		if err != nil {
   150  			return result, fmt.Errorf("include globbing failed for pattern %s: %s", glob, err.Error())
   151  		}
   152  		// excluding files that matches exclude glob pattern
   153  		for _, f := range files {
   154  			ok, err := isFileExcluded(ctx.Config.Archive.ExcludeFiles, f)
   155  			if err != nil {
   156  				return result, err
   157  			}
   158  
   159  			if !ok {
   160  				result = append(result, f)
   161  			}
   162  		}
   163  	}
   164  	// remove duplicates
   165  	unique.Slice(&result, func(i, j int) bool {
   166  		return strings.Compare(result[i], result[j]) < 0
   167  	})
   168  	return
   169  }
   170  
   171  func isFileExcluded(patterns []string, file string) (bool, error) {
   172  	for _, pattern := range patterns {
   173  		ok, err := zglob.Match(pattern, file)
   174  		if err != nil {
   175  			return false, fmt.Errorf("exclude globbing failed for pattern %s: %s", pattern, err.Error())
   176  		}
   177  		if ok {
   178  			return true, nil
   179  		}
   180  	}
   181  
   182  	return false, nil
   183  }
   184  
   185  // Convert existing dirs into glob pattern to have the same effect that git does with the .gitignore file
   186  func betterGlob(patterns []string) (result []string) {
   187  	for _, glob := range patterns {
   188  		if info, err := os.Stat(glob); err == nil && info.IsDir() {
   189  			result = append(result, info.Name(), filepath.Join(info.Name(), "**/*"))
   190  			continue
   191  		}
   192  
   193  		result = append(result, glob)
   194  	}
   195  
   196  	return
   197  }
   198  
   199  func addReleaseInfoFile(a *targz.Archive, ctx *context.Context, wrap string) error {
   200  	if ctx.Config.Archive.InfoFile.Name == "" {
   201  		return nil
   202  	}
   203  
   204  	t := tmpl.New(ctx)
   205  
   206  	name, err := t.Apply(ctx.Config.Archive.InfoFile.Name)
   207  	if err != nil {
   208  		return err
   209  	}
   210  	name = filepath.Join(wrap, name)
   211  
   212  	log.WithField("file", name).Info("adding release info file")
   213  
   214  	content, err := t.Apply(ctx.Config.Archive.InfoFile.Content)
   215  	if err != nil {
   216  		return err
   217  	}
   218  
   219  	return a.AddFromString(name, content, 0644)
   220  }