github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/internal/runners/packages/search.go (about) 1 package packages 2 3 import ( 4 "fmt" 5 6 "github.com/ActiveState/cli/internal/captain" 7 "github.com/ActiveState/cli/internal/errs" 8 "github.com/ActiveState/cli/internal/locale" 9 "github.com/ActiveState/cli/internal/logging" 10 "github.com/ActiveState/cli/internal/output" 11 "github.com/ActiveState/cli/pkg/localcommit" 12 "github.com/ActiveState/cli/pkg/platform/api/vulnerabilities/request" 13 "github.com/ActiveState/cli/pkg/platform/authentication" 14 "github.com/ActiveState/cli/pkg/platform/model" 15 "github.com/ActiveState/cli/pkg/project" 16 tea "github.com/charmbracelet/bubbletea" 17 ) 18 19 // SearchRunParams tracks the info required for running search. 20 type SearchRunParams struct { 21 Language string 22 ExactTerm bool 23 Ingredient captain.PackageValueNoVersion 24 Timestamp captain.TimeValue 25 } 26 27 // Search manages the searching execution context. 28 type Search struct { 29 out output.Outputer 30 proj *project.Project 31 auth *authentication.Auth 32 } 33 34 // NewSearch prepares a searching execution context for use. 35 func NewSearch(prime primeable) *Search { 36 return &Search{ 37 out: prime.Output(), 38 proj: prime.Project(), 39 auth: prime.Auth(), 40 } 41 } 42 43 // Run is executed when `state packages search` is ran 44 func (s *Search) Run(params SearchRunParams, nstype model.NamespaceType) error { 45 logging.Debug("ExecuteSearch") 46 47 s.out.Notice(output.Title(locale.Tl("search_title", "Searching for: [ACTIONABLE]{{.V0}}[/RESET]", params.Ingredient.Name))) 48 49 var ns model.Namespace 50 if params.Ingredient.Namespace == "" { 51 language, err := targetedLanguage(params.Language, s.proj, s.auth) 52 if err != nil { 53 return locale.WrapError(err, fmt.Sprintf("%s_err_cannot_obtain_language", nstype)) 54 } 55 56 ns = model.NewNamespacePkgOrBundle(language, nstype) 57 } else { 58 ns = model.NewRawNamespace(params.Ingredient.Namespace) 59 } 60 61 ts, err := getTime(¶ms.Timestamp, s.auth, s.proj) 62 if err != nil { 63 return errs.Wrap(err, "Unable to get timestamp from params") 64 } 65 66 var packages []*model.IngredientAndVersion 67 if params.ExactTerm { 68 packages, err = model.SearchIngredientsLatestStrict(ns.String(), params.Ingredient.Name, true, true, ts, s.auth) 69 } else { 70 packages, err = model.SearchIngredientsLatest(ns.String(), params.Ingredient.Name, true, ts, s.auth) 71 } 72 if err != nil { 73 return locale.WrapError(err, "package_err_cannot_obtain_search_results") 74 } 75 if len(packages) == 0 { 76 return errs.AddTips( 77 locale.NewInputError("err_search_no_"+ns.Type().String(), "", params.Ingredient.Name), 78 locale.Tl("search_try_term", "Try a different search term"), 79 locale.Tl("search_request_"+ns.Type().String(), ""), 80 ) 81 } 82 83 var vulns []*model.VulnerabilityIngredient 84 if s.auth.Authenticated() { 85 vulns, err = s.getVulns(packages) 86 if err != nil { 87 return errs.Wrap(err, "Could not fetch vulnerabilities") 88 } 89 } 90 91 results, err := createSearchResults(packages, vulns) 92 if err != nil { 93 return errs.Wrap(err, "Could not create search table") 94 } 95 96 if s.out.Type().IsStructured() || !s.out.Config().Interactive { 97 s.out.Print(results) 98 return nil 99 } 100 101 v, err := NewView(results, s.out) 102 if err != nil { 103 return errs.Wrap(err, "Could not create search view") 104 } 105 106 p := tea.NewProgram(v) 107 108 if _, err := p.Run(); err != nil { 109 return errs.Wrap(err, "Failed to run search view") 110 } 111 112 return nil 113 } 114 115 func targetedLanguage(languageOpt string, proj *project.Project, auth *authentication.Auth) (string, error) { 116 if languageOpt != "" { 117 return languageOpt, nil 118 } 119 if proj == nil { 120 return "", locale.NewInputError( 121 "err_no_language_derived", 122 "Language must be provided by flag or by running this command within a project.", 123 ) 124 } 125 126 commitID, err := localcommit.Get(proj.Dir()) 127 if err != nil { 128 return "", errs.Wrap(err, "Unable to get local commit") 129 } 130 lang, err := model.LanguageByCommit(commitID, auth) 131 if err != nil { 132 return "", errs.Wrap(err, "LanguageByCommit failed") 133 } 134 return lang.Name, nil 135 } 136 137 func (s *Search) getVulns(packages []*model.IngredientAndVersion) ([]*model.VulnerabilityIngredient, error) { 138 var ingredients []*request.Ingredient 139 for _, pkg := range packages { 140 ingredients = append(ingredients, &request.Ingredient{ 141 Name: *pkg.Ingredient.Name, 142 Namespace: *pkg.Ingredient.PrimaryNamespace, 143 Version: pkg.Version, 144 }) 145 } 146 147 return model.FetchVulnerabilitiesForIngredients(s.auth, ingredients) 148 }