github.com/ahmet2mir/goreleaser@v0.180.3-0.20210927151101-8e5ee5a9b8c5/internal/artifact/artifact.go (about)

     1  // Package artifact provides the core artifact storage for goreleaser.
     2  package artifact
     3  
     4  // nolint: gosec
     5  import (
     6  	"crypto/md5"
     7  	"crypto/sha1"
     8  	"crypto/sha256"
     9  	"crypto/sha512"
    10  	"encoding/hex"
    11  	"fmt"
    12  	"hash"
    13  	"hash/crc32"
    14  	"io"
    15  	"os"
    16  	"sync"
    17  
    18  	"github.com/apex/log"
    19  )
    20  
    21  // Type defines the type of an artifact.
    22  type Type int
    23  
    24  const (
    25  	// UploadableArchive a tar.gz/zip archive to be uploaded.
    26  	UploadableArchive Type = iota
    27  	// UploadableBinary is a binary file to be uploaded.
    28  	UploadableBinary
    29  	// UploadableFile is any file that can be uploaded.
    30  	UploadableFile
    31  	// Binary is a binary (output of a gobuild).
    32  	Binary
    33  	// LinuxPackage is a linux package generated by nfpm.
    34  	LinuxPackage
    35  	// PublishableSnapcraft is a snap package yet to be published.
    36  	PublishableSnapcraft
    37  	// Snapcraft is a published snap package.
    38  	Snapcraft
    39  	// PublishableDockerImage is a Docker image yet to be published.
    40  	PublishableDockerImage
    41  	// DockerImage is a published Docker image.
    42  	DockerImage
    43  	// DockerManifest is a published Docker manifest.
    44  	DockerManifest
    45  	// Checksum is a checksums file.
    46  	Checksum
    47  	// Signature is a signature file.
    48  	Signature
    49  	// UploadableSourceArchive is the archive with the current commit source code.
    50  	UploadableSourceArchive
    51  	// BrewTap is an uploadable homebrew tap recipe file.
    52  	BrewTap
    53  	// ScoopManifest is an uploadable scoop manifest file.
    54  	ScoopManifest
    55  )
    56  
    57  func (t Type) String() string {
    58  	switch t {
    59  	case UploadableArchive:
    60  		return "Archive"
    61  	case UploadableFile:
    62  		return "File"
    63  	case UploadableBinary, Binary:
    64  		return "Binary"
    65  	case LinuxPackage:
    66  		return "Linux Package"
    67  	case PublishableDockerImage, DockerImage:
    68  		return "Docker Image"
    69  	case DockerManifest:
    70  		return "Docker Manifest"
    71  	case PublishableSnapcraft, Snapcraft:
    72  		return "Snap"
    73  	case Checksum:
    74  		return "Checksum"
    75  	case Signature:
    76  		return "Signature"
    77  	case UploadableSourceArchive:
    78  		return "Source"
    79  	case BrewTap:
    80  		return "Brew Tap"
    81  	case ScoopManifest:
    82  		return "Scoop Manifest"
    83  	default:
    84  		return "unknown"
    85  	}
    86  }
    87  
    88  // Artifact represents an artifact and its relevant info.
    89  type Artifact struct {
    90  	Name   string
    91  	Path   string
    92  	Goos   string
    93  	Goarch string
    94  	Goarm  string
    95  	Gomips string
    96  	Type   Type
    97  	Extra  map[string]interface{}
    98  }
    99  
   100  // ExtraOr returns the Extra field with the given key or the or value specified
   101  // if it is nil.
   102  func (a Artifact) ExtraOr(key string, or interface{}) interface{} {
   103  	if a.Extra[key] == nil {
   104  		return or
   105  	}
   106  	return a.Extra[key]
   107  }
   108  
   109  // Checksum calculates the checksum of the artifact.
   110  // nolint: gosec
   111  func (a Artifact) Checksum(algorithm string) (string, error) {
   112  	log.Debugf("calculating checksum for %s", a.Path)
   113  	file, err := os.Open(a.Path)
   114  	if err != nil {
   115  		return "", fmt.Errorf("failed to checksum: %w", err)
   116  	}
   117  	defer file.Close()
   118  	var h hash.Hash
   119  	switch algorithm {
   120  	case "crc32":
   121  		h = crc32.NewIEEE()
   122  	case "md5":
   123  		h = md5.New()
   124  	case "sha224":
   125  		h = sha256.New224()
   126  	case "sha384":
   127  		h = sha512.New384()
   128  	case "sha256":
   129  		h = sha256.New()
   130  	case "sha1":
   131  		h = sha1.New()
   132  	case "sha512":
   133  		h = sha512.New()
   134  	default:
   135  		return "", fmt.Errorf("invalid algorithm: %s", algorithm)
   136  	}
   137  
   138  	if _, err := io.Copy(h, file); err != nil {
   139  		return "", fmt.Errorf("failed to checksum: %w", err)
   140  	}
   141  	return hex.EncodeToString(h.Sum(nil)), nil
   142  }
   143  
   144  // Artifacts is a list of artifacts.
   145  type Artifacts struct {
   146  	items []*Artifact
   147  	lock  *sync.Mutex
   148  }
   149  
   150  // New return a new list of artifacts.
   151  func New() Artifacts {
   152  	return Artifacts{
   153  		items: []*Artifact{},
   154  		lock:  &sync.Mutex{},
   155  	}
   156  }
   157  
   158  // List return the actual list of artifacts.
   159  func (artifacts Artifacts) List() []*Artifact {
   160  	return artifacts.items
   161  }
   162  
   163  // GroupByPlatform groups the artifacts by their platform.
   164  func (artifacts Artifacts) GroupByPlatform() map[string][]*Artifact {
   165  	result := map[string][]*Artifact{}
   166  	for _, a := range artifacts.items {
   167  		plat := a.Goos + a.Goarch + a.Goarm + a.Gomips
   168  		result[plat] = append(result[plat], a)
   169  	}
   170  	return result
   171  }
   172  
   173  // Add safely adds a new artifact to an artifact list.
   174  func (artifacts *Artifacts) Add(a *Artifact) {
   175  	artifacts.lock.Lock()
   176  	defer artifacts.lock.Unlock()
   177  	log.WithFields(log.Fields{
   178  		"name": a.Name,
   179  		"path": a.Path,
   180  		"type": a.Type,
   181  	}).Debug("added new artifact")
   182  	artifacts.items = append(artifacts.items, a)
   183  }
   184  
   185  // Filter defines an artifact filter which can be used within the Filter
   186  // function.
   187  type Filter func(a *Artifact) bool
   188  
   189  // ByGoos is a predefined filter that filters by the given goos.
   190  func ByGoos(s string) Filter {
   191  	return func(a *Artifact) bool {
   192  		return a.Goos == s
   193  	}
   194  }
   195  
   196  // ByGoarch is a predefined filter that filters by the given goarch.
   197  func ByGoarch(s string) Filter {
   198  	return func(a *Artifact) bool {
   199  		return a.Goarch == s
   200  	}
   201  }
   202  
   203  // ByGoarm is a predefined filter that filters by the given goarm.
   204  func ByGoarm(s string) Filter {
   205  	return func(a *Artifact) bool {
   206  		return a.Goarm == s
   207  	}
   208  }
   209  
   210  // ByType is a predefined filter that filters by the given type.
   211  func ByType(t Type) Filter {
   212  	return func(a *Artifact) bool {
   213  		return a.Type == t
   214  	}
   215  }
   216  
   217  // ByFormats filters artifacts by a `Format` extra field.
   218  func ByFormats(formats ...string) Filter {
   219  	filters := make([]Filter, 0, len(formats))
   220  	for _, format := range formats {
   221  		format := format
   222  		filters = append(filters, func(a *Artifact) bool {
   223  			return a.ExtraOr("Format", "") == format
   224  		})
   225  	}
   226  	return Or(filters...)
   227  }
   228  
   229  // ByIDs filter artifacts by an `ID` extra field.
   230  func ByIDs(ids ...string) Filter {
   231  	filters := make([]Filter, 0, len(ids))
   232  	for _, id := range ids {
   233  		id := id
   234  		filters = append(filters, func(a *Artifact) bool {
   235  			// checksum and source archive are always for all artifacts, so return always true.
   236  			return a.Type == Checksum ||
   237  				a.Type == UploadableSourceArchive ||
   238  				a.ExtraOr("ID", "") == id
   239  		})
   240  	}
   241  	return Or(filters...)
   242  }
   243  
   244  // Or performs an OR between all given filters.
   245  func Or(filters ...Filter) Filter {
   246  	return func(a *Artifact) bool {
   247  		for _, f := range filters {
   248  			if f(a) {
   249  				return true
   250  			}
   251  		}
   252  		return false
   253  	}
   254  }
   255  
   256  // And performs an AND between all given filters.
   257  func And(filters ...Filter) Filter {
   258  	return func(a *Artifact) bool {
   259  		for _, f := range filters {
   260  			if !f(a) {
   261  				return false
   262  			}
   263  		}
   264  		return true
   265  	}
   266  }
   267  
   268  // Filter filters the artifact list, returning a new instance.
   269  // There are some pre-defined filters but anything of the Type Filter
   270  // is accepted.
   271  // You can compose filters by using the And and Or filters.
   272  func (artifacts *Artifacts) Filter(filter Filter) Artifacts {
   273  	if filter == nil {
   274  		return *artifacts
   275  	}
   276  
   277  	result := New()
   278  	for _, a := range artifacts.items {
   279  		if filter(a) {
   280  			result.items = append(result.items, a)
   281  		}
   282  	}
   283  	return result
   284  }
   285  
   286  // Paths returns the artifact.Path of the current artifact list.
   287  func (artifacts Artifacts) Paths() []string {
   288  	var result []string
   289  	for _, artifact := range artifacts.List() {
   290  		result = append(result, artifact.Path)
   291  	}
   292  	return result
   293  }