github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/internal/runners/packages/list.go (about) 1 package packages 2 3 import ( 4 "errors" 5 "fmt" 6 "sort" 7 "strings" 8 9 "github.com/ActiveState/cli/pkg/buildplan" 10 bpModel "github.com/ActiveState/cli/pkg/platform/model/buildplanner" 11 "github.com/go-openapi/strfmt" 12 13 "github.com/ActiveState/cli/internal/analytics" 14 "github.com/ActiveState/cli/internal/config" 15 "github.com/ActiveState/cli/internal/errs" 16 "github.com/ActiveState/cli/internal/locale" 17 "github.com/ActiveState/cli/internal/logging" 18 "github.com/ActiveState/cli/internal/output" 19 "github.com/ActiveState/cli/internal/rtutils/ptr" 20 "github.com/ActiveState/cli/internal/runbits/rationalize" 21 "github.com/ActiveState/cli/pkg/localcommit" 22 gqlModel "github.com/ActiveState/cli/pkg/platform/api/graphql/model" 23 "github.com/ActiveState/cli/pkg/platform/authentication" 24 "github.com/ActiveState/cli/pkg/platform/model" 25 "github.com/ActiveState/cli/pkg/project" 26 ) 27 28 // ListRunParams tracks the info required for running List. 29 type ListRunParams struct { 30 Commit string 31 Name string 32 Project string 33 } 34 35 // List manages the listing execution context. 36 type List struct { 37 out output.Outputer 38 project *project.Project 39 analytics analytics.Dispatcher 40 svcModel *model.SvcModel 41 auth *authentication.Auth 42 cfg *config.Instance 43 } 44 45 // NewList prepares a list execution context for use. 46 func NewList(prime primeable) *List { 47 return &List{ 48 out: prime.Output(), 49 project: prime.Project(), 50 analytics: prime.Analytics(), 51 svcModel: prime.SvcModel(), 52 auth: prime.Auth(), 53 cfg: prime.Config(), 54 } 55 } 56 57 type requirement struct { 58 Name string `json:"package"` 59 Version string `json:"version" ` 60 ResolvedVersion string `json:"resolved_version"` 61 } 62 63 type requirementPlainOutput struct { 64 Name string `locale:"package_name,Name"` 65 Version string `locale:"package_version,Version"` 66 } 67 68 // Run executes the list behavior. 69 func (l *List) Run(params ListRunParams, nstype model.NamespaceType) error { 70 logging.Debug("ExecuteList") 71 72 l.out.Notice(locale.T("manifest_deprecation_warning")) 73 74 if l.project != nil && params.Project == "" { 75 l.out.Notice(locale.Tr("operating_message", l.project.NamespaceString(), l.project.Dir())) 76 } 77 78 var commitID *strfmt.UUID 79 var err error 80 switch { 81 case params.Commit != "": 82 commitID, err = targetFromCommit(params.Commit, l.project) 83 if err != nil { 84 return locale.WrapError(err, fmt.Sprintf("%s_err_cannot_obtain_commit", nstype)) 85 } 86 case params.Project != "": 87 commitID, err = targetFromProject(params.Project) 88 if err != nil { 89 return locale.WrapError(err, fmt.Sprintf("%s_err_cannot_obtain_commit", nstype)) 90 } 91 default: 92 commitID, err = targetFromProjectFile(l.project) 93 if err != nil { 94 return locale.WrapError(err, fmt.Sprintf("%s_err_cannot_obtain_commit", nstype)) 95 } 96 } 97 98 checkpoint, err := fetchCheckpoint(commitID, l.auth) 99 if err != nil { 100 return locale.WrapError(err, fmt.Sprintf("%s_err_cannot_fetch_checkpoint", nstype)) 101 } 102 103 language, err := model.LanguageByCommit(*commitID, l.auth) 104 if err != nil { 105 return locale.WrapError(err, "err_package_list_language", "Unable to get language from project") 106 } 107 ns := ptr.To(model.NewNamespacePkgOrBundle(language.Name, nstype)) 108 109 // Fetch resolved artifacts list for showing full version numbers, if possible. 110 var artifacts buildplan.Artifacts 111 if l.project != nil && params.Project == "" { 112 bpm := bpModel.NewBuildPlannerModel(l.auth) 113 commit, err := bpm.FetchCommit(*commitID, l.project.Owner(), l.project.Name(), nil) 114 if err != nil { 115 return errs.Wrap(err, "could not fetch commit") 116 } 117 artifacts = commit.BuildPlan().Artifacts(buildplan.FilterStateArtifacts()) 118 } 119 120 requirements := model.FilterCheckpointNamespace(checkpoint, model.NamespacePackage, model.NamespaceBundle) 121 sort.SliceStable(requirements, func(i, j int) bool { 122 return strings.ToLower(requirements[i].Requirement) < strings.ToLower(requirements[j].Requirement) 123 }) 124 125 requirementsPlainOutput := []requirementPlainOutput{} 126 requirementsOutput := []requirement{} 127 128 for _, req := range requirements { 129 if !strings.Contains(strings.ToLower(req.Requirement), strings.ToLower(params.Name)) { 130 continue 131 } 132 133 if !strings.HasPrefix(req.Namespace, nstype.Prefix()) { 134 continue 135 } 136 137 version := req.VersionConstraint 138 if version == "" { 139 version = model.GqlReqVersionConstraintsString(req) 140 if version == "" { 141 version = locale.T("constraint_auto") 142 } 143 } 144 145 resolvedVersion := "" 146 (func() { 147 for _, a := range artifacts { 148 for _, i := range a.Ingredients { 149 if i.Namespace == ns.String() && i.Name == req.Requirement { 150 resolvedVersion = i.Version 151 return // break outer loop 152 } 153 } 154 } 155 })() 156 157 plainVersion := version 158 if resolvedVersion != "" && resolvedVersion != version { 159 plainVersion = locale.Tr("constraint_resolved", version, resolvedVersion) 160 } 161 requirementsPlainOutput = append(requirementsPlainOutput, requirementPlainOutput{ 162 Name: req.Requirement, 163 Version: plainVersion, 164 }) 165 166 requirementsOutput = append(requirementsOutput, requirement{ 167 Name: req.Requirement, 168 Version: version, 169 ResolvedVersion: resolvedVersion, 170 }) 171 } 172 173 var plainOutput interface{} = requirementsPlainOutput 174 if len(requirementsOutput) == 0 { 175 plainOutput = locale.T(fmt.Sprintf("%s_list_no_packages", nstype.String())) 176 } 177 178 l.out.Print(output.Prepare(plainOutput, requirementsOutput)) 179 return nil 180 } 181 182 func targetFromCommit(commitOpt string, proj *project.Project) (*strfmt.UUID, error) { 183 if commitOpt == "latest" { 184 logging.Debug("latest commit selected") 185 return model.BranchCommitID(proj.Owner(), proj.Name(), proj.BranchName()) 186 } 187 188 return prepareCommit(commitOpt) 189 } 190 191 func targetFromProject(projectString string) (*strfmt.UUID, error) { 192 ns, err := project.ParseNamespace(projectString) 193 if err != nil { 194 return nil, err 195 } 196 197 branch, err := model.DefaultBranchForProjectName(ns.Owner, ns.Project) 198 if err != nil { 199 return nil, errs.Wrap(err, "Could not grab default branch for project") 200 } 201 202 return branch.CommitID, nil 203 } 204 205 func targetFromProjectFile(proj *project.Project) (*strfmt.UUID, error) { 206 logging.Debug("commit from project file") 207 if proj == nil { 208 return nil, rationalize.ErrNoProject 209 } 210 commit, err := localcommit.Get(proj.Dir()) 211 if err != nil { 212 return nil, errs.Wrap(err, "Unable to get local commit") 213 } 214 if commit == "" { 215 logging.Debug("latest commit used as fallback selection") 216 return model.BranchCommitID(proj.Owner(), proj.Name(), proj.BranchName()) 217 } 218 219 return prepareCommit(commit.String()) 220 } 221 222 func prepareCommit(commit string) (*strfmt.UUID, error) { 223 logging.Debug("commit %s selected", commit) 224 if ok := strfmt.Default.Validates("uuid", commit); !ok { 225 return nil, locale.NewInputError("err_invalid_commit", "Invalid commit: {{.V0}}", commit) 226 } 227 228 var uuid strfmt.UUID 229 if err := uuid.UnmarshalText([]byte(commit)); err != nil { 230 return nil, errs.Wrap(err, "UnmarshalText %s failed", commit) 231 } 232 233 return &uuid, nil 234 } 235 236 func fetchCheckpoint(commit *strfmt.UUID, auth *authentication.Auth) ([]*gqlModel.Requirement, error) { 237 if commit == nil { 238 logging.Debug("commit id is nil") 239 return nil, nil 240 } 241 242 checkpoint, _, err := model.FetchCheckpointForCommit(*commit, auth) 243 if err != nil && errors.Is(err, model.ErrNoData) { 244 return nil, locale.WrapExternalError(err, "package_no_data") 245 } 246 247 return checkpoint, err 248 }