Skip to content

Conversation

savil
Copy link
Collaborator

@savil savil commented Jul 25, 2023

Summary

Problem:
Latest nix version of 2.17 changes the output format of nix profile list to be:

Index:              0
Flake attribute:    legacyPackages.aarch64-linux.curl
Original flake URL: github:NixOS/nixpkgs/3c614fbc76fc152f3e1bc4b2263da6d90adf80fb
Locked flake URL:   github:NixOS/nixpkgs/3c614fbc76fc152f3e1bc4b2263da6d90adf80fb
Store paths:        /nix/store/3qcvhxxmgcdj6izfs8d3m8csdlsj92ng-curl-8.1.1-bin /nix/store/6skgivna073ziw95xvbksvd5c8n5vi5w-curl-8.1.1-man

Previously, the "keys" didn't exist in the output. This broke how we were parsing the output.

Fix in pseudo code:

try invoke: nix profile list --json
if error:
  do old code: nix profile list

the older nix versions don't support --json

How was it tested?

in some devbox projects:

# clear old state
> rm -rf .devbox

> devbox shell

# run some commands specific to that project

Copy link
Collaborator Author

savil commented Jul 25, 2023

Base automatically changed from savil/nix-profile-error to main July 25, 2023 19:54
@savil savil force-pushed the savil/nix-profile-error-debug branch 2 times, most recently from 4189b2a to e90ae04 Compare July 25, 2023 21:46
@savil savil changed the title print for debugging [nix profile list] parse using --json in new nix version Jul 25, 2023
@savil savil force-pushed the savil/nix-profile-error-debug branch from e90ae04 to 2fd0217 Compare July 25, 2023 21:49
@savil savil force-pushed the savil/nix-profile-error-debug branch from 2fd0217 to cf23256 Compare July 25, 2023 21:51

// versionRegex parses the output of `nix --version` to capture the semver version string
// example output: nix (Nix) 2.13.3
var versionRegex = regexp.MustCompile(`nix\s\(Nix\)\s([0-9]+.[0-9]+.[0-9]+)`)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit, you can use instead of \s

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should make this more forgiving. the second term should be (.*) with trimmed space?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And then you can use strings.TrimPrefix("nix (Nix)") to make this code simpler.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, I'll switch to TrimPrefix

) (map[string]*NixProfileListItem, error) {

lines, err := nix.ProfileList(writer, profileDir)
version, err := nix.Version()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't return error here. That puts us at a risk of failing even when we would do correct thing.

Copy link
Contributor

@mikeland73 mikeland73 Jul 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(e.g. what if in some past version nix --version returned something weird, we're better off just trying with useJSON = false)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure I follow. What do you mean "failing even when we do correct thing?"

In general, I'd prefer erroring here if we fail to get the nix version properly. Otherwise, we'll error below with a more obscure error...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we fail to read the version and ignore the error, we can continue doing what we were doing before. Otherwise we'll have introduced a new error and potentially break old nix installations that were already working.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd just try running nix profile list --json and if it fails try nix profile list (without the flag). Then you don't need to do the version comparison stuff. Example with old Nix:

$ nix profile list --json
error: unrecognised flag '--json'
Try 'nix --help' for more information.
$ nix profile list       
0 flake:nixpkgs#legacyPackages.aarch64-darwin.fuse github:NixOS/nixpkgs/d0f2758381caca8b4fb4a6cac61721cc9de06bd9#legacyPackages.aarch64-darwin.fuse /nix/store/8pqmbsp8zkq8656k5yaravbaxixaaa3c-macfuse-stubs-4.4.1

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's a concrete example:

Imagine that in nix v2.1.0 running nix --version returns 2.1.0 without the nix (Nix) prefix. Then this function would return an error and we would stop execution. But in this case, version would be "" which would currently set useJSON to false. So it's a case where ignoring the error would cause correct execution but returning an error would cause execution to break.

We don't want to break any old versions of nix, so I think just assuming useJSON is false if we can't parse the version is the correct way to go.

Comment on lines 36 to 37
// if version is >= 2.17, we can use the json output
useJSON := vercheck.SemverCompare(version, "2.17") >= 0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use 2.17.0 (2.17 is technically not valid semver)

This defaults to false if version is not semver. Is that OK? (I think probably yes)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ugh... good point

let me check the behavior of this vercheck.SemverCompare for 2.17 like output.

btw, do we know if nix --version prints 2.17 or 2.17.0?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2.17.0

image

vercheck.SemverCompare seems to work because semver.parse can read versions missing the patch and defaults them to 0. But I'm not sure that a missing patch number is generally considered semver.

Comment on lines 67 to 68
unlockedReference: element.OriginalURL,
lockedReference: element.URL,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mikeland73 do we think I got this mapping right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it's not 1 to 1 on either locked or unlocked. I'm taking a look

@savil savil requested review from gcurtis, mikeland73 and ipince and removed request for gcurtis July 25, 2023 22:31
@savil savil marked this pull request as ready for review July 25, 2023 22:32
}

// if version is >= 2.17.0, we can use the json output
useJSON := vercheck.SemverCompare(version, "2.17.0") >= 0
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am somewhat concerned about this failing in an unexpected way. The underlying function call will return:

// An invalid semantic version string is considered less than a valid one.
// All invalid semantic version strings compare equal to each other.

According to https://lazamar.co.uk/nix-versions/?channel=nixpkgs-unstable&package=nix, there may be some prerelease versions of nix that have non-standard semver versions.

However, these prerelease versions also appear to be rather old, and not present in newer releases of nix. So, maybe this is okay?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is OK. non semver versions will be considered older which defaults to the non --json.

I think as a follow up after this PR we could:

(only use the --json flag if we're sure version is higher (e.g. both are semver)) and then try to parse things both ways (try one and if it fails try the other)

Copy link
Collaborator

@gcurtis gcurtis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. If we can get away with not needing to parse the version, then that would be nice.

) (map[string]*NixProfileListItem, error) {

lines, err := nix.ProfileList(writer, profileDir)
version, err := nix.Version()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd just try running nix profile list --json and if it fails try nix profile list (without the flag). Then you don't need to do the version comparison stuff. Example with old Nix:

$ nix profile list --json
error: unrecognised flag '--json'
Try 'nix --help' for more information.
$ nix profile list       
0 flake:nixpkgs#legacyPackages.aarch64-darwin.fuse github:NixOS/nixpkgs/d0f2758381caca8b4fb4a6cac61721cc9de06bd9#legacyPackages.aarch64-darwin.fuse /nix/store/8pqmbsp8zkq8656k5yaravbaxixaaa3c-macfuse-stubs-4.4.1

@savil savil requested a review from gcurtis July 25, 2023 22:51
@savil
Copy link
Collaborator Author

savil commented Jul 25, 2023

@gcurtis @mikeland73 PTAL: simplified to "try with --json" and "fallback to non-json way upon any error"

}

output, err = nix.ProfileList(writer, profileDir, false /*useJSON*/)
if err == nil {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ugh, this should be err != nil

@savil savil merged commit 2cfe2cf into main Jul 25, 2023
@savil savil deleted the savil/nix-profile-error-debug branch July 25, 2023 23:54
mikeland73 added a commit that referenced this pull request Jul 26, 2023
## Summary

Since we saw CLI format change (See
#1308) we should test against
many nix versions.

Possible followup: In `main` should we run all nix versions?

## How was it tested?
CICD
@sentry-io
Copy link

sentry-io bot commented Aug 1, 2023

Suspect Issues

This pull request has been deployed and Sentry has observed the following issues:

  • ‼️ **exec.Error: <redacted errors.withStack>: error running "nix profile list": <redacted exec.Error>: <redacted... go.jetpack.io/devbox/internal/nix in ProfileList View Issue
  • ‼️ **exec.Error: <redacted errors.withStack>: error running "nix profile list": <redacted exec.Error>: <redacted... go.jetpack.io/devbox/internal/nix in ProfileList View Issue
  • ‼️ **exec.ExitError: <redacted errors.withStack>: <redacted errors.withStack>: error running "nix profile list": <re... go.jetpack.io/devbox/internal/nix in ProfileList View Issue
  • ‼️ **exec.ExitError: <redacted errors.withStack>: error running "nix profile list": <redacted exec.ExitError> go.jetpack.io/devbox/internal/nix in ProfileList View Issue
  • ‼️ *exec.ExitError: error running command in Devbox: <redacted errors.withStack>: error running "nix profile list": ... go.jetpack.io/devbox/internal/nix in ProfileList View Issue

Did you find this useful? React with a 👍 or 👎

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

3 participants