github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/pkg/buildplan/artifact.go (about)

     1  package buildplan
     2  
     3  import (
     4  	"reflect"
     5  	"sort"
     6  
     7  	"github.com/ActiveState/cli/internal/logging"
     8  	"github.com/ActiveState/cli/internal/sliceutils"
     9  	"github.com/ActiveState/cli/pkg/buildplan/raw"
    10  	"github.com/ActiveState/cli/pkg/platform/api/buildplanner/types"
    11  	"github.com/go-openapi/strfmt"
    12  )
    13  
    14  // Artifact represents a downloadable artifact.
    15  // This artifact may or may not be installable by the State Tool.
    16  type Artifact struct {
    17  	raw *raw.Artifact // Don't expose as this may lead to external packages using low level buildplan logic
    18  
    19  	ArtifactID  strfmt.UUID
    20  	DisplayName string
    21  	MimeType    string
    22  	URL         string
    23  	LogURL      string
    24  	Errors      []string
    25  	Checksum    string
    26  	Status      string
    27  
    28  	Ingredients []*Ingredient `json:"-"` // While most artifacts only have a single ingredient, some artifacts such as installers can have multiple.
    29  
    30  	isRuntimeDependency   bool
    31  	isBuildtimeDependency bool
    32  
    33  	platforms []strfmt.UUID
    34  	children  []ArtifactRelation
    35  }
    36  
    37  type Relation int
    38  
    39  const (
    40  	RuntimeRelation Relation = iota
    41  	BuildtimeRelation
    42  )
    43  
    44  type ArtifactRelation struct {
    45  	Artifact *Artifact
    46  	Relation Relation
    47  }
    48  
    49  // Name returns the name of the ingredient for this artifact, if it only has exactly one ingredient associated.
    50  // Otherwise it returns the DisplayName, which is less reliable and consistent.
    51  func (a *Artifact) Name() string {
    52  	if len(a.Ingredients) == 1 {
    53  		return a.Ingredients[0].Name
    54  	}
    55  	logging.Debug("Using displayname because artifact has %d ingredients", len(a.Ingredients))
    56  	return a.DisplayName
    57  }
    58  
    59  // Version returns the name of the ingredient for this artifact, if it only has exactly one ingredient associated.
    60  // Otherwise it returns an empty version.
    61  func (a *Artifact) Version() string {
    62  	if len(a.Ingredients) == 1 {
    63  		return a.Ingredients[0].Version
    64  	}
    65  	return ""
    66  }
    67  
    68  func (a *Artifact) NameAndVersion() string {
    69  	version := a.Version()
    70  	if version == "" {
    71  		return a.Name()
    72  	}
    73  	return a.Name() + "@" + version
    74  }
    75  
    76  type Artifacts []*Artifact
    77  
    78  type ArtifactIDMap map[strfmt.UUID]*Artifact
    79  
    80  type ArtifactNameMap map[string]*Artifact
    81  
    82  func (a Artifacts) Filter(filters ...FilterArtifact) Artifacts {
    83  	if len(filters) == 0 {
    84  		return a
    85  	}
    86  	artifacts := []*Artifact{}
    87  	for _, ar := range a {
    88  		include := true
    89  		for _, filter := range filters {
    90  			if !filter(ar) {
    91  				include = false
    92  				break
    93  			}
    94  		}
    95  		if include {
    96  			artifacts = append(artifacts, ar)
    97  		}
    98  	}
    99  	return artifacts
   100  }
   101  
   102  func (a Artifacts) Ingredients() Ingredients {
   103  	result := Ingredients{}
   104  	for _, a := range a {
   105  		result = append(result, a.Ingredients...)
   106  	}
   107  	return sliceutils.Unique(result)
   108  }
   109  
   110  func (a Artifacts) ToIDMap() ArtifactIDMap {
   111  	result := make(map[strfmt.UUID]*Artifact, len(a))
   112  	for _, a := range a {
   113  		result[a.ArtifactID] = a
   114  	}
   115  	return result
   116  }
   117  
   118  func (a Artifacts) ToIDSlice() []strfmt.UUID {
   119  	result := make([]strfmt.UUID, len(a))
   120  	for n, a := range a {
   121  		result[n] = a.ArtifactID
   122  	}
   123  	return result
   124  }
   125  
   126  func (a Artifacts) ToNameMap() ArtifactNameMap {
   127  	result := make(map[string]*Artifact, len(a))
   128  	for _, a := range a {
   129  		name := a.DisplayName
   130  		if len(a.Ingredients) == 0 {
   131  			name = a.Ingredients[0].Name
   132  		}
   133  		result[name] = a
   134  	}
   135  	return result
   136  }
   137  
   138  type ArtifactChangeset struct {
   139  	Added   []*Artifact
   140  	Removed []*Artifact
   141  	Updated []ArtifactUpdate
   142  }
   143  
   144  type ArtifactUpdate struct {
   145  	From *Artifact
   146  	To   *Artifact
   147  }
   148  
   149  func (a ArtifactUpdate) VersionsChanged() bool {
   150  	fromVersions := []string{}
   151  	for _, ing := range a.From.Ingredients {
   152  		fromVersions = append(fromVersions, ing.Version)
   153  	}
   154  	sort.Strings(fromVersions)
   155  	toVersions := []string{}
   156  	for _, ing := range a.To.Ingredients {
   157  		toVersions = append(toVersions, ing.Version)
   158  	}
   159  	sort.Strings(toVersions)
   160  
   161  	return !reflect.DeepEqual(fromVersions, toVersions)
   162  }
   163  
   164  func (as Artifacts) RuntimeDependencies(recursive bool) Artifacts {
   165  	seen := make(map[strfmt.UUID]struct{})
   166  	dependencies := Artifacts{}
   167  	for _, a := range as {
   168  		dependencies = append(dependencies, a.runtimeDependencies(recursive, seen)...)
   169  	}
   170  	return dependencies
   171  }
   172  
   173  func (a *Artifact) RuntimeDependencies(recursive bool) Artifacts {
   174  	return a.runtimeDependencies(recursive, make(map[strfmt.UUID]struct{}))
   175  }
   176  
   177  func (a *Artifact) runtimeDependencies(recursive bool, seen map[strfmt.UUID]struct{}) Artifacts {
   178  	// Guard against recursion, this shouldn't really be possible but we don't know how the buildplan might evolve
   179  	// so better safe than sorry.
   180  	if _, ok := seen[a.ArtifactID]; ok {
   181  		return Artifacts{}
   182  	}
   183  	seen[a.ArtifactID] = struct{}{}
   184  
   185  	dependencies := Artifacts{}
   186  	for _, ac := range a.children {
   187  		if ac.Relation != RuntimeRelation {
   188  			continue
   189  		}
   190  		dependencies = append(dependencies, ac.Artifact)
   191  		if recursive {
   192  			dependencies = append(dependencies, ac.Artifact.RuntimeDependencies(recursive)...)
   193  		}
   194  	}
   195  	return dependencies
   196  }
   197  
   198  // SetDownload is used to update the URL and checksum of an artifact. This allows us to keep using the same artifact
   199  // type, while also facilitating dressing up in-progress artifacts with their download info later on
   200  func (a *Artifact) SetDownload(uri string, checksum string) {
   201  	a.URL = uri
   202  	a.Checksum = checksum
   203  	a.Status = types.ArtifactSucceeded
   204  }