From 4ee317c2b4521ed15a96f084c5d64367850a2cc2 Mon Sep 17 00:00:00 2001 From: mitchell Date: Wed, 4 Sep 2024 16:11:08 -0400 Subject: [PATCH 1/6] Use new hasura-inventory endpoint for package search. --- .../runtime/requirements/requirements.go | 8 +-- internal/runners/packages/info.go | 72 ++++++------------- internal/runners/packages/search.go | 4 +- internal/runners/packages/searchresult.go | 14 ++-- internal/runners/publish/publish.go | 20 +++++- .../api/hasura_inventory/model/inventory.go | 24 +++++++ .../request/search_ingredients.go | 53 ++++++++++++++ pkg/platform/model/inventory.go | 65 +++++------------ pkg/platform/model/vcs.go | 2 +- 9 files changed, 150 insertions(+), 112 deletions(-) create mode 100644 pkg/platform/api/hasura_inventory/request/search_ingredients.go diff --git a/internal/runbits/runtime/requirements/requirements.go b/internal/runbits/runtime/requirements/requirements.go index f7625b5e1d..cf7cd8f89a 100644 --- a/internal/runbits/runtime/requirements/requirements.go +++ b/internal/runbits/runtime/requirements/requirements.go @@ -608,12 +608,12 @@ func resolvePkgAndNamespace(prompt prompt.Prompter, packageName string, nsType m choices := []string{} values := map[string][]string{} for _, i := range ingredients { - language := model.LanguageFromNamespace(*i.Ingredient.PrimaryNamespace) + language := model.LanguageFromNamespace(i.Namespace.Namespace) // Generate ingredient choices to present to the user - name := fmt.Sprintf("%s (%s)", *i.Ingredient.Name, language) + name := fmt.Sprintf("%s (%s)", i.Name, language) choices = append(choices, name) - values[name] = []string{*i.Ingredient.Name, language} + values[name] = []string{i.Name, language} } if len(choices) == 0 { @@ -656,7 +656,7 @@ func getSuggestions(ns model.Namespace, name string, auth *authentication.Auth) suggestions := make([]string, 0, maxResults+1) for _, result := range results { - suggestions = append(suggestions, fmt.Sprintf(" - %s", *result.Ingredient.Name)) + suggestions = append(suggestions, fmt.Sprintf(" - %s", result.Name)) } return suggestions, nil diff --git a/internal/runners/packages/info.go b/internal/runners/packages/info.go index fb559b8ecc..cb0cbc88a8 100644 --- a/internal/runners/packages/info.go +++ b/internal/runners/packages/info.go @@ -13,12 +13,11 @@ import ( "github.com/ActiveState/cli/internal/output" "github.com/ActiveState/cli/internal/rtutils/ptr" "github.com/ActiveState/cli/internal/runbits/commits_runbit" - "github.com/ActiveState/cli/pkg/platform/api/inventory/inventory_models" + hsInventoryModel "github.com/ActiveState/cli/pkg/platform/api/hasura_inventory/model" "github.com/ActiveState/cli/pkg/platform/api/vulnerabilities/request" "github.com/ActiveState/cli/pkg/platform/authentication" "github.com/ActiveState/cli/pkg/platform/model" "github.com/ActiveState/cli/pkg/project" - "github.com/go-openapi/strfmt" ) // InfoRunParams tracks the info required for running Info. @@ -90,16 +89,18 @@ func (i *Info) Run(params InfoRunParams, nstype model.NamespaceType) error { } pkg := packages[0] - ingredientVersion := pkg.LatestVersion + ingredientVersion := pkg.Versions[0] // latest version if params.Package.Version != "" { - ingredientVersion, err = specificIngredientVersion(pkg.Ingredient.IngredientID, params.Package.Version, i.auth) - if err != nil { - return locale.WrapExternalError(err, "info_err_version_not_found", "Could not find version {{.V0}} for package {{.V1}}", params.Package.Version, params.Package.Name) + for _, v := range pkg.Versions { + if v.Version == params.Package.Version { + ingredientVersion = v + break + } } } - authors, err := model.FetchAuthors(pkg.Ingredient.IngredientID, ingredientVersion.IngredientVersionID, i.auth) + authors, err := model.FetchAuthors(&pkg.IngredientID, &ingredientVersion.IngredientVersionID, i.auth) if err != nil { return locale.WrapError(err, "package_err_cannot_obtain_authors_info", "Cannot obtain authors info") } @@ -109,8 +110,8 @@ func (i *Info) Run(params InfoRunParams, nstype model.NamespaceType) error { vulnerabilityIngredients := make([]*request.Ingredient, len(pkg.Versions)) for i, p := range pkg.Versions { vulnerabilityIngredients[i] = &request.Ingredient{ - Name: *pkg.Ingredient.Name, - Namespace: *pkg.Ingredient.PrimaryNamespace, + Name: pkg.Name, + Namespace: pkg.Namespace.Namespace, Version: p.Version, } } @@ -122,8 +123,8 @@ func (i *Info) Run(params InfoRunParams, nstype model.NamespaceType) error { } i.out.Print(&infoOutput{i.out, structuredOutput{ - pkg.Ingredient, - ingredientVersion, + pkg.SearchIngredient, + &ingredientVersion, authors, pkg.Versions, vulns, @@ -132,21 +133,6 @@ func (i *Info) Run(params InfoRunParams, nstype model.NamespaceType) error { return nil } -func specificIngredientVersion(ingredientID *strfmt.UUID, version string, auth *authentication.Auth) (*inventory_models.IngredientVersion, error) { - ingredientVersions, err := model.FetchIngredientVersions(ingredientID, auth) - if err != nil { - return nil, locale.WrapError(err, "info_err_cannot_obtain_version", "Could not retrieve ingredient version information") - } - - for _, iv := range ingredientVersions { - if iv.Version != nil && *iv.Version == version { - return iv, nil - } - } - - return nil, locale.NewInputError("err_no_ingredient_version_found", "No ingredient version found") -} - // PkgDetailsTable describes package details. type PkgDetailsTable struct { Description string `opts:"omitEmpty" locale:"package_description,[HEADING]Description[/RESET]" json:"description"` @@ -171,25 +157,13 @@ func newInfoResult(so structuredOutput) *infoResult { PkgDetailsTable: &PkgDetailsTable{}, } - if so.Ingredient.Name != nil { - res.name = *so.Ingredient.Name - } - - if so.IngredientVersion.Version != nil { - res.version = *so.IngredientVersion.Version - } - - if so.Ingredient.Description != nil { - res.PkgDetailsTable.Description = *so.Ingredient.Description - } - - if so.Ingredient.Website != "" { - res.PkgDetailsTable.Website = so.Ingredient.Website.String() - } - - if so.IngredientVersion.LicenseExpression != nil { - res.PkgDetailsTable.License = fmt.Sprintf("[CYAN]%s[/RESET]", *so.IngredientVersion.LicenseExpression) + res.name = so.Ingredient.Name + res.version = so.IngredientVersion.Version + res.PkgDetailsTable.Description = so.Ingredient.Description + if so.Ingredient.Website != nil { + res.PkgDetailsTable.Website = *so.Ingredient.Website } + res.PkgDetailsTable.License = fmt.Sprintf("[CYAN]%s[/RESET]", so.IngredientVersion.LicenseExpression) for _, version := range so.Versions { res.plainVersions = append(res.plainVersions, version.Version) @@ -286,11 +260,11 @@ func newInfoResult(so structuredOutput) *infoResult { } type structuredOutput struct { - Ingredient *inventory_models.Ingredient `json:"ingredient"` - IngredientVersion *inventory_models.IngredientVersion `json:"ingredient_version"` - Authors model.Authors `json:"authors"` - Versions []*inventory_models.SearchIngredientsResponseVersion `json:"versions"` - Vulnerabilities []*model.VulnerabilityIngredient `json:"vulnerabilities,omitempty"` + Ingredient *hsInventoryModel.SearchIngredient `json:"ingredient"` + IngredientVersion *hsInventoryModel.IngredientVersion `json:"ingredient_version"` + Authors model.Authors `json:"authors"` + Versions []hsInventoryModel.IngredientVersion `json:"versions"` + Vulnerabilities []*model.VulnerabilityIngredient `json:"vulnerabilities,omitempty"` } type infoOutput struct { diff --git a/internal/runners/packages/search.go b/internal/runners/packages/search.go index ed6ad2e4f0..540068b492 100644 --- a/internal/runners/packages/search.go +++ b/internal/runners/packages/search.go @@ -142,8 +142,8 @@ func (s *Search) getVulns(packages []*model.IngredientAndVersion) ([]*model.Vuln var ingredients []*request.Ingredient for _, pkg := range packages { ingredients = append(ingredients, &request.Ingredient{ - Name: *pkg.Ingredient.Name, - Namespace: *pkg.Ingredient.PrimaryNamespace, + Name: pkg.Name, + Namespace: pkg.Namespace.Namespace, Version: pkg.Version, }) } diff --git a/internal/runners/packages/searchresult.go b/internal/runners/packages/searchresult.go index c60366409f..56a4e8b4cd 100644 --- a/internal/runners/packages/searchresult.go +++ b/internal/runners/packages/searchresult.go @@ -28,10 +28,10 @@ func createSearchResults(packages []*model.IngredientAndVersion, vulns []*model. var packageNames []string for _, pkg := range packages { result := &searchResult{} - result.Name = ptr.From(pkg.Ingredient.Name, "") - result.Description = ptr.From(pkg.Ingredient.Description, "") - result.Website = pkg.Ingredient.Website.String() - result.License = ptr.From(pkg.LatestVersion.LicenseExpression, "") + result.Name = pkg.Name + result.Description = pkg.Description + result.Website = ptr.From(pkg.Website, "") + result.License = pkg.Versions[0].LicenseExpression // latest version var versions []string for _, v := range pkg.Versions { @@ -44,8 +44,8 @@ func createSearchResults(packages []*model.IngredientAndVersion, vulns []*model. var ingredientVulns *model.VulnerabilityIngredient for _, v := range vulns { - if strings.EqualFold(v.Name, *pkg.Ingredient.Name) && - strings.EqualFold(v.PrimaryNamespace, *pkg.Ingredient.PrimaryNamespace) && + if strings.EqualFold(v.Name, pkg.Name) && + strings.EqualFold(v.PrimaryNamespace, pkg.Namespace.Namespace) && strings.EqualFold(v.Version, pkg.Version) { ingredientVulns = v break @@ -56,7 +56,7 @@ func createSearchResults(packages []*model.IngredientAndVersion, vulns []*model. result.Vulnerabilities = ingredientVulns.Vulnerabilities.Count() } - packageNames = append(packageNames, *pkg.Ingredient.Name) + packageNames = append(packageNames, pkg.Name) results = append(results, result) } diff --git a/internal/runners/publish/publish.go b/internal/runners/publish/publish.go index d75084e25d..b831913fac 100644 --- a/internal/runners/publish/publish.go +++ b/internal/runners/publish/publish.go @@ -173,9 +173,25 @@ func (r *Runner) Run(params *Params) error { if err != nil && !errors.As(err, &errSearch404) { // 404 means either the ingredient or the namespace was not found, which is fine return locale.WrapError(err, "err_uploadingredient_search", "Could not search for ingredient") } + if len(ingredients) > 0 { - i := ingredients[0].LatestVersion - ingredient = &ParentIngredient{*i.IngredientID, *i.IngredientVersionID, *i.Version, i.Dependencies} + i := ingredients[0] + + // Attempt to find the ingredient's dependencies. + var dependencies []inventory_models.Dependency + ingredientVersions, err := model.FetchIngredientVersions(&i.IngredientID, r.auth) + if err != nil { + return locale.WrapError(err, "err_uploadingredient_fetch_versions", "Could not retrieve ingredient version information") + } + for _, iv := range ingredientVersions { + if iv.Version != nil && *iv.Version == i.Version { + dependencies = iv.Dependencies + break + } + } + + ingredientVersionID := i.Versions[0].IngredientVersionID // latest version + ingredient = &ParentIngredient{i.IngredientID, ingredientVersionID, i.Version, dependencies} if params.Version == "" { isRevision = true } diff --git a/pkg/platform/api/hasura_inventory/model/inventory.go b/pkg/platform/api/hasura_inventory/model/inventory.go index 689cc89ddf..265be023d8 100644 --- a/pkg/platform/api/hasura_inventory/model/inventory.go +++ b/pkg/platform/api/hasura_inventory/model/inventory.go @@ -11,3 +11,27 @@ type LastIngredientRevisionTime struct { type LatestRevisionResponse struct { RevisionTimes []LastIngredientRevisionTime `json:"last_ingredient_revision_time"` } + +type Namespace struct { + Namespace string `json:"namespace"` +} + +type IngredientVersion struct { + Version string `json:"version"` + IngredientVersionID strfmt.UUID `json:"ingredient_version_id"` + LicenseExpression string `json:"license_expression"` +} + +type SearchIngredient struct { + Name string `json:"name"` + NormalizedName string `json:"normalized_name"` + Namespace Namespace `json:"namespace"` + IngredientID strfmt.UUID `json:"ingredient_id"` + Description string `json:"description"` + Website *string `json:"website"` + Versions []IngredientVersion `json:"versions"` +} + +type SearchIngredientsResponse struct { + SearchIngredients []SearchIngredient `json:"search_ingredients"` +} diff --git a/pkg/platform/api/hasura_inventory/request/search_ingredients.go b/pkg/platform/api/hasura_inventory/request/search_ingredients.go new file mode 100644 index 0000000000..74c2efe65f --- /dev/null +++ b/pkg/platform/api/hasura_inventory/request/search_ingredients.go @@ -0,0 +1,53 @@ +package request + +import ( + "fmt" + "strings" + "time" +) + +func SearchIngredients(namespaces []string, name string, exact bool, time *time.Time, limit, offset int) *searchIngredients { + return &searchIngredients{map[string]interface{}{ + "namespaces": fmt.Sprintf("{%s}", strings.Join(namespaces, ",")), // API requires enclosure in {} + "name": name, + "exact": exact, + "time": time, + "limit": limit, + "offset": offset, + }} +} + +type searchIngredients struct { + vars map[string]interface{} +} + +func (s *searchIngredients) Query() string { + return ` +query ($namespaces: _non_empty_citext, $name: non_empty_citext, $exact: Boolean!, $time: timestamptz, $limit: Int!, $offset: Int!) { + search_ingredients( + args: {namespaces: $namespaces, name_: $name, exact: $exact, timestamp_: $time, limit_: $limit, offset_: $offset} + ) { + name + normalized_name + namespace { + namespace + } + ingredient_id + description + website + versions(order_by:{sortable_version:desc}) { + version + ingredient_version_id + license_expression + } + } +}` +} + +func (s *searchIngredients) Vars() (map[string]interface{}, error) { + return s.vars, nil +} + +func (s *searchIngredients) SetOffset(offset int) { + s.vars["offset"] = offset +} diff --git a/pkg/platform/model/inventory.go b/pkg/platform/model/inventory.go index 5e976b9c68..bf435908fc 100644 --- a/pkg/platform/model/inventory.go +++ b/pkg/platform/model/inventory.go @@ -48,9 +48,8 @@ func (e ErrNoMatchingPlatform) Error() string { type ErrSearch404 struct{ *locale.LocalizedError } -// IngredientAndVersion is a sane version of whatever the hell it is go-swagger thinks it's doing type IngredientAndVersion struct { - *inventory_models.SearchIngredientsResponseItem + *hsInventoryModel.SearchIngredient Version string } @@ -104,9 +103,7 @@ func SearchIngredientsStrict(namespace string, name string, caseSensitive bool, ingredients := results[:0] for _, ing := range results { var ingName string - if ing.Ingredient.Name != nil { - ingName = *ing.Ingredient.Name - } + ingName = ing.Name if !caseSensitive { ingName = strings.ToLower(ingName) } @@ -146,14 +143,11 @@ func processLatestIngredients(ingredients []*IngredientAndVersion) []*Ingredient seen := make(map[string]bool) var processedIngredients []*IngredientAndVersion for _, ing := range ingredients { - if ing.Ingredient.Name == nil { - continue - } - if seen[*ing.Ingredient.Name] { + if seen[ing.Name] { continue } processedIngredients = append(processedIngredients, ing) - seen[*ing.Ingredient.Name] = true + seen[ing.Name] = true } return processedIngredients } @@ -190,62 +184,39 @@ type ErrTooManyMatches struct { } func searchIngredientsNamespace(ns string, name string, includeVersions bool, exactOnly bool, ts *time.Time, auth *authentication.Auth) ([]*IngredientAndVersion, error) { - limit := int64(100) - offset := int64(0) - - client := inventory.Get(auth) - - params := inventory_operations.NewSearchIngredientsParams() - params.SetQ(&name) - if exactOnly { - params.SetExactOnly(&exactOnly) - } - if ns != "" { - params.SetNamespaces(&ns) - } - params.SetLimit(&limit) - params.SetHTTPClient(api.NewHTTPClient()) + limit := 100 + offset := 0 - if ts != nil { - dt := strfmt.DateTime(*ts) - params.SetStateAt(&dt) - } + client := hsInventory.New(auth) + request := hsInventoryRequest.SearchIngredients([]string{ns}, name, exactOnly, ts, limit, offset) var ingredients []*IngredientAndVersion - var entries []*inventory_models.SearchIngredientsResponseItem - for offset == 0 || len(entries) == int(limit) { + for { + response := hsInventoryModel.SearchIngredientsResponse{} if offset > (limit * 10) { // at most we will get 10 pages of ingredients (that's ONE THOUSAND ingredients) // Guard against queries that match TOO MANY ingredients return nil, &ErrTooManyMatches{locale.NewInputError("err_searchingredient_toomany", "", name), name} } - params.SetOffset(&offset) - results, err := client.SearchIngredients(params, auth.ClientAuth()) + request.SetOffset(offset) + err := client.Run(request, &response) if err != nil { - if sidErr, ok := err.(*inventory_operations.SearchIngredientsDefault); ok { - errv := locale.NewError(*sidErr.Payload.Message) - if sidErr.Code() == 404 { - return nil, &ErrSearch404{errv} - } - return nil, errv - } return nil, errs.Wrap(err, "SearchIngredients failed") } - entries = results.Payload.Ingredients - for _, res := range entries { - if res.Ingredient.PrimaryNamespace == nil { - continue // Shouldn't ever happen, but this at least guards around nil pointer panics - } + for _, res := range response.SearchIngredients { if includeVersions { for _, v := range res.Versions { - ingredients = append(ingredients, &IngredientAndVersion{res, v.Version}) + ingredients = append(ingredients, &IngredientAndVersion{&res, v.Version}) } } else { - ingredients = append(ingredients, &IngredientAndVersion{res, ""}) + ingredients = append(ingredients, &IngredientAndVersion{&res, res.Versions[0].Version}) } } + if len(response.SearchIngredients) < limit { + break + } offset += limit } diff --git a/pkg/platform/model/vcs.go b/pkg/platform/model/vcs.go index 845b576a95..a6c75d822a 100644 --- a/pkg/platform/model/vcs.go +++ b/pkg/platform/model/vcs.go @@ -226,7 +226,7 @@ func FilterSupportedIngredients(supported []model.SupportedLanguage, ingredients var res []*IngredientAndVersion for _, i := range ingredients { - language := LanguageFromNamespace(*i.Ingredient.PrimaryNamespace) + language := LanguageFromNamespace(i.Namespace.Namespace) for _, l := range supported { if l.Name != language { From 613fb5c28639fd7a566b8f2f593d9534ba4c679d Mon Sep 17 00:00:00 2001 From: mitchell Date: Thu, 5 Sep 2024 11:26:36 -0400 Subject: [PATCH 2/6] The new hasura inventory service search endpoint requires a timestamp. By always using the latest inventory timestamp, we don't need to pass around or specify timestamps anymore. This eliminates the `--ts now` flag for package operations. --- cmd/state/internal/cmdtree/packages.go | 15 ------ internal/locale/locales/en-us.yaml | 4 +- internal/runbits/commits_runbit/time.go | 29 ------------ .../runtime/requirements/requirements.go | 47 +++++++++---------- internal/runners/initialize/init.go | 2 +- internal/runners/languages/install.go | 2 +- internal/runners/packages/info.go | 13 ++--- internal/runners/packages/install.go | 14 ++---- internal/runners/packages/search.go | 12 ++--- internal/runners/packages/uninstall.go | 9 +--- internal/runners/platforms/add.go | 1 - internal/runners/platforms/remove.go | 1 - internal/runners/publish/publish.go | 2 +- .../request/search_ingredients.go | 4 +- pkg/platform/model/inventory.go | 23 +++++---- test/integration/package_int_test.go | 2 +- test/integration/publish_int_test.go | 2 +- test/integration/runtime_int_test.go | 4 +- 18 files changed, 55 insertions(+), 131 deletions(-) diff --git a/cmd/state/internal/cmdtree/packages.go b/cmd/state/internal/cmdtree/packages.go index 39b66a1cb0..e1992b5788 100644 --- a/cmd/state/internal/cmdtree/packages.go +++ b/cmd/state/internal/cmdtree/packages.go @@ -60,11 +60,6 @@ func newInstallCommand(prime *primer.Values) *captain.Command { locale.T("package_install_cmd_description"), prime, []*captain.Flag{ - { - Name: "ts", - Description: locale.T("package_flag_ts_description"), - Value: ¶ms.Timestamp, - }, { Name: "revision", Shorthand: "r", @@ -181,11 +176,6 @@ func newSearchCommand(prime *primer.Values) *captain.Command { Description: locale.T("package_search_flag_exact-term_description"), Value: ¶ms.ExactTerm, }, - { - Name: "ts", - Description: locale.T("package_flag_ts_description"), - Value: ¶ms.Timestamp, - }, }, []*captain.Argument{ { @@ -217,11 +207,6 @@ func newInfoCommand(prime *primer.Values) *captain.Command { Description: locale.T("package_info_flag_language_description"), Value: ¶ms.Language, }, - { - Name: "ts", - Description: locale.T("package_flag_ts_description"), - Value: ¶ms.Timestamp, - }, }, []*captain.Argument{ { diff --git a/internal/locale/locales/en-us.yaml b/internal/locale/locales/en-us.yaml index 62aa856235..acbe597f77 100644 --- a/internal/locale/locales/en-us.yaml +++ b/internal/locale/locales/en-us.yaml @@ -594,8 +594,6 @@ namespace_list_flag_project_description: other: The namespace packages should be listed from package_search_flag_ns_description: other: The namespace to search within. -package_flag_ts_description: - other: The timestamp at which you want to query. Can be either 'now' or RFC3339 formatted timestamp. package_flag_rev_description: other: The revision you want to use. This ensures you get this exact revision and nothing else. package_search_flag_language_description: @@ -1455,7 +1453,7 @@ uploadingredient_success: Revision: [ACTIONABLE]{{.V3}}[/RESET] Timestamp: [ACTIONABLE]{{.V4}}[/RESET] - You can install this package by running `[ACTIONABLE]state install {{.V1}}/{{.V0}} --ts now`[/RESET]. + You can install this package by running `[ACTIONABLE]state install {{.V1}}/{{.V0}}`[/RESET]. err_runtime_cache_invalid: other: Your runtime needs to be updated. Please run '[ACTIONABLE]state refresh[/RESET]'. err_buildscript_not_exist: diff --git a/internal/runbits/commits_runbit/time.go b/internal/runbits/commits_runbit/time.go index 478cfc544d..698bfe095c 100644 --- a/internal/runbits/commits_runbit/time.go +++ b/internal/runbits/commits_runbit/time.go @@ -5,10 +5,8 @@ import ( "github.com/ActiveState/cli/internal/captain" "github.com/ActiveState/cli/internal/errs" - "github.com/ActiveState/cli/pkg/localcommit" "github.com/ActiveState/cli/pkg/platform/authentication" "github.com/ActiveState/cli/pkg/platform/model" - "github.com/ActiveState/cli/pkg/project" ) // ExpandTime returns a timestamp based on the given "--ts" value. @@ -36,30 +34,3 @@ func ExpandTime(ts *captain.TimeValue, auth *authentication.Auth) (time.Time, er return latest, nil } - -// ExpandTimeForProject is the same as ExpandTime except that it ensures the returned time is either the same or -// later than that of the most recent commit. -func ExpandTimeForProject(ts *captain.TimeValue, auth *authentication.Auth, proj *project.Project) (time.Time, error) { - timestamp, err := ExpandTime(ts, auth) - if err != nil { - return time.Time{}, errs.Wrap(err, "Unable to expand time") - } - - if proj != nil { - commitID, err := localcommit.Get(proj.Dir()) - if err != nil { - return time.Time{}, errs.Wrap(err, "Unable to get commit ID") - } - - atTime, err := model.FetchTimeStampForCommit(commitID, auth) - if err != nil { - return time.Time{}, errs.Wrap(err, "Unable to get commit time") - } - - if atTime.After(timestamp) { - return *atTime, nil - } - } - - return timestamp, nil -} diff --git a/internal/runbits/runtime/requirements/requirements.go b/internal/runbits/runtime/requirements/requirements.go index cf7cd8f89a..f21a015251 100644 --- a/internal/runbits/runtime/requirements/requirements.go +++ b/internal/runbits/runtime/requirements/requirements.go @@ -6,7 +6,6 @@ import ( "regexp" "strconv" "strings" - "time" "github.com/ActiveState/cli/internal/analytics" anaConsts "github.com/ActiveState/cli/internal/analytics/constants" @@ -130,7 +129,7 @@ type Requirement struct { // ExecuteRequirementOperation executes the operation on the requirement // This has become quite unwieldy, and is ripe for a refactor - https://activestatef.atlassian.net/browse/DX-1897 -func (r *RequirementOperation) ExecuteRequirementOperation(ts *time.Time, requirements ...*Requirement) (rerr error) { +func (r *RequirementOperation) ExecuteRequirementOperation(requirements ...*Requirement) (rerr error) { defer r.rationalizeError(&rerr) if len(requirements) == 0 { @@ -154,7 +153,7 @@ func (r *RequirementOperation) ExecuteRequirementOperation(ts *time.Time, requir } out.Notice(locale.Tr("operating_message", r.Project.NamespaceString(), r.Project.Dir())) - if err := r.resolveNamespaces(ts, requirements...); err != nil { + if err := r.resolveNamespaces(requirements...); err != nil { return errs.Wrap(err, "Could not resolve namespaces") } @@ -200,7 +199,7 @@ func (r *RequirementOperation) ExecuteRequirementOperation(ts *time.Time, requir } bp := bpModel.NewBuildPlannerModel(r.Auth) - script, err := r.prepareBuildScript(bp, parentCommitID, requirements, ts) + script, err := r.prepareBuildScript(bp, parentCommitID, requirements) if err != nil { return errs.Wrap(err, "Could not prepare build script") } @@ -281,25 +280,21 @@ func (r *RequirementOperation) ExecuteRequirementOperation(ts *time.Time, requir return nil } -func (r *RequirementOperation) prepareBuildScript(bp *bpModel.BuildPlanner, parentCommit strfmt.UUID, requirements []*Requirement, ts *time.Time) (*buildscript.BuildScript, error) { +func (r *RequirementOperation) prepareBuildScript(bp *bpModel.BuildPlanner, parentCommit strfmt.UUID, requirements []*Requirement) (*buildscript.BuildScript, error) { script, err := bp.GetBuildScript(string(parentCommit)) if err != nil { return nil, errs.Wrap(err, "Failed to get build expression") } - if ts != nil { - script.SetAtTime(*ts) - } else { - // If no atTime was provided then we need to ensure that the atTime in the script is updated to use - // the most recent, which is either the current value or the platform latest. - latest, err := model.FetchLatestTimeStamp(r.Auth) - if err != nil { - return nil, errs.Wrap(err, "Unable to fetch latest Platform timestamp") - } - atTime := script.AtTime() - if atTime == nil || latest.After(*atTime) { - script.SetAtTime(latest) - } + // Ensure that the atTime in the script is updated to use + // the most recent, which is either the current value or the platform latest. + latest, err := model.FetchLatestTimeStamp(r.Auth) + if err != nil { + return nil, errs.Wrap(err, "Unable to fetch latest Platform timestamp") + } + atTime := script.AtTime() + if atTime == nil || latest.After(*atTime) { + script.SetAtTime(latest) } for _, req := range requirements { @@ -334,9 +329,9 @@ func (e ResolveNamespaceError) Error() string { return "unable to resolve namespace" } -func (r *RequirementOperation) resolveNamespaces(ts *time.Time, requirements ...*Requirement) error { +func (r *RequirementOperation) resolveNamespaces(requirements ...*Requirement) error { for _, requirement := range requirements { - if err := r.resolveNamespace(ts, requirement); err != nil { + if err := r.resolveNamespace(requirement); err != nil { if err != errNoLanguage { err = errs.Pack(err, &ResolveNamespaceError{requirement.Name}) } @@ -346,7 +341,7 @@ func (r *RequirementOperation) resolveNamespaces(ts *time.Time, requirements ... return nil } -func (r *RequirementOperation) resolveNamespace(ts *time.Time, requirement *Requirement) error { +func (r *RequirementOperation) resolveNamespace(requirement *Requirement) error { requirement.langName = "undetermined" if requirement.NamespaceType != nil { @@ -386,7 +381,7 @@ func (r *RequirementOperation) resolveNamespace(ts *time.Time, requirement *Requ var nsv model.Namespace var supportedLang *medmodel.SupportedLanguage - requirement.Name, nsv, supportedLang, err = resolvePkgAndNamespace(r.Prompt, requirement.Name, *requirement.NamespaceType, supported, ts, r.Auth) + requirement.Name, nsv, supportedLang, err = resolvePkgAndNamespace(r.Prompt, requirement.Name, *requirement.NamespaceType, supported, r.Auth) if err != nil { return errs.Wrap(err, "Could not resolve pkg and namespace") } @@ -441,7 +436,7 @@ func (r *RequirementOperation) validatePackage(requirement *Requirement) error { multilog.Error("Failed to normalize '%s': %v", requirement.Name, err) } - packages, err := model.SearchIngredientsStrict(requirement.Namespace.String(), normalized, false, false, nil, r.Auth) // ideally case-sensitive would be true (PB-4371) + packages, err := model.SearchIngredientsStrict(requirement.Namespace.String(), normalized, false, false, r.Auth) // ideally case-sensitive would be true (PB-4371) if err != nil { return locale.WrapError(err, "package_err_cannot_obtain_search_results") } @@ -591,11 +586,11 @@ func supportedLanguageByName(supported []medmodel.SupportedLanguage, langName st return funk.Find(supported, func(l medmodel.SupportedLanguage) bool { return l.Name == langName }).(medmodel.SupportedLanguage) } -func resolvePkgAndNamespace(prompt prompt.Prompter, packageName string, nsType model.NamespaceType, supported []medmodel.SupportedLanguage, ts *time.Time, auth *authentication.Auth) (string, model.Namespace, *medmodel.SupportedLanguage, error) { +func resolvePkgAndNamespace(prompt prompt.Prompter, packageName string, nsType model.NamespaceType, supported []medmodel.SupportedLanguage, auth *authentication.Auth) (string, model.Namespace, *medmodel.SupportedLanguage, error) { ns := model.NewBlankNamespace() // Find ingredients that match the input query - ingredients, err := model.SearchIngredientsStrict("", packageName, false, false, ts, auth) + ingredients, err := model.SearchIngredientsStrict("", packageName, false, false, auth) if err != nil { return "", ns, nil, locale.WrapError(err, "err_pkgop_search_err", "Failed to check for ingredients.") } @@ -644,7 +639,7 @@ func resolvePkgAndNamespace(prompt prompt.Prompter, packageName string, nsType m } func getSuggestions(ns model.Namespace, name string, auth *authentication.Auth) ([]string, error) { - results, err := model.SearchIngredients(ns.String(), name, false, nil, auth) + results, err := model.SearchIngredients(ns.String(), name, false, auth) if err != nil { return []string{}, locale.WrapError(err, "package_ingredient_err_search", "Failed to resolve ingredient named: {{.V0}}", name) } diff --git a/internal/runners/initialize/init.go b/internal/runners/initialize/init.go index 1951a206f8..1b8aeab49d 100644 --- a/internal/runners/initialize/init.go +++ b/internal/runners/initialize/init.go @@ -334,7 +334,7 @@ func (r *Initialize) Run(params *RunParams) (rerr error) { } func getKnownVersions(lang language.Language, auth *authentication.Auth) ([]string, error) { - pkgs, err := model.SearchIngredientsStrict(model.NewNamespaceLanguage().String(), lang.Requirement(), false, true, nil, auth) + pkgs, err := model.SearchIngredientsStrict(model.NewNamespaceLanguage().String(), lang.Requirement(), false, true, auth) if err != nil { return nil, errs.Wrap(err, "Failed to fetch Platform languages") } diff --git a/internal/runners/languages/install.go b/internal/runners/languages/install.go index d2f208dae6..d5f73bd9da 100644 --- a/internal/runners/languages/install.go +++ b/internal/runners/languages/install.go @@ -54,7 +54,7 @@ func (u *Update) Run(params *UpdateParams) error { } op := requirements.NewRequirementOperation(u.prime) - return op.ExecuteRequirementOperation(nil, &requirements.Requirement{ + return op.ExecuteRequirementOperation(&requirements.Requirement{ Name: lang.Name, Version: lang.Version, NamespaceType: &model.NamespaceLanguage, diff --git a/internal/runners/packages/info.go b/internal/runners/packages/info.go index cb0cbc88a8..f0917fdabf 100644 --- a/internal/runners/packages/info.go +++ b/internal/runners/packages/info.go @@ -12,7 +12,6 @@ import ( "github.com/ActiveState/cli/internal/multilog" "github.com/ActiveState/cli/internal/output" "github.com/ActiveState/cli/internal/rtutils/ptr" - "github.com/ActiveState/cli/internal/runbits/commits_runbit" hsInventoryModel "github.com/ActiveState/cli/pkg/platform/api/hasura_inventory/model" "github.com/ActiveState/cli/pkg/platform/api/vulnerabilities/request" "github.com/ActiveState/cli/pkg/platform/authentication" @@ -22,9 +21,8 @@ import ( // InfoRunParams tracks the info required for running Info. type InfoRunParams struct { - Package captain.PackageValue - Timestamp captain.TimeValue - Language string + Package captain.PackageValue + Language string } // Info manages the information execution context. @@ -70,12 +68,7 @@ func (i *Info) Run(params InfoRunParams, nstype model.NamespaceType) error { normalized = params.Package.Name } - ts, err := commits_runbit.ExpandTimeForProject(¶ms.Timestamp, i.auth, i.proj) - if err != nil { - return errs.Wrap(err, "Unable to get timestamp from params") - } - - packages, err := model.SearchIngredientsStrict(ns.String(), normalized, false, false, &ts, i.auth) // ideally case-sensitive would be true (PB-4371) + packages, err := model.SearchIngredientsStrict(ns.String(), normalized, false, false, i.auth) // ideally case-sensitive would be true (PB-4371) if err != nil { return locale.WrapError(err, "package_err_cannot_obtain_search_results") } diff --git a/internal/runners/packages/install.go b/internal/runners/packages/install.go index 30193f4cf8..3e3ed7a570 100644 --- a/internal/runners/packages/install.go +++ b/internal/runners/packages/install.go @@ -2,10 +2,8 @@ package packages import ( "github.com/ActiveState/cli/internal/captain" - "github.com/ActiveState/cli/internal/errs" "github.com/ActiveState/cli/internal/logging" "github.com/ActiveState/cli/internal/rtutils/ptr" - "github.com/ActiveState/cli/internal/runbits/commits_runbit" "github.com/ActiveState/cli/internal/runbits/runtime/requirements" "github.com/ActiveState/cli/pkg/platform/api/buildplanner/types" "github.com/ActiveState/cli/pkg/platform/model" @@ -13,9 +11,8 @@ import ( // InstallRunParams tracks the info required for running Install. type InstallRunParams struct { - Packages captain.PackagesValue - Timestamp captain.TimeValue - Revision captain.IntValue + Packages captain.PackagesValue + Revision captain.IntValue } // Install manages the installing execution context. @@ -52,10 +49,5 @@ func (a *Install) Run(params InstallRunParams, nsType model.NamespaceType) (rerr reqs = append(reqs, req) } - ts, err := commits_runbit.ExpandTimeForProject(¶ms.Timestamp, a.prime.Auth(), a.prime.Project()) - if err != nil { - return errs.Wrap(err, "Unable to get timestamp from params") - } - - return requirements.NewRequirementOperation(a.prime).ExecuteRequirementOperation(&ts, reqs...) + return requirements.NewRequirementOperation(a.prime).ExecuteRequirementOperation(reqs...) } diff --git a/internal/runners/packages/search.go b/internal/runners/packages/search.go index 540068b492..809bfb764a 100644 --- a/internal/runners/packages/search.go +++ b/internal/runners/packages/search.go @@ -8,7 +8,6 @@ import ( "github.com/ActiveState/cli/internal/locale" "github.com/ActiveState/cli/internal/logging" "github.com/ActiveState/cli/internal/output" - "github.com/ActiveState/cli/internal/runbits/commits_runbit" "github.com/ActiveState/cli/pkg/localcommit" "github.com/ActiveState/cli/pkg/platform/api/vulnerabilities/request" "github.com/ActiveState/cli/pkg/platform/authentication" @@ -22,7 +21,6 @@ type SearchRunParams struct { Language string ExactTerm bool Ingredient captain.PackageValueNoVersion - Timestamp captain.TimeValue } // Search manages the searching execution context. @@ -59,16 +57,12 @@ func (s *Search) Run(params SearchRunParams, nstype model.NamespaceType) error { ns = model.NewRawNamespace(params.Ingredient.Namespace) } - ts, err := commits_runbit.ExpandTimeForProject(¶ms.Timestamp, s.auth, s.proj) - if err != nil { - return errs.Wrap(err, "Unable to get timestamp from params") - } - var packages []*model.IngredientAndVersion + var err error if params.ExactTerm { - packages, err = model.SearchIngredientsLatestStrict(ns.String(), params.Ingredient.Name, true, true, &ts, s.auth) + packages, err = model.SearchIngredientsLatestStrict(ns.String(), params.Ingredient.Name, true, true, s.auth) } else { - packages, err = model.SearchIngredientsLatest(ns.String(), params.Ingredient.Name, true, &ts, s.auth) + packages, err = model.SearchIngredientsLatest(ns.String(), params.Ingredient.Name, true, s.auth) } if err != nil { return locale.WrapError(err, "package_err_cannot_obtain_search_results") diff --git a/internal/runners/packages/uninstall.go b/internal/runners/packages/uninstall.go index a9008261dd..4cd7571ff1 100644 --- a/internal/runners/packages/uninstall.go +++ b/internal/runners/packages/uninstall.go @@ -2,10 +2,8 @@ package packages import ( "github.com/ActiveState/cli/internal/captain" - "github.com/ActiveState/cli/internal/errs" "github.com/ActiveState/cli/internal/logging" "github.com/ActiveState/cli/internal/rtutils/ptr" - "github.com/ActiveState/cli/internal/runbits/commits_runbit" "github.com/ActiveState/cli/internal/runbits/rationalize" "github.com/ActiveState/cli/internal/runbits/runtime/requirements" "github.com/ActiveState/cli/pkg/platform/api/buildplanner/types" @@ -51,10 +49,5 @@ func (u *Uninstall) Run(params UninstallRunParams, nsType model.NamespaceType) ( reqs = append(reqs, req) } - ts, err := commits_runbit.ExpandTimeForProject(&captain.TimeValue{}, u.prime.Auth(), u.prime.Project()) - if err != nil { - return errs.Wrap(err, "Unable to get timestamp from params") - } - - return requirements.NewRequirementOperation(u.prime).ExecuteRequirementOperation(&ts, reqs...) + return requirements.NewRequirementOperation(u.prime).ExecuteRequirementOperation(reqs...) } diff --git a/internal/runners/platforms/add.go b/internal/runners/platforms/add.go index 767c569f2a..6d97f2952f 100644 --- a/internal/runners/platforms/add.go +++ b/internal/runners/platforms/add.go @@ -51,7 +51,6 @@ func (a *Add) Run(ps AddRunParams) error { } if err := requirements.NewRequirementOperation(a.prime).ExecuteRequirementOperation( - nil, &requirements.Requirement{ Name: params.name, Version: params.version, diff --git a/internal/runners/platforms/remove.go b/internal/runners/platforms/remove.go index 3690be8257..d85df219f8 100644 --- a/internal/runners/platforms/remove.go +++ b/internal/runners/platforms/remove.go @@ -41,7 +41,6 @@ func (r *Remove) Run(ps RemoveRunParams) error { } if err := requirements.NewRequirementOperation(r.prime).ExecuteRequirementOperation( - nil, &requirements.Requirement{ Name: params.name, Version: params.version, diff --git a/internal/runners/publish/publish.go b/internal/runners/publish/publish.go index b831913fac..f890af64ae 100644 --- a/internal/runners/publish/publish.go +++ b/internal/runners/publish/publish.go @@ -168,7 +168,7 @@ func (r *Runner) Run(params *Params) error { if ingredient == nil { // Attempt to find the existing ingredient, if we didn't already get it from the version specific call above - ingredients, err := model.SearchIngredientsStrict(reqVars.Namespace, reqVars.Name, true, false, &latestRevisionTime, r.auth) + ingredients, err := model.SearchIngredientsStrict(reqVars.Namespace, reqVars.Name, true, false, r.auth) var errSearch404 *model.ErrSearch404 if err != nil && !errors.As(err, &errSearch404) { // 404 means either the ingredient or the namespace was not found, which is fine return locale.WrapError(err, "err_uploadingredient_search", "Could not search for ingredient") diff --git a/pkg/platform/api/hasura_inventory/request/search_ingredients.go b/pkg/platform/api/hasura_inventory/request/search_ingredients.go index 74c2efe65f..4b91c747f5 100644 --- a/pkg/platform/api/hasura_inventory/request/search_ingredients.go +++ b/pkg/platform/api/hasura_inventory/request/search_ingredients.go @@ -6,7 +6,7 @@ import ( "time" ) -func SearchIngredients(namespaces []string, name string, exact bool, time *time.Time, limit, offset int) *searchIngredients { +func SearchIngredients(namespaces []string, name string, exact bool, time time.Time, limit, offset int) *searchIngredients { return &searchIngredients{map[string]interface{}{ "namespaces": fmt.Sprintf("{%s}", strings.Join(namespaces, ",")), // API requires enclosure in {} "name": name, @@ -23,7 +23,7 @@ type searchIngredients struct { func (s *searchIngredients) Query() string { return ` -query ($namespaces: _non_empty_citext, $name: non_empty_citext, $exact: Boolean!, $time: timestamptz, $limit: Int!, $offset: Int!) { +query ($namespaces: _non_empty_citext, $name: non_empty_citext, $exact: Boolean!, $time: timestamptz!, $limit: Int!, $offset: Int!) { search_ingredients( args: {namespaces: $namespaces, name_: $name, exact: $exact, timestamp_: $time, limit_: $limit, offset_: $offset} ) { diff --git a/pkg/platform/model/inventory.go b/pkg/platform/model/inventory.go index bf435908fc..1a274e823f 100644 --- a/pkg/platform/model/inventory.go +++ b/pkg/platform/model/inventory.go @@ -84,14 +84,14 @@ func GetIngredientByNameAndVersion(namespace string, name string, version string // SearchIngredients will return all ingredients+ingredientVersions that fuzzily // match the ingredient name. -func SearchIngredients(namespace string, name string, includeVersions bool, ts *time.Time, auth *authentication.Auth) ([]*IngredientAndVersion, error) { - return searchIngredientsNamespace(namespace, name, includeVersions, false, ts, auth) +func SearchIngredients(namespace string, name string, includeVersions bool, auth *authentication.Auth) ([]*IngredientAndVersion, error) { + return searchIngredientsNamespace(namespace, name, includeVersions, false, auth) } // SearchIngredientsStrict will return all ingredients+ingredientVersions that // strictly match the ingredient name. -func SearchIngredientsStrict(namespace string, name string, caseSensitive bool, includeVersions bool, ts *time.Time, auth *authentication.Auth) ([]*IngredientAndVersion, error) { - results, err := searchIngredientsNamespace(namespace, name, includeVersions, true, ts, auth) +func SearchIngredientsStrict(namespace string, name string, caseSensitive bool, includeVersions bool, auth *authentication.Auth) ([]*IngredientAndVersion, error) { + results, err := searchIngredientsNamespace(namespace, name, includeVersions, true, auth) if err != nil { return nil, err } @@ -118,8 +118,8 @@ func SearchIngredientsStrict(namespace string, name string, caseSensitive bool, // SearchIngredientsLatest will return all ingredients+ingredientVersions that // fuzzily match the ingredient name, but only the latest version of each // ingredient. -func SearchIngredientsLatest(namespace string, name string, includeVersions bool, ts *time.Time, auth *authentication.Auth) ([]*IngredientAndVersion, error) { - results, err := searchIngredientsNamespace(namespace, name, includeVersions, false, ts, auth) +func SearchIngredientsLatest(namespace string, name string, includeVersions bool, auth *authentication.Auth) ([]*IngredientAndVersion, error) { + results, err := searchIngredientsNamespace(namespace, name, includeVersions, false, auth) if err != nil { return nil, err } @@ -130,8 +130,8 @@ func SearchIngredientsLatest(namespace string, name string, includeVersions bool // SearchIngredientsLatestStrict will return all ingredients+ingredientVersions that // strictly match the ingredient name, but only the latest version of each // ingredient. -func SearchIngredientsLatestStrict(namespace string, name string, caseSensitive bool, includeVersions bool, ts *time.Time, auth *authentication.Auth) ([]*IngredientAndVersion, error) { - results, err := SearchIngredientsStrict(namespace, name, caseSensitive, includeVersions, ts, auth) +func SearchIngredientsLatestStrict(namespace string, name string, caseSensitive bool, includeVersions bool, auth *authentication.Auth) ([]*IngredientAndVersion, error) { + results, err := SearchIngredientsStrict(namespace, name, caseSensitive, includeVersions, auth) if err != nil { return nil, err } @@ -183,10 +183,15 @@ type ErrTooManyMatches struct { Query string } -func searchIngredientsNamespace(ns string, name string, includeVersions bool, exactOnly bool, ts *time.Time, auth *authentication.Auth) ([]*IngredientAndVersion, error) { +func searchIngredientsNamespace(ns string, name string, includeVersions bool, exactOnly bool, auth *authentication.Auth) ([]*IngredientAndVersion, error) { limit := 100 offset := 0 + ts, err := FetchLatestRevisionTimeStamp(auth) + if err != nil { + return nil, errs.Wrap(err, "Unable to fetch latest inventory timestamp") + } + client := hsInventory.New(auth) request := hsInventoryRequest.SearchIngredients([]string{ns}, name, exactOnly, ts, limit, offset) diff --git a/test/integration/package_int_test.go b/test/integration/package_int_test.go index d7243b1167..8b4cd658ec 100644 --- a/test/integration/package_int_test.go +++ b/test/integration/package_int_test.go @@ -710,7 +710,7 @@ func (suite *PackageIntegrationTestSuite) TestCVE_Indirect() { cp = ts.Spawn("config", "set", constants.SecurityPromptConfig, "true") cp.ExpectExitCode(0) - cp = ts.Spawn("install", "private/ActiveState-CLI-Testing/language/python/django_dep", "--ts=now") + cp = ts.Spawn("install", "private/ActiveState-CLI-Testing/language/python/django_dep") cp.ExpectRe(`Warning: Dependency has \d indirect known vulnerabilities`) cp.Expect("Do you want to continue") cp.SendLine("n") diff --git a/test/integration/publish_int_test.go b/test/integration/publish_int_test.go index 709cde17ab..a18cc66a69 100644 --- a/test/integration/publish_int_test.go +++ b/test/integration/publish_int_test.go @@ -457,7 +457,7 @@ authors: cp.Expect(version) cp.ExpectExitCode(inv.expect.exitCode) - cp = ts.Spawn("search", namespace+"/"+name, "--ts=now") + cp = ts.Spawn("search", namespace+"/"+name) cp.Expect(version) time.Sleep(time.Second) cp.Send("q") diff --git a/test/integration/runtime_int_test.go b/test/integration/runtime_int_test.go index 83ab55cfa4..6b59ad6f3d 100644 --- a/test/integration/runtime_int_test.go +++ b/test/integration/runtime_int_test.go @@ -148,7 +148,7 @@ func (suite *RuntimeIntegrationTestSuite) TestBuildInProgress() { ts.LoginAsPersistentUser() - // Publish a new ingredient revision, which, when coupled with `state install --ts now`, will + // Publish a new ingredient revision, which, when coupled with `state install`, will // force a build. // The ingredient is a tarball comprising: // 1. An empty, executable "configure" script (emulating autotools). @@ -167,7 +167,7 @@ func (suite *RuntimeIntegrationTestSuite) TestBuildInProgress() { ts.PrepareEmptyProject() - cp = ts.Spawn("install", "private/"+e2e.PersistentUsername+"/hello-world", "--ts", "now") + cp = ts.Spawn("install", "private/"+e2e.PersistentUsername+"/hello-world") cp.Expect("Build Log") cp.Expect("Building") cp.Expect("All dependencies have been installed and verified", e2e.RuntimeBuildSourcingTimeoutOpt) From 0180631c86e175aa8f35a418599025e02218923e Mon Sep 17 00:00:00 2001 From: mitchell Date: Thu, 5 Sep 2024 12:40:05 -0400 Subject: [PATCH 3/6] Updated test expectations with new endpoint results. Also allow partial search results (for suggestions) since the new endpoint returns more than the old one. --- .../runtime/requirements/requirements.go | 2 +- internal/runners/packages/search.go | 2 +- pkg/platform/model/inventory.go | 17 +++++-------- test/integration/package_int_test.go | 24 ++++++++++++------- 4 files changed, 23 insertions(+), 22 deletions(-) diff --git a/internal/runbits/runtime/requirements/requirements.go b/internal/runbits/runtime/requirements/requirements.go index f21a015251..1a7747b3d2 100644 --- a/internal/runbits/runtime/requirements/requirements.go +++ b/internal/runbits/runtime/requirements/requirements.go @@ -639,7 +639,7 @@ func resolvePkgAndNamespace(prompt prompt.Prompter, packageName string, nsType m } func getSuggestions(ns model.Namespace, name string, auth *authentication.Auth) ([]string, error) { - results, err := model.SearchIngredients(ns.String(), name, false, auth) + results, err := model.SearchIngredientsLatest(ns.String(), name, false, true, auth) if err != nil { return []string{}, locale.WrapError(err, "package_ingredient_err_search", "Failed to resolve ingredient named: {{.V0}}", name) } diff --git a/internal/runners/packages/search.go b/internal/runners/packages/search.go index 809bfb764a..9eb7ed9bee 100644 --- a/internal/runners/packages/search.go +++ b/internal/runners/packages/search.go @@ -62,7 +62,7 @@ func (s *Search) Run(params SearchRunParams, nstype model.NamespaceType) error { if params.ExactTerm { packages, err = model.SearchIngredientsLatestStrict(ns.String(), params.Ingredient.Name, true, true, s.auth) } else { - packages, err = model.SearchIngredientsLatest(ns.String(), params.Ingredient.Name, true, s.auth) + packages, err = model.SearchIngredientsLatest(ns.String(), params.Ingredient.Name, true, false, s.auth) } if err != nil { return locale.WrapError(err, "package_err_cannot_obtain_search_results") diff --git a/pkg/platform/model/inventory.go b/pkg/platform/model/inventory.go index 1a274e823f..0a74300578 100644 --- a/pkg/platform/model/inventory.go +++ b/pkg/platform/model/inventory.go @@ -82,16 +82,10 @@ func GetIngredientByNameAndVersion(namespace string, name string, version string return response.Payload, nil } -// SearchIngredients will return all ingredients+ingredientVersions that fuzzily -// match the ingredient name. -func SearchIngredients(namespace string, name string, includeVersions bool, auth *authentication.Auth) ([]*IngredientAndVersion, error) { - return searchIngredientsNamespace(namespace, name, includeVersions, false, auth) -} - // SearchIngredientsStrict will return all ingredients+ingredientVersions that // strictly match the ingredient name. func SearchIngredientsStrict(namespace string, name string, caseSensitive bool, includeVersions bool, auth *authentication.Auth) ([]*IngredientAndVersion, error) { - results, err := searchIngredientsNamespace(namespace, name, includeVersions, true, auth) + results, err := searchIngredientsNamespace(namespace, name, includeVersions, true, false, auth) if err != nil { return nil, err } @@ -118,8 +112,9 @@ func SearchIngredientsStrict(namespace string, name string, caseSensitive bool, // SearchIngredientsLatest will return all ingredients+ingredientVersions that // fuzzily match the ingredient name, but only the latest version of each // ingredient. -func SearchIngredientsLatest(namespace string, name string, includeVersions bool, auth *authentication.Auth) ([]*IngredientAndVersion, error) { - results, err := searchIngredientsNamespace(namespace, name, includeVersions, false, auth) +// Returns an error if there are too many matches unless `partial` is true. +func SearchIngredientsLatest(namespace string, name string, includeVersions bool, partial bool, auth *authentication.Auth) ([]*IngredientAndVersion, error) { + results, err := searchIngredientsNamespace(namespace, name, includeVersions, false, partial, auth) if err != nil { return nil, err } @@ -183,7 +178,7 @@ type ErrTooManyMatches struct { Query string } -func searchIngredientsNamespace(ns string, name string, includeVersions bool, exactOnly bool, auth *authentication.Auth) ([]*IngredientAndVersion, error) { +func searchIngredientsNamespace(ns string, name string, includeVersions bool, exactOnly bool, partial bool, auth *authentication.Auth) ([]*IngredientAndVersion, error) { limit := 100 offset := 0 @@ -219,7 +214,7 @@ func searchIngredientsNamespace(ns string, name string, includeVersions bool, ex } } - if len(response.SearchIngredients) < limit { + if len(response.SearchIngredients) < limit || partial { break } offset += limit diff --git a/test/integration/package_int_test.go b/test/integration/package_int_test.go index 8b4cd658ec..f2ebd51b61 100644 --- a/test/integration/package_int_test.go +++ b/test/integration/package_int_test.go @@ -151,7 +151,7 @@ func (suite *PackageIntegrationTestSuite) TestPackage_searchSimple() { suite.PrepareActiveStateYAML(ts) // Note that the expected strings might change due to inventory changes - cp := ts.Spawn("search", "requests") + cp := ts.Spawn("search", "requests2") expectations := []string{ "requests2", "2.16.0", @@ -207,8 +207,8 @@ func (suite *PackageIntegrationTestSuite) TestPackage_searchWithLang() { cp := ts.Spawn("search", "Moose", "--language=perl") cp.Expect("Name") cp.Expect("Moose") - cp.Expect("Moose-Autobox") - cp.Expect("MooseFS") + cp.Expect("IO-Moose") + cp.Expect("MooseX") cp.Send("q") cp.ExpectExitCode(0) } @@ -246,7 +246,7 @@ func (suite *PackageIntegrationTestSuite) TestPackage_searchWithBadLang() { suite.PrepareActiveStateYAML(ts) cp := ts.Spawn("search", "numpy", "--language=bad") - cp.Expect("Cannot obtain search") + cp.Expect("No packages in our catalog match") cp.ExpectExitCode(1) ts.IgnoreLogErrors() } @@ -279,8 +279,8 @@ func (suite *PackageIntegrationTestSuite) TestPackage_detached_operation() { suite.Run("install non-existing", func() { cp := ts.Spawn("install", "json") - cp.Expect("No results found for search term") - cp.Expect("json2") + cp.Expect(`No results found for search term "json". Did you mean:`) + cp.Expect("json") // suggestions include packages with json in the name cp.Wait() }) @@ -665,8 +665,11 @@ func (suite *PackageIntegrationTestSuite) TestCVE_NoPrompt() { cp := ts.Spawn("config", "set", constants.AsyncRuntimeConfig, "true") cp.ExpectExitCode(0) + // Note: this version has 2 known vulnerabilities, but since the number of indirect + // vulnerabilities is variable, we need to craft our expectations accordingly. cp = ts.Spawn("install", "urllib3@2.0.2") - cp.Expect("Warning: Dependency has 2 known vulnerabilities", e2e.RuntimeSourcingTimeoutOpt) + cp.Expect("Warning: Dependency has 2") + cp.Expect("known vulnerabilities") cp.ExpectExitCode(0) } @@ -688,8 +691,11 @@ func (suite *PackageIntegrationTestSuite) TestCVE_Prompt() { cp = ts.Spawn("config", "set", constants.SecurityPromptConfig, "true") cp.ExpectExitCode(0) + // Note: this version has 2 known vulnerabilities, but since the number of indirect + // vulnerabilities is variable, we need to craft our expectations accordingly. cp = ts.Spawn("install", "urllib3@2.0.2") - cp.Expect("Warning: Dependency has 2 known vulnerabilities") + cp.Expect("Warning: Dependency has 2") + cp.Expect("known vulnerabilities") cp.Expect("Do you want to continue") cp.SendLine("y") cp.ExpectExitCode(0) @@ -711,7 +717,7 @@ func (suite *PackageIntegrationTestSuite) TestCVE_Indirect() { cp.ExpectExitCode(0) cp = ts.Spawn("install", "private/ActiveState-CLI-Testing/language/python/django_dep") - cp.ExpectRe(`Warning: Dependency has \d indirect known vulnerabilities`) + cp.ExpectRe(`Warning: Dependency has \d+ indirect known vulnerabilities`) cp.Expect("Do you want to continue") cp.SendLine("n") cp.ExpectExitCode(1) From 0291af721eb9086e28342b3cac5912336376d081 Mon Sep 17 00:00:00 2001 From: mitchell Date: Thu, 5 Sep 2024 13:17:47 -0400 Subject: [PATCH 4/6] Added profiling for searchIngredientsNamespace. --- pkg/platform/model/inventory.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/platform/model/inventory.go b/pkg/platform/model/inventory.go index 0a74300578..dabbedde76 100644 --- a/pkg/platform/model/inventory.go +++ b/pkg/platform/model/inventory.go @@ -17,6 +17,7 @@ import ( "github.com/ActiveState/cli/internal/errs" "github.com/ActiveState/cli/internal/locale" configMediator "github.com/ActiveState/cli/internal/mediators/config" + "github.com/ActiveState/cli/internal/profile" "github.com/ActiveState/cli/pkg/platform/api" hsInventory "github.com/ActiveState/cli/pkg/platform/api/hasura_inventory" hsInventoryModel "github.com/ActiveState/cli/pkg/platform/api/hasura_inventory/model" @@ -179,6 +180,7 @@ type ErrTooManyMatches struct { } func searchIngredientsNamespace(ns string, name string, includeVersions bool, exactOnly bool, partial bool, auth *authentication.Auth) ([]*IngredientAndVersion, error) { + defer profile.Measure("searchIngredientsNamespace", time.Now()) limit := 100 offset := 0 From c8af791a38081ad9ccbaf3cea237bca82e71699c Mon Sep 17 00:00:00 2001 From: mitchell Date: Thu, 5 Sep 2024 14:03:39 -0400 Subject: [PATCH 5/6] Revert --ts=now removal, but use latest platform time for inventory searches. Do not use latest inventory time. --- cmd/state/internal/cmdtree/packages.go | 15 ++++++ internal/locale/locales/en-us.yaml | 4 +- internal/runbits/commits_runbit/time.go | 29 ++++++++++++ .../runtime/requirements/requirements.go | 47 ++++++++++--------- internal/runners/initialize/init.go | 2 +- internal/runners/languages/install.go | 2 +- internal/runners/packages/info.go | 13 +++-- internal/runners/packages/install.go | 14 ++++-- internal/runners/packages/search.go | 12 +++-- internal/runners/packages/uninstall.go | 9 +++- internal/runners/platforms/add.go | 1 + internal/runners/platforms/remove.go | 1 + internal/runners/publish/publish.go | 2 +- pkg/platform/model/inventory.go | 25 +++++----- test/integration/package_int_test.go | 2 +- test/integration/publish_int_test.go | 2 +- test/integration/runtime_int_test.go | 4 +- 17 files changed, 134 insertions(+), 50 deletions(-) diff --git a/cmd/state/internal/cmdtree/packages.go b/cmd/state/internal/cmdtree/packages.go index e1992b5788..39b66a1cb0 100644 --- a/cmd/state/internal/cmdtree/packages.go +++ b/cmd/state/internal/cmdtree/packages.go @@ -60,6 +60,11 @@ func newInstallCommand(prime *primer.Values) *captain.Command { locale.T("package_install_cmd_description"), prime, []*captain.Flag{ + { + Name: "ts", + Description: locale.T("package_flag_ts_description"), + Value: ¶ms.Timestamp, + }, { Name: "revision", Shorthand: "r", @@ -176,6 +181,11 @@ func newSearchCommand(prime *primer.Values) *captain.Command { Description: locale.T("package_search_flag_exact-term_description"), Value: ¶ms.ExactTerm, }, + { + Name: "ts", + Description: locale.T("package_flag_ts_description"), + Value: ¶ms.Timestamp, + }, }, []*captain.Argument{ { @@ -207,6 +217,11 @@ func newInfoCommand(prime *primer.Values) *captain.Command { Description: locale.T("package_info_flag_language_description"), Value: ¶ms.Language, }, + { + Name: "ts", + Description: locale.T("package_flag_ts_description"), + Value: ¶ms.Timestamp, + }, }, []*captain.Argument{ { diff --git a/internal/locale/locales/en-us.yaml b/internal/locale/locales/en-us.yaml index acbe597f77..62aa856235 100644 --- a/internal/locale/locales/en-us.yaml +++ b/internal/locale/locales/en-us.yaml @@ -594,6 +594,8 @@ namespace_list_flag_project_description: other: The namespace packages should be listed from package_search_flag_ns_description: other: The namespace to search within. +package_flag_ts_description: + other: The timestamp at which you want to query. Can be either 'now' or RFC3339 formatted timestamp. package_flag_rev_description: other: The revision you want to use. This ensures you get this exact revision and nothing else. package_search_flag_language_description: @@ -1453,7 +1455,7 @@ uploadingredient_success: Revision: [ACTIONABLE]{{.V3}}[/RESET] Timestamp: [ACTIONABLE]{{.V4}}[/RESET] - You can install this package by running `[ACTIONABLE]state install {{.V1}}/{{.V0}}`[/RESET]. + You can install this package by running `[ACTIONABLE]state install {{.V1}}/{{.V0}} --ts now`[/RESET]. err_runtime_cache_invalid: other: Your runtime needs to be updated. Please run '[ACTIONABLE]state refresh[/RESET]'. err_buildscript_not_exist: diff --git a/internal/runbits/commits_runbit/time.go b/internal/runbits/commits_runbit/time.go index 698bfe095c..478cfc544d 100644 --- a/internal/runbits/commits_runbit/time.go +++ b/internal/runbits/commits_runbit/time.go @@ -5,8 +5,10 @@ import ( "github.com/ActiveState/cli/internal/captain" "github.com/ActiveState/cli/internal/errs" + "github.com/ActiveState/cli/pkg/localcommit" "github.com/ActiveState/cli/pkg/platform/authentication" "github.com/ActiveState/cli/pkg/platform/model" + "github.com/ActiveState/cli/pkg/project" ) // ExpandTime returns a timestamp based on the given "--ts" value. @@ -34,3 +36,30 @@ func ExpandTime(ts *captain.TimeValue, auth *authentication.Auth) (time.Time, er return latest, nil } + +// ExpandTimeForProject is the same as ExpandTime except that it ensures the returned time is either the same or +// later than that of the most recent commit. +func ExpandTimeForProject(ts *captain.TimeValue, auth *authentication.Auth, proj *project.Project) (time.Time, error) { + timestamp, err := ExpandTime(ts, auth) + if err != nil { + return time.Time{}, errs.Wrap(err, "Unable to expand time") + } + + if proj != nil { + commitID, err := localcommit.Get(proj.Dir()) + if err != nil { + return time.Time{}, errs.Wrap(err, "Unable to get commit ID") + } + + atTime, err := model.FetchTimeStampForCommit(commitID, auth) + if err != nil { + return time.Time{}, errs.Wrap(err, "Unable to get commit time") + } + + if atTime.After(timestamp) { + return *atTime, nil + } + } + + return timestamp, nil +} diff --git a/internal/runbits/runtime/requirements/requirements.go b/internal/runbits/runtime/requirements/requirements.go index 1a7747b3d2..e041052df6 100644 --- a/internal/runbits/runtime/requirements/requirements.go +++ b/internal/runbits/runtime/requirements/requirements.go @@ -6,6 +6,7 @@ import ( "regexp" "strconv" "strings" + "time" "github.com/ActiveState/cli/internal/analytics" anaConsts "github.com/ActiveState/cli/internal/analytics/constants" @@ -129,7 +130,7 @@ type Requirement struct { // ExecuteRequirementOperation executes the operation on the requirement // This has become quite unwieldy, and is ripe for a refactor - https://activestatef.atlassian.net/browse/DX-1897 -func (r *RequirementOperation) ExecuteRequirementOperation(requirements ...*Requirement) (rerr error) { +func (r *RequirementOperation) ExecuteRequirementOperation(ts *time.Time, requirements ...*Requirement) (rerr error) { defer r.rationalizeError(&rerr) if len(requirements) == 0 { @@ -153,7 +154,7 @@ func (r *RequirementOperation) ExecuteRequirementOperation(requirements ...*Requ } out.Notice(locale.Tr("operating_message", r.Project.NamespaceString(), r.Project.Dir())) - if err := r.resolveNamespaces(requirements...); err != nil { + if err := r.resolveNamespaces(ts, requirements...); err != nil { return errs.Wrap(err, "Could not resolve namespaces") } @@ -199,7 +200,7 @@ func (r *RequirementOperation) ExecuteRequirementOperation(requirements ...*Requ } bp := bpModel.NewBuildPlannerModel(r.Auth) - script, err := r.prepareBuildScript(bp, parentCommitID, requirements) + script, err := r.prepareBuildScript(bp, parentCommitID, requirements, ts) if err != nil { return errs.Wrap(err, "Could not prepare build script") } @@ -280,21 +281,25 @@ func (r *RequirementOperation) ExecuteRequirementOperation(requirements ...*Requ return nil } -func (r *RequirementOperation) prepareBuildScript(bp *bpModel.BuildPlanner, parentCommit strfmt.UUID, requirements []*Requirement) (*buildscript.BuildScript, error) { +func (r *RequirementOperation) prepareBuildScript(bp *bpModel.BuildPlanner, parentCommit strfmt.UUID, requirements []*Requirement, ts *time.Time) (*buildscript.BuildScript, error) { script, err := bp.GetBuildScript(string(parentCommit)) if err != nil { return nil, errs.Wrap(err, "Failed to get build expression") } - // Ensure that the atTime in the script is updated to use - // the most recent, which is either the current value or the platform latest. - latest, err := model.FetchLatestTimeStamp(r.Auth) - if err != nil { - return nil, errs.Wrap(err, "Unable to fetch latest Platform timestamp") - } - atTime := script.AtTime() - if atTime == nil || latest.After(*atTime) { - script.SetAtTime(latest) + if ts != nil { + script.SetAtTime(*ts) + } else { + // If no atTime was provided then we need to ensure that the atTime in the script is updated to use + // the most recent, which is either the current value or the platform latest. + latest, err := model.FetchLatestTimeStamp(r.Auth) + if err != nil { + return nil, errs.Wrap(err, "Unable to fetch latest Platform timestamp") + } + atTime := script.AtTime() + if atTime == nil || latest.After(*atTime) { + script.SetAtTime(latest) + } } for _, req := range requirements { @@ -329,9 +334,9 @@ func (e ResolveNamespaceError) Error() string { return "unable to resolve namespace" } -func (r *RequirementOperation) resolveNamespaces(requirements ...*Requirement) error { +func (r *RequirementOperation) resolveNamespaces(ts *time.Time, requirements ...*Requirement) error { for _, requirement := range requirements { - if err := r.resolveNamespace(requirement); err != nil { + if err := r.resolveNamespace(ts, requirement); err != nil { if err != errNoLanguage { err = errs.Pack(err, &ResolveNamespaceError{requirement.Name}) } @@ -341,7 +346,7 @@ func (r *RequirementOperation) resolveNamespaces(requirements ...*Requirement) e return nil } -func (r *RequirementOperation) resolveNamespace(requirement *Requirement) error { +func (r *RequirementOperation) resolveNamespace(ts *time.Time, requirement *Requirement) error { requirement.langName = "undetermined" if requirement.NamespaceType != nil { @@ -381,7 +386,7 @@ func (r *RequirementOperation) resolveNamespace(requirement *Requirement) error var nsv model.Namespace var supportedLang *medmodel.SupportedLanguage - requirement.Name, nsv, supportedLang, err = resolvePkgAndNamespace(r.Prompt, requirement.Name, *requirement.NamespaceType, supported, r.Auth) + requirement.Name, nsv, supportedLang, err = resolvePkgAndNamespace(r.Prompt, requirement.Name, *requirement.NamespaceType, supported, ts, r.Auth) if err != nil { return errs.Wrap(err, "Could not resolve pkg and namespace") } @@ -436,7 +441,7 @@ func (r *RequirementOperation) validatePackage(requirement *Requirement) error { multilog.Error("Failed to normalize '%s': %v", requirement.Name, err) } - packages, err := model.SearchIngredientsStrict(requirement.Namespace.String(), normalized, false, false, r.Auth) // ideally case-sensitive would be true (PB-4371) + packages, err := model.SearchIngredientsStrict(requirement.Namespace.String(), normalized, false, false, nil, r.Auth) // ideally case-sensitive would be true (PB-4371) if err != nil { return locale.WrapError(err, "package_err_cannot_obtain_search_results") } @@ -586,11 +591,11 @@ func supportedLanguageByName(supported []medmodel.SupportedLanguage, langName st return funk.Find(supported, func(l medmodel.SupportedLanguage) bool { return l.Name == langName }).(medmodel.SupportedLanguage) } -func resolvePkgAndNamespace(prompt prompt.Prompter, packageName string, nsType model.NamespaceType, supported []medmodel.SupportedLanguage, auth *authentication.Auth) (string, model.Namespace, *medmodel.SupportedLanguage, error) { +func resolvePkgAndNamespace(prompt prompt.Prompter, packageName string, nsType model.NamespaceType, supported []medmodel.SupportedLanguage, ts *time.Time, auth *authentication.Auth) (string, model.Namespace, *medmodel.SupportedLanguage, error) { ns := model.NewBlankNamespace() // Find ingredients that match the input query - ingredients, err := model.SearchIngredientsStrict("", packageName, false, false, auth) + ingredients, err := model.SearchIngredientsStrict("", packageName, false, false, ts, auth) if err != nil { return "", ns, nil, locale.WrapError(err, "err_pkgop_search_err", "Failed to check for ingredients.") } @@ -639,7 +644,7 @@ func resolvePkgAndNamespace(prompt prompt.Prompter, packageName string, nsType m } func getSuggestions(ns model.Namespace, name string, auth *authentication.Auth) ([]string, error) { - results, err := model.SearchIngredientsLatest(ns.String(), name, false, true, auth) + results, err := model.SearchIngredientsLatest(ns.String(), name, false, true, nil, auth) if err != nil { return []string{}, locale.WrapError(err, "package_ingredient_err_search", "Failed to resolve ingredient named: {{.V0}}", name) } diff --git a/internal/runners/initialize/init.go b/internal/runners/initialize/init.go index 1b8aeab49d..1951a206f8 100644 --- a/internal/runners/initialize/init.go +++ b/internal/runners/initialize/init.go @@ -334,7 +334,7 @@ func (r *Initialize) Run(params *RunParams) (rerr error) { } func getKnownVersions(lang language.Language, auth *authentication.Auth) ([]string, error) { - pkgs, err := model.SearchIngredientsStrict(model.NewNamespaceLanguage().String(), lang.Requirement(), false, true, auth) + pkgs, err := model.SearchIngredientsStrict(model.NewNamespaceLanguage().String(), lang.Requirement(), false, true, nil, auth) if err != nil { return nil, errs.Wrap(err, "Failed to fetch Platform languages") } diff --git a/internal/runners/languages/install.go b/internal/runners/languages/install.go index d5f73bd9da..d2f208dae6 100644 --- a/internal/runners/languages/install.go +++ b/internal/runners/languages/install.go @@ -54,7 +54,7 @@ func (u *Update) Run(params *UpdateParams) error { } op := requirements.NewRequirementOperation(u.prime) - return op.ExecuteRequirementOperation(&requirements.Requirement{ + return op.ExecuteRequirementOperation(nil, &requirements.Requirement{ Name: lang.Name, Version: lang.Version, NamespaceType: &model.NamespaceLanguage, diff --git a/internal/runners/packages/info.go b/internal/runners/packages/info.go index f0917fdabf..cb0cbc88a8 100644 --- a/internal/runners/packages/info.go +++ b/internal/runners/packages/info.go @@ -12,6 +12,7 @@ import ( "github.com/ActiveState/cli/internal/multilog" "github.com/ActiveState/cli/internal/output" "github.com/ActiveState/cli/internal/rtutils/ptr" + "github.com/ActiveState/cli/internal/runbits/commits_runbit" hsInventoryModel "github.com/ActiveState/cli/pkg/platform/api/hasura_inventory/model" "github.com/ActiveState/cli/pkg/platform/api/vulnerabilities/request" "github.com/ActiveState/cli/pkg/platform/authentication" @@ -21,8 +22,9 @@ import ( // InfoRunParams tracks the info required for running Info. type InfoRunParams struct { - Package captain.PackageValue - Language string + Package captain.PackageValue + Timestamp captain.TimeValue + Language string } // Info manages the information execution context. @@ -68,7 +70,12 @@ func (i *Info) Run(params InfoRunParams, nstype model.NamespaceType) error { normalized = params.Package.Name } - packages, err := model.SearchIngredientsStrict(ns.String(), normalized, false, false, i.auth) // ideally case-sensitive would be true (PB-4371) + ts, err := commits_runbit.ExpandTimeForProject(¶ms.Timestamp, i.auth, i.proj) + if err != nil { + return errs.Wrap(err, "Unable to get timestamp from params") + } + + packages, err := model.SearchIngredientsStrict(ns.String(), normalized, false, false, &ts, i.auth) // ideally case-sensitive would be true (PB-4371) if err != nil { return locale.WrapError(err, "package_err_cannot_obtain_search_results") } diff --git a/internal/runners/packages/install.go b/internal/runners/packages/install.go index 3e3ed7a570..30193f4cf8 100644 --- a/internal/runners/packages/install.go +++ b/internal/runners/packages/install.go @@ -2,8 +2,10 @@ package packages import ( "github.com/ActiveState/cli/internal/captain" + "github.com/ActiveState/cli/internal/errs" "github.com/ActiveState/cli/internal/logging" "github.com/ActiveState/cli/internal/rtutils/ptr" + "github.com/ActiveState/cli/internal/runbits/commits_runbit" "github.com/ActiveState/cli/internal/runbits/runtime/requirements" "github.com/ActiveState/cli/pkg/platform/api/buildplanner/types" "github.com/ActiveState/cli/pkg/platform/model" @@ -11,8 +13,9 @@ import ( // InstallRunParams tracks the info required for running Install. type InstallRunParams struct { - Packages captain.PackagesValue - Revision captain.IntValue + Packages captain.PackagesValue + Timestamp captain.TimeValue + Revision captain.IntValue } // Install manages the installing execution context. @@ -49,5 +52,10 @@ func (a *Install) Run(params InstallRunParams, nsType model.NamespaceType) (rerr reqs = append(reqs, req) } - return requirements.NewRequirementOperation(a.prime).ExecuteRequirementOperation(reqs...) + ts, err := commits_runbit.ExpandTimeForProject(¶ms.Timestamp, a.prime.Auth(), a.prime.Project()) + if err != nil { + return errs.Wrap(err, "Unable to get timestamp from params") + } + + return requirements.NewRequirementOperation(a.prime).ExecuteRequirementOperation(&ts, reqs...) } diff --git a/internal/runners/packages/search.go b/internal/runners/packages/search.go index 9eb7ed9bee..fde109fe23 100644 --- a/internal/runners/packages/search.go +++ b/internal/runners/packages/search.go @@ -8,6 +8,7 @@ import ( "github.com/ActiveState/cli/internal/locale" "github.com/ActiveState/cli/internal/logging" "github.com/ActiveState/cli/internal/output" + "github.com/ActiveState/cli/internal/runbits/commits_runbit" "github.com/ActiveState/cli/pkg/localcommit" "github.com/ActiveState/cli/pkg/platform/api/vulnerabilities/request" "github.com/ActiveState/cli/pkg/platform/authentication" @@ -21,6 +22,7 @@ type SearchRunParams struct { Language string ExactTerm bool Ingredient captain.PackageValueNoVersion + Timestamp captain.TimeValue } // Search manages the searching execution context. @@ -57,12 +59,16 @@ func (s *Search) Run(params SearchRunParams, nstype model.NamespaceType) error { ns = model.NewRawNamespace(params.Ingredient.Namespace) } + ts, err := commits_runbit.ExpandTimeForProject(¶ms.Timestamp, s.auth, s.proj) + if err != nil { + return errs.Wrap(err, "Unable to get timestamp from params") + } + var packages []*model.IngredientAndVersion - var err error if params.ExactTerm { - packages, err = model.SearchIngredientsLatestStrict(ns.String(), params.Ingredient.Name, true, true, s.auth) + packages, err = model.SearchIngredientsLatestStrict(ns.String(), params.Ingredient.Name, true, true, &ts, s.auth) } else { - packages, err = model.SearchIngredientsLatest(ns.String(), params.Ingredient.Name, true, false, s.auth) + packages, err = model.SearchIngredientsLatest(ns.String(), params.Ingredient.Name, true, false, &ts, s.auth) } if err != nil { return locale.WrapError(err, "package_err_cannot_obtain_search_results") diff --git a/internal/runners/packages/uninstall.go b/internal/runners/packages/uninstall.go index 4cd7571ff1..a9008261dd 100644 --- a/internal/runners/packages/uninstall.go +++ b/internal/runners/packages/uninstall.go @@ -2,8 +2,10 @@ package packages import ( "github.com/ActiveState/cli/internal/captain" + "github.com/ActiveState/cli/internal/errs" "github.com/ActiveState/cli/internal/logging" "github.com/ActiveState/cli/internal/rtutils/ptr" + "github.com/ActiveState/cli/internal/runbits/commits_runbit" "github.com/ActiveState/cli/internal/runbits/rationalize" "github.com/ActiveState/cli/internal/runbits/runtime/requirements" "github.com/ActiveState/cli/pkg/platform/api/buildplanner/types" @@ -49,5 +51,10 @@ func (u *Uninstall) Run(params UninstallRunParams, nsType model.NamespaceType) ( reqs = append(reqs, req) } - return requirements.NewRequirementOperation(u.prime).ExecuteRequirementOperation(reqs...) + ts, err := commits_runbit.ExpandTimeForProject(&captain.TimeValue{}, u.prime.Auth(), u.prime.Project()) + if err != nil { + return errs.Wrap(err, "Unable to get timestamp from params") + } + + return requirements.NewRequirementOperation(u.prime).ExecuteRequirementOperation(&ts, reqs...) } diff --git a/internal/runners/platforms/add.go b/internal/runners/platforms/add.go index 6d97f2952f..767c569f2a 100644 --- a/internal/runners/platforms/add.go +++ b/internal/runners/platforms/add.go @@ -51,6 +51,7 @@ func (a *Add) Run(ps AddRunParams) error { } if err := requirements.NewRequirementOperation(a.prime).ExecuteRequirementOperation( + nil, &requirements.Requirement{ Name: params.name, Version: params.version, diff --git a/internal/runners/platforms/remove.go b/internal/runners/platforms/remove.go index d85df219f8..3690be8257 100644 --- a/internal/runners/platforms/remove.go +++ b/internal/runners/platforms/remove.go @@ -41,6 +41,7 @@ func (r *Remove) Run(ps RemoveRunParams) error { } if err := requirements.NewRequirementOperation(r.prime).ExecuteRequirementOperation( + nil, &requirements.Requirement{ Name: params.name, Version: params.version, diff --git a/internal/runners/publish/publish.go b/internal/runners/publish/publish.go index f890af64ae..b831913fac 100644 --- a/internal/runners/publish/publish.go +++ b/internal/runners/publish/publish.go @@ -168,7 +168,7 @@ func (r *Runner) Run(params *Params) error { if ingredient == nil { // Attempt to find the existing ingredient, if we didn't already get it from the version specific call above - ingredients, err := model.SearchIngredientsStrict(reqVars.Namespace, reqVars.Name, true, false, r.auth) + ingredients, err := model.SearchIngredientsStrict(reqVars.Namespace, reqVars.Name, true, false, &latestRevisionTime, r.auth) var errSearch404 *model.ErrSearch404 if err != nil && !errors.As(err, &errSearch404) { // 404 means either the ingredient or the namespace was not found, which is fine return locale.WrapError(err, "err_uploadingredient_search", "Could not search for ingredient") diff --git a/pkg/platform/model/inventory.go b/pkg/platform/model/inventory.go index dabbedde76..d94ad98494 100644 --- a/pkg/platform/model/inventory.go +++ b/pkg/platform/model/inventory.go @@ -85,8 +85,8 @@ func GetIngredientByNameAndVersion(namespace string, name string, version string // SearchIngredientsStrict will return all ingredients+ingredientVersions that // strictly match the ingredient name. -func SearchIngredientsStrict(namespace string, name string, caseSensitive bool, includeVersions bool, auth *authentication.Auth) ([]*IngredientAndVersion, error) { - results, err := searchIngredientsNamespace(namespace, name, includeVersions, true, false, auth) +func SearchIngredientsStrict(namespace string, name string, caseSensitive bool, includeVersions bool, ts *time.Time, auth *authentication.Auth) ([]*IngredientAndVersion, error) { + results, err := searchIngredientsNamespace(namespace, name, includeVersions, true, false, ts, auth) if err != nil { return nil, err } @@ -114,8 +114,8 @@ func SearchIngredientsStrict(namespace string, name string, caseSensitive bool, // fuzzily match the ingredient name, but only the latest version of each // ingredient. // Returns an error if there are too many matches unless `partial` is true. -func SearchIngredientsLatest(namespace string, name string, includeVersions bool, partial bool, auth *authentication.Auth) ([]*IngredientAndVersion, error) { - results, err := searchIngredientsNamespace(namespace, name, includeVersions, false, partial, auth) +func SearchIngredientsLatest(namespace string, name string, includeVersions bool, partial bool, ts *time.Time, auth *authentication.Auth) ([]*IngredientAndVersion, error) { + results, err := searchIngredientsNamespace(namespace, name, includeVersions, false, partial, ts, auth) if err != nil { return nil, err } @@ -126,8 +126,8 @@ func SearchIngredientsLatest(namespace string, name string, includeVersions bool // SearchIngredientsLatestStrict will return all ingredients+ingredientVersions that // strictly match the ingredient name, but only the latest version of each // ingredient. -func SearchIngredientsLatestStrict(namespace string, name string, caseSensitive bool, includeVersions bool, auth *authentication.Auth) ([]*IngredientAndVersion, error) { - results, err := SearchIngredientsStrict(namespace, name, caseSensitive, includeVersions, auth) +func SearchIngredientsLatestStrict(namespace string, name string, caseSensitive bool, includeVersions bool, ts *time.Time, auth *authentication.Auth) ([]*IngredientAndVersion, error) { + results, err := SearchIngredientsStrict(namespace, name, caseSensitive, includeVersions, ts, auth) if err != nil { return nil, err } @@ -179,18 +179,21 @@ type ErrTooManyMatches struct { Query string } -func searchIngredientsNamespace(ns string, name string, includeVersions bool, exactOnly bool, partial bool, auth *authentication.Auth) ([]*IngredientAndVersion, error) { +func searchIngredientsNamespace(ns string, name string, includeVersions bool, exactOnly bool, partial bool, ts *time.Time, auth *authentication.Auth) ([]*IngredientAndVersion, error) { defer profile.Measure("searchIngredientsNamespace", time.Now()) limit := 100 offset := 0 - ts, err := FetchLatestRevisionTimeStamp(auth) - if err != nil { - return nil, errs.Wrap(err, "Unable to fetch latest inventory timestamp") + if ts == nil { + platformTime, err := FetchLatestTimeStamp(auth) + if err != nil { + return nil, errs.Wrap(err, "Unable to fetch latest platform timestamp") + } + ts = &platformTime } client := hsInventory.New(auth) - request := hsInventoryRequest.SearchIngredients([]string{ns}, name, exactOnly, ts, limit, offset) + request := hsInventoryRequest.SearchIngredients([]string{ns}, name, exactOnly, *ts, limit, offset) var ingredients []*IngredientAndVersion for { diff --git a/test/integration/package_int_test.go b/test/integration/package_int_test.go index f2ebd51b61..93dab1d641 100644 --- a/test/integration/package_int_test.go +++ b/test/integration/package_int_test.go @@ -716,7 +716,7 @@ func (suite *PackageIntegrationTestSuite) TestCVE_Indirect() { cp = ts.Spawn("config", "set", constants.SecurityPromptConfig, "true") cp.ExpectExitCode(0) - cp = ts.Spawn("install", "private/ActiveState-CLI-Testing/language/python/django_dep") + cp = ts.Spawn("install", "private/ActiveState-CLI-Testing/language/python/django_dep", "--ts=now") cp.ExpectRe(`Warning: Dependency has \d+ indirect known vulnerabilities`) cp.Expect("Do you want to continue") cp.SendLine("n") diff --git a/test/integration/publish_int_test.go b/test/integration/publish_int_test.go index a18cc66a69..709cde17ab 100644 --- a/test/integration/publish_int_test.go +++ b/test/integration/publish_int_test.go @@ -457,7 +457,7 @@ authors: cp.Expect(version) cp.ExpectExitCode(inv.expect.exitCode) - cp = ts.Spawn("search", namespace+"/"+name) + cp = ts.Spawn("search", namespace+"/"+name, "--ts=now") cp.Expect(version) time.Sleep(time.Second) cp.Send("q") diff --git a/test/integration/runtime_int_test.go b/test/integration/runtime_int_test.go index 6b59ad6f3d..83ab55cfa4 100644 --- a/test/integration/runtime_int_test.go +++ b/test/integration/runtime_int_test.go @@ -148,7 +148,7 @@ func (suite *RuntimeIntegrationTestSuite) TestBuildInProgress() { ts.LoginAsPersistentUser() - // Publish a new ingredient revision, which, when coupled with `state install`, will + // Publish a new ingredient revision, which, when coupled with `state install --ts now`, will // force a build. // The ingredient is a tarball comprising: // 1. An empty, executable "configure" script (emulating autotools). @@ -167,7 +167,7 @@ func (suite *RuntimeIntegrationTestSuite) TestBuildInProgress() { ts.PrepareEmptyProject() - cp = ts.Spawn("install", "private/"+e2e.PersistentUsername+"/hello-world") + cp = ts.Spawn("install", "private/"+e2e.PersistentUsername+"/hello-world", "--ts", "now") cp.Expect("Build Log") cp.Expect("Building") cp.Expect("All dependencies have been installed and verified", e2e.RuntimeBuildSourcingTimeoutOpt) From b42e6d95cfc4b31c3409af6d5473ac61c3f9ccda Mon Sep 17 00:00:00 2001 From: mitchell Date: Thu, 5 Sep 2024 14:37:02 -0400 Subject: [PATCH 6/6] Request up to 1000 ingredients at a time during searches to avoid multiple paged requests. --- pkg/platform/model/inventory.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/platform/model/inventory.go b/pkg/platform/model/inventory.go index d94ad98494..ba54524d5b 100644 --- a/pkg/platform/model/inventory.go +++ b/pkg/platform/model/inventory.go @@ -181,7 +181,7 @@ type ErrTooManyMatches struct { func searchIngredientsNamespace(ns string, name string, includeVersions bool, exactOnly bool, partial bool, ts *time.Time, auth *authentication.Auth) ([]*IngredientAndVersion, error) { defer profile.Measure("searchIngredientsNamespace", time.Now()) - limit := 100 + limit := 1000 offset := 0 if ts == nil { @@ -198,7 +198,7 @@ func searchIngredientsNamespace(ns string, name string, includeVersions bool, ex var ingredients []*IngredientAndVersion for { response := hsInventoryModel.SearchIngredientsResponse{} - if offset > (limit * 10) { // at most we will get 10 pages of ingredients (that's ONE THOUSAND ingredients) + if offset > 0 { // Guard against queries that match TOO MANY ingredients return nil, &ErrTooManyMatches{locale.NewInputError("err_searchingredient_toomany", "", name), name} }