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(&params.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  }