github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/pkg/buildplan/buildplan.go (about) 1 package buildplan 2 3 import ( 4 "encoding/json" 5 6 "github.com/ActiveState/cli/internal/errs" 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 type BuildPlan struct { 15 platforms []strfmt.UUID 16 artifacts Artifacts 17 requirements Requirements 18 ingredients Ingredients 19 raw *raw.Build 20 } 21 22 func Unmarshal(data []byte) (*BuildPlan, error) { 23 logging.Debug("Unmarshalling buildplan") 24 25 b := &BuildPlan{} 26 27 var rawBuild raw.Build 28 if err := json.Unmarshal(data, &rawBuild); err != nil { 29 return nil, errs.Wrap(err, "error unmarshalling build plan") 30 } 31 32 b.raw = &rawBuild 33 34 b.cleanup() 35 36 if err := b.hydrate(); err != nil { 37 return nil, errs.Wrap(err, "error hydrating build plan") 38 } 39 40 if len(b.artifacts) == 0 || len(b.ingredients) == 0 || len(b.platforms) == 0 { 41 return nil, errs.New("Buildplan unmarshalling failed as it got zero artifacts (%d), ingredients (%d) and or platforms (%d).", 42 len(b.artifacts), len(b.ingredients), len(b.platforms)) 43 } 44 45 return b, nil 46 } 47 48 func (b *BuildPlan) Marshal() ([]byte, error) { 49 return json.Marshal(b.raw) 50 } 51 52 // cleanup empty targets 53 // The type aliasing in the query populates the response with emtpy targets that we should remove 54 func (b *BuildPlan) cleanup() { 55 logging.Debug("Cleaning up build plan") 56 57 b.raw.Steps = sliceutils.Filter(b.raw.Steps, func(s *raw.Step) bool { 58 return s.StepID != "" 59 }) 60 61 b.raw.Sources = sliceutils.Filter(b.raw.Sources, func(s *raw.Source) bool { 62 return s.NodeID != "" 63 }) 64 65 b.raw.Artifacts = sliceutils.Filter(b.raw.Artifacts, func(a *raw.Artifact) bool { 66 return a.NodeID != "" 67 }) 68 } 69 70 func (b *BuildPlan) Platforms() []strfmt.UUID { 71 return b.platforms 72 } 73 74 func (b *BuildPlan) Artifacts(filters ...FilterArtifact) Artifacts { 75 return b.artifacts.Filter(filters...) 76 } 77 78 type filterIngredient func(i *Ingredient) bool 79 80 func (b *BuildPlan) Ingredients(filters ...filterIngredient) Ingredients { 81 return b.ingredients.Filter(filters...) 82 } 83 84 func (b *BuildPlan) DiffArtifacts(oldBp *BuildPlan, requestedOnly bool) ArtifactChangeset { 85 // Basic outline of what needs to happen here: 86 // - add ArtifactID to the `Added` field if artifactID only appears in the the `new` buildplan 87 // - add ArtifactID to the `Removed` field if artifactID only appears in the the `old` buildplan 88 // - add ArtifactID to the `Updated` field if `ResolvedRequirements.feature` appears in both buildplans, but the resolved version has changed. 89 90 var new ArtifactNameMap 91 var old ArtifactNameMap 92 93 if requestedOnly { 94 new = b.RequestedArtifacts().ToNameMap() 95 old = oldBp.RequestedArtifacts().ToNameMap() 96 } else { 97 new = b.Artifacts().ToNameMap() 98 old = oldBp.Artifacts().ToNameMap() 99 } 100 101 var updated []ArtifactUpdate 102 var added []*Artifact 103 for name, artf := range new { 104 if artfOld, notNew := old[name]; notNew { 105 // The artifact name exists in both the old and new recipe, maybe it was updated though 106 if artfOld.ArtifactID == artf.ArtifactID { 107 continue 108 } 109 updated = append(updated, ArtifactUpdate{ 110 From: artfOld, 111 To: artf, 112 }) 113 114 } else { 115 // If it's not an update it is a new artifact 116 added = append(added, artf) 117 } 118 } 119 120 var removed []*Artifact 121 for name, artf := range old { 122 if _, noDiff := new[name]; noDiff { 123 continue 124 } 125 removed = append(removed, artf) 126 } 127 128 return ArtifactChangeset{ 129 Added: added, 130 Removed: removed, 131 Updated: updated, 132 } 133 } 134 135 func (b *BuildPlan) Engine() types.BuildEngine { 136 buildEngine := types.Alternative 137 for _, s := range b.raw.Sources { 138 if s.Namespace == "builder" && s.Name == "camel" { 139 buildEngine = types.Camel 140 break 141 } 142 } 143 return buildEngine 144 } 145 146 // RecipeID extracts the recipe ID from the BuildLogIDs. 147 // We do this because if the build is in progress we will need to reciepe ID to 148 // initialize the build log streamer. 149 // This information will only be populated if the build is an alternate build. 150 // This is specified in the build planner queries. 151 func (b *BuildPlan) RecipeID() (strfmt.UUID, error) { 152 var result strfmt.UUID 153 for _, id := range b.raw.BuildLogIDs { 154 if result != "" && result.String() != id.ID { 155 return result, errs.New("Build plan contains multiple recipe IDs") 156 } 157 result = strfmt.UUID(id.ID) 158 } 159 return result, nil 160 } 161 162 func (b *BuildPlan) IsBuildReady() bool { 163 return b.raw.Status == raw.Completed 164 } 165 166 func (b *BuildPlan) IsBuildInProgress() bool { 167 return b.raw.Status == raw.Started || b.raw.Status == raw.Planned 168 } 169 170 // RequestedIngredients returns the resolved requirements of the buildplan as ingredients 171 func (b *BuildPlan) RequestedIngredients() Ingredients { 172 ingredients := Ingredients{} 173 seen := make(map[strfmt.UUID]struct{}) 174 for _, r := range b.requirements { 175 if _, ok := seen[r.Ingredient.IngredientID]; ok { 176 continue 177 } 178 seen[r.Ingredient.IngredientID] = struct{}{} 179 ingredients = append(ingredients, r.Ingredient) 180 } 181 return ingredients 182 } 183 184 // RequestedArtifacts returns the resolved requirements of the buildplan as artifacts 185 func (b *BuildPlan) RequestedArtifacts() Artifacts { 186 result := []*Artifact{} 187 for _, i := range b.RequestedIngredients() { 188 for _, a := range i.Artifacts { 189 result = append(result, a) 190 } 191 } 192 return result 193 } 194 195 // Requirements returns what the project has defined as the top level requirements (ie. the "order"). 196 // This is usually the same as the "ingredients" but it can be different if the project has multiple requirements that 197 // are satisfied by the same ingredient. eg. rake is satisfied by ruby. 198 func (b *BuildPlan) Requirements() Requirements { 199 return b.requirements 200 }