Skip to content

Commit 0a280d1

Browse files
authored
[search] Update search endpoints (#1249)
## Summary This updates search endpoints to use new `v1` endpoints. It's a fairly small change, but required modernizing the format we use to unmarshal results. I took advantage to make a few low risk refactoring changes: * Removed lock dependency from search. This allows lock to have an actual searcher instance and not an interface. * Removed `devpkgutil` package and incorporated to searcher. * Improved search command to truncate results/versions to improve ui/ux. Added flag to show all commands. * Improved search command by allowing version to be specified to get a preview of what it will resolve to. This can be improved by doing server side filtering and using search instead of resolve. * Fixed `devbox update` bug where updating was causing lock files entries to get replaced even if version had not changed. (This causes nixpkgs to change which slows stuff down) ## How was it tested? * Ran `devbox update` * Tested `devbox add [email protected]` * Tested `devbox search` with many values, with and without the `--show-all` flag <img width="738" alt="image" src="https://github.com/jetpack-io/devbox/assets/544948/033f0720-ede2-45ad-b36e-eac4d9e2895c">
1 parent 8b6be0b commit 0a280d1

File tree

15 files changed

+243
-209
lines changed

15 files changed

+243
-209
lines changed

devbox.lock

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
"version": "1.6.23"
88
},
99
10-
"last_modified": "2023-05-01T16:53:22Z",
11-
"resolved": "github:NixOS/nixpkgs/8670e496ffd093b60e74e7fa53526aa5920d09eb#go",
12-
"version": "1.20.3"
10+
"last_modified": "2023-05-25T03:54:59Z",
11+
"resolved": "github:NixOS/nixpkgs/8d4d822bc0efa9de6eddc79cb0d82897a9baa750#go",
12+
"version": "1.20.4"
1313
},
1414
1515
"last_modified": "2023-05-01T16:53:22Z",

internal/boxcli/add.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ func addCmd() *cobra.Command {
4040
}
4141
err := addCmdFunc(cmd, args, flags)
4242
if errors.Is(err, nix.ErrPackageNotFound) {
43-
return usererr.New("%s\n\n%s", err, toSearchForPackages)
43+
return usererr.WithUserMessage(err, toSearchForPackages)
4444
}
4545
return err
4646
},

internal/boxcli/search.go

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,115 @@
44
package boxcli
55

66
import (
7+
"fmt"
8+
"io"
9+
"math"
10+
"strings"
11+
712
"github.com/spf13/cobra"
813

14+
"go.jetpack.io/devbox/internal/boxcli/usererr"
915
"go.jetpack.io/devbox/internal/searcher"
1016
"go.jetpack.io/devbox/internal/ux"
1117
)
1218

19+
type searchCmdFlags struct {
20+
showAll bool
21+
}
22+
1323
func searchCmd() *cobra.Command {
24+
flags := &searchCmdFlags{}
1425
command := &cobra.Command{
1526
Use: "search <pkg>",
1627
Short: "Search for nix packages",
1728
Args: cobra.ExactArgs(1),
1829
RunE: func(cmd *cobra.Command, args []string) error {
19-
ux.Fwarning(cmd.ErrOrStderr(), "Search is experimental and may not work as expected.\n\n")
20-
return searcher.SearchAndPrint(cmd.OutOrStdout(), args[0])
30+
query := args[0]
31+
name, version, isVersioned := searcher.ParseVersionedPackage(query)
32+
if !isVersioned {
33+
results, err := searcher.Client().Search(query)
34+
if err != nil {
35+
return err
36+
}
37+
return printSearchResults(
38+
cmd.OutOrStdout(), query, results, flags.showAll)
39+
}
40+
packageVersion, err := searcher.Client().Resolve(name, version)
41+
if err != nil {
42+
// This is not ideal. Search service should return valid response we
43+
// can parse
44+
return usererr.WithUserMessage(err, "No results found for %q\n", query)
45+
}
46+
fmt.Fprintf(
47+
cmd.OutOrStdout(),
48+
"%s resolves to: %s@%s\n",
49+
query,
50+
packageVersion.Name,
51+
packageVersion.Version,
52+
)
53+
return nil
2154
},
2255
}
2356

57+
command.Flags().BoolVar(
58+
&flags.showAll, "show-all", false,
59+
"show all available templates",
60+
)
61+
2462
return command
2563
}
64+
65+
func printSearchResults(
66+
w io.Writer,
67+
query string,
68+
results *searcher.SearchResults,
69+
showAll bool,
70+
) error {
71+
if len(results.Packages) == 0 {
72+
fmt.Fprintf(w, "No results found for %q\n", query)
73+
return nil
74+
}
75+
fmt.Fprintf(
76+
w,
77+
"Found %d+ results for %q:\n\n",
78+
results.NumResults,
79+
query,
80+
)
81+
82+
resultsAreTrimmed := false
83+
pkgs := results.Packages
84+
if !showAll && len(pkgs) > 10 {
85+
resultsAreTrimmed = true
86+
pkgs = results.Packages[:int(math.Min(10, float64(len(results.Packages))))]
87+
}
88+
89+
for _, pkg := range pkgs {
90+
nonEmptyVersions := []string{}
91+
for i, v := range pkg.Versions {
92+
if !showAll && i >= 10 {
93+
resultsAreTrimmed = true
94+
break
95+
}
96+
if v.Version != "" {
97+
nonEmptyVersions = append(nonEmptyVersions, v.Version)
98+
}
99+
}
100+
101+
versionString := ""
102+
if len(nonEmptyVersions) > 0 {
103+
versionString = fmt.Sprintf(" (%s)", strings.Join(nonEmptyVersions, ", "))
104+
}
105+
fmt.Fprintf(w, "* %s %s\n", pkg.Name, versionString)
106+
}
107+
108+
if resultsAreTrimmed {
109+
fmt.Println()
110+
ux.Fwarning(
111+
w,
112+
"Showing top 10 results and truncated versions. Use --show-all to "+
113+
"show all.\n\n",
114+
)
115+
}
116+
117+
return nil
118+
}

internal/devpkg/package.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,8 @@ func (p *Package) PathInBinaryStore() (string, error) {
437437
if isInStore, err := p.IsInBinaryStore(); err != nil {
438438
return "", err
439439
} else if !isInStore {
440-
return "", errors.Errorf("Package %q cannot be fetched from binary cache store", p.Raw)
440+
return "",
441+
errors.Errorf("Package %q cannot be fetched from binary cache store", p.Raw)
441442
}
442443

443444
entry, err := p.lockfile.Resolve(p.Raw)

internal/impl/devbox.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ import (
3838
"go.jetpack.io/devbox/internal/nix"
3939
"go.jetpack.io/devbox/internal/plugin"
4040
"go.jetpack.io/devbox/internal/redact"
41-
"go.jetpack.io/devbox/internal/searcher"
4241
"go.jetpack.io/devbox/internal/services"
4342
"go.jetpack.io/devbox/internal/ux"
4443
"go.jetpack.io/devbox/internal/wrapnix"
@@ -90,7 +89,7 @@ func Open(opts *devopt.Opts) (*Devbox, error) {
9089
pure: opts.Pure,
9190
}
9291

93-
lock, err := lock.GetFile(box, searcher.Client())
92+
lock, err := lock.GetFile(box)
9493
if err != nil {
9594
return nil, err
9695
}

internal/impl/packages.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ func (d *Devbox) Add(ctx context.Context, pkgsNames ...string) error {
7070
return err
7171
}
7272
if !ok {
73-
return errors.WithMessage(nix.ErrPackageNotFound, pkg.Raw)
73+
return errors.Wrap(nix.ErrPackageNotFound, pkg.Raw)
7474
}
7575
}
7676

internal/impl/update.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"fmt"
99

1010
"go.jetpack.io/devbox/internal/devpkg"
11-
"go.jetpack.io/devbox/internal/devpkg/devpkgutil"
1211
"go.jetpack.io/devbox/internal/nix"
1312
"go.jetpack.io/devbox/internal/nix/nixprofile"
1413
"go.jetpack.io/devbox/internal/searcher"
@@ -44,7 +43,7 @@ func (d *Devbox) Update(ctx context.Context, pkgs ...string) error {
4443
}
4544

4645
for _, pkg := range pendingPackagesToUpdate {
47-
if _, _, isVersioned := devpkgutil.ParseVersionedPackage(pkg.Raw); !isVersioned {
46+
if _, _, isVersioned := searcher.ParseVersionedPackage(pkg.Raw); !isVersioned {
4847
if err = d.attemptToUpgradeFlake(pkg); err != nil {
4948
return err
5049
}
@@ -87,7 +86,7 @@ func (d *Devbox) updateDevboxPackage(
8786
pkg *devpkg.Package,
8887
) error {
8988
existing := d.lockfile.Packages[pkg.Raw]
90-
newEntry, err := searcher.Client().Resolve(pkg.Raw)
89+
newEntry, err := d.lockfile.FetchResolvedPackage(pkg.Raw)
9190
if err != nil {
9291
return err
9392
}
@@ -98,13 +97,14 @@ func (d *Devbox) updateDevboxPackage(
9897
// sync the profile so we don't need to do this manually.
9998
ux.Fwarning(d.writer, "Failed to remove %s from profile: %s\n", pkg, err)
10099
}
100+
d.lockfile.Packages[pkg.Raw] = newEntry
101101
} else if existing == nil {
102102
ux.Finfo(d.writer, "Resolved %s to %[1]s %[2]s\n", pkg, newEntry.Resolved)
103+
d.lockfile.Packages[pkg.Raw] = newEntry
103104
} else {
104105
ux.Finfo(d.writer, "Already up-to-date %s %s\n", pkg, existing.Version)
105106
}
106-
// Set the new entry after we've removed the old package from the profile
107-
d.lockfile.Packages[pkg.Raw] = newEntry
107+
108108
return nil
109109
}
110110

internal/lock/interfaces.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,8 @@ type devboxProject interface {
1010
ProjectDir() string
1111
}
1212

13-
type resolver interface {
14-
Resolve(pkg string) (*Package, error)
15-
}
16-
1713
type Locker interface {
18-
resolver
1914
LegacyNixpkgsPath(string) string
2015
ProjectDir() string
16+
Resolve(string) (*Package, error)
2117
}

internal/lock/lockfile.go

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ import (
1212
"github.com/pkg/errors"
1313
"github.com/samber/lo"
1414
"go.jetpack.io/devbox/internal/boxcli/featureflag"
15-
"go.jetpack.io/devbox/internal/devpkg/devpkgutil"
1615
"go.jetpack.io/devbox/internal/nix"
16+
"go.jetpack.io/devbox/internal/searcher"
1717

1818
"go.jetpack.io/devbox/internal/cuecfg"
1919
)
@@ -23,7 +23,6 @@ const lockFileVersion = "1"
2323
// Lightly inspired by package-lock.json
2424
type File struct {
2525
devboxProject
26-
resolver
2726

2827
LockFileVersion string `json:"lockfile_version"`
2928

@@ -49,10 +48,9 @@ type SystemInfo struct {
4948
ToHash string `json:"to_hash,omitempty"`
5049
}
5150

52-
func GetFile(project devboxProject, resolver resolver) (*File, error) {
51+
func GetFile(project devboxProject) (*File, error) {
5352
lockFile := &File{
5453
devboxProject: project,
55-
resolver: resolver,
5654

5755
LockFileVersion: lockFileVersion,
5856
Packages: map[string]*Package{},
@@ -101,8 +99,8 @@ func (l *File) Resolve(pkg string) (*Package, error) {
10199
if !hasEntry || entry.Resolved == "" || needsSysInfo {
102100
locked := &Package{}
103101
var err error
104-
if _, _, versioned := devpkgutil.ParseVersionedPackage(pkg); versioned {
105-
locked, err = l.resolver.Resolve(pkg)
102+
if _, _, versioned := searcher.ParseVersionedPackage(pkg); versioned {
103+
locked, err = l.FetchResolvedPackage(pkg)
106104
if err != nil {
107105
return nil, err
108106
}
@@ -161,7 +159,7 @@ func (l *File) LegacyNixpkgsPath(pkg string) string {
161159
// This probably belongs in input.go but can't add it there because it will
162160
// create a circular dependency. We could move Input into own package.
163161
func IsLegacyPackage(pkg string) bool {
164-
_, _, versioned := devpkgutil.ParseVersionedPackage(pkg)
162+
_, _, versioned := searcher.ParseVersionedPackage(pkg)
165163
return !versioned &&
166164
!strings.Contains(pkg, ":") &&
167165
// We don't support absolute paths without "path:" prefix, but adding here

internal/lock/resolve.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Copyright 2023 Jetpack Technologies Inc and contributors. All rights reserved.
2+
// Use of this source code is governed by the license in the LICENSE file.
3+
4+
package lock
5+
6+
import (
7+
"fmt"
8+
"time"
9+
10+
"github.com/pkg/errors"
11+
"go.jetpack.io/devbox/internal/boxcli/featureflag"
12+
"go.jetpack.io/devbox/internal/boxcli/usererr"
13+
"go.jetpack.io/devbox/internal/nix"
14+
"go.jetpack.io/devbox/internal/searcher"
15+
"golang.org/x/exp/maps"
16+
)
17+
18+
// FetchResolvedPackage fetches a resolution but does not write it to the lock
19+
// struct. This allows testing new versions of packages without writing to the
20+
// lock. This is useful to avoid changing nixpkgs commit hashes when version has
21+
// not changed. This can happen when doing `devbox update` and search has
22+
// a newer hash than the lock file but same version. In that case we don't want
23+
// to update because it would be slow and wasteful.
24+
func (l *File) FetchResolvedPackage(pkg string) (*Package, error) {
25+
name, version, _ := searcher.ParseVersionedPackage(pkg)
26+
if version == "" {
27+
return nil, usererr.New("No version specified for %q.", name)
28+
}
29+
30+
packageVersion, err := searcher.Client().Resolve(name, version)
31+
if err != nil {
32+
return nil, errors.Wrapf(nix.ErrPackageNotFound, "%s@%s", name, version)
33+
}
34+
35+
sysInfos := map[string]*SystemInfo{}
36+
if featureflag.RemoveNixpkgs.Enabled() {
37+
sysInfos = buildLockSystemInfos(packageVersion)
38+
}
39+
packageInfo, err := selectForSystem(packageVersion)
40+
if err != nil {
41+
return nil, err
42+
}
43+
44+
if len(packageInfo.AttrPaths) == 0 {
45+
return nil, fmt.Errorf("no attr paths found for package %q", name)
46+
}
47+
48+
return &Package{
49+
LastModified: time.Unix(int64(packageInfo.LastUpdated), 0).UTC().
50+
Format(time.RFC3339),
51+
Resolved: fmt.Sprintf(
52+
"github:NixOS/nixpkgs/%s#%s",
53+
packageInfo.CommitHash,
54+
packageInfo.AttrPaths[0],
55+
),
56+
Version: packageInfo.Version,
57+
Systems: sysInfos,
58+
}, nil
59+
}
60+
61+
func selectForSystem(pkg *searcher.PackageVersion) (searcher.PackageInfo, error) {
62+
currentSystem, err := nix.System()
63+
if err != nil {
64+
return searcher.PackageInfo{}, err
65+
}
66+
if pi, ok := pkg.Systems[currentSystem]; ok {
67+
return pi, nil
68+
}
69+
if pi, ok := pkg.Systems["x86_64-linux"]; ok {
70+
return pi, nil
71+
}
72+
if len(pkg.Systems) == 0 {
73+
return searcher.PackageInfo{},
74+
fmt.Errorf("no systems found for package %q", pkg.Name)
75+
}
76+
return maps.Values(pkg.Systems)[0], nil
77+
}
78+
79+
func buildLockSystemInfos(pkg *searcher.PackageVersion) map[string]*SystemInfo {
80+
sysInfos := map[string]*SystemInfo{}
81+
for sysName, sysInfo := range pkg.Systems {
82+
sysInfos[sysName] = &SystemInfo{
83+
System: sysName,
84+
FromHash: sysInfo.StoreHash,
85+
StoreName: sysInfo.StoreName,
86+
StoreVersion: sysInfo.StoreVersion,
87+
}
88+
}
89+
return sysInfos
90+
}

internal/nix/nixprofile/profile.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"strings"
1212

1313
"github.com/fatih/color"
14+
"github.com/pkg/errors"
1415
"go.jetpack.io/devbox/internal/devpkg"
1516
"go.jetpack.io/devbox/internal/nix"
1617

@@ -85,7 +86,7 @@ func ProfileListIndex(args *ProfileListIndexArgs) (int, error) {
8586
return item.index, nil
8687
}
8788
}
88-
return -1, nix.ErrPackageNotFound
89+
return -1, errors.Wrap(nix.ErrPackageNotFound, args.Input.String())
8990
}
9091

9192
// NixProfileListItem is a go-struct of a line of printed output from `nix profile list`

0 commit comments

Comments
 (0)