Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support greater than/less than versions for packages in treefile. #2151

Conversation

kelvinfan001
Copy link
Member

Add a helper function for whitespace_split_packages() so that it now splits a String by whitespace only if it is not wrapped between single quotes.
This should allow RHCOS to use syntax like podman > 1.4 in the treefile.

Copy link
Member

@cgwalters cgwalters left a comment

Choose a reason for hiding this comment

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

Overall looks like a great start, thanks so much for taking this on!

rust/src/treefile.rs Outdated Show resolved Hide resolved
let mut to_whitespace_split: Vec<String> = vec![];
let mut start_index = 0;
let mut looping_over_quoted_pkg = false;
for (i, c) in element.chars().enumerate() {
Copy link
Member

Choose a reason for hiding this comment

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

I'm OK if we try to handle the fully general case but...I would also be totally fine if we just required that users only include a single package on a line if using version qualifiers.

IOW,

packages:
  - kernel grub2
  - "'selinux-policy > 1.5'"
  - "'podman > 2.0'"

but not:

packages:
  - kernel grub2
  - "'selinux-policy > 1.5' 'podman > 2.0'"

Copy link
Member

Choose a reason for hiding this comment

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

If if we make that a requirement, we can simply check:

if element.starts_with('\'') {
   ...
}

which would simplify all of this code a lot.

Copy link
Member Author

Choose a reason for hiding this comment

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

It would indeed. I consulted with Jonathan about this and he recommended solving for the general case. I originally had a hacky solution using Rust's library that would shorten the code by a lot, but it felt a bit unintuitive and difficult to understand what was going on.

Copy link
Member

Choose a reason for hiding this comment

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

I consulted with Jonathan about this and he recommended solving for the general case

There might've a misunderstanding. It's 100% fine with me if you want to go the easy route (I initially suggested it myself in #2138 (comment)). That said, if you feel like tackling the generic case, that's totally cool with me too! :)

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, I see. I'm okay with either way, as well :)
I'll leave it as is for now, but let me know if I should change it!

Copy link
Member

Choose a reason for hiding this comment

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

If we're keeping the code as is I'd need to think hard about whether it's correct; it looks plausible - the unit tests would help a lot too particularly if we have failure conditions like missing quotes, etc.

If we only handle the simple case I'd be much more confident it's correct.


echo gpgcheck=0 >> yumrepo.repo
ln "$PWD/yumrepo.repo" config/yumrepo.repo
# the top-level manifest doesn't have any packages, so just set it
treefile_append "packages" '["foobar"]'
treefile_append "packages" $'["\'foobar >= 0.5\' quuz \'corge < 1.0.0\'"]'
Copy link
Member

Choose a reason for hiding this comment

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

The quoting required here is... 🤯
It has to get through bash, then Python, then YAML, then rpm-ostree...

tests/compose/test-misc-tweaks.sh Outdated Show resolved Hide resolved
@kelvinfan001 kelvinfan001 changed the title WIP: Support greater than/less than versions for packages in treefile. Support greater than/less than versions for packages in treefile. Jun 29, 2020
Copy link
Member

@zonggen zonggen left a comment

Choose a reason for hiding this comment

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

Some feedback from my side:

  • Can we assume the correctness of the package lists? e.g. can I assume there's no incorrect use of single quotes such as 'foo bar baz, foo bar baz' and foo' 'bar baz in the package lists. The last case foo' 'bar baz in particular will result in an empty string in the quoted_pkgs. It might be helpful to check for this case and trim the empty string in quoted_pkgs.
  • It might be helpful to wrap the return value of both functions (whitespace_split_packages and split_whitespace_unless_quoted) in a Result<>, and report/return error if necessary.
  • For commit messages it will be more clear to add the origin of the change at the start, e.g. treefile: Support greater than/less than versions for packages in treefile and no period at the end of subject line

@zonggen
Copy link
Member

zonggen commented Jun 29, 2020

Overall looks really good, and nice work on learning and working on Rust code 😄 .

@kelvinfan001
Copy link
Member Author

kelvinfan001 commented Jun 30, 2020

Thanks, @zonggen, for the helpful comments! I guess I have the same question as Allen: @cgwalters, can we assume that there's no incorrect usage of quotes in the treefile? If not, I'm happy to implement checks for whether there are missing/improper quotes and return errors if necessary in the functions.

@jlebon
Copy link
Member

jlebon commented Jun 30, 2020

IMO, bad quoting should be hard errors because it likely implies someone mistyped something.

@kelvinfan001 kelvinfan001 force-pushed the greater-than-less-than-packages-treefile branch from aa38a1d to d5e653f Compare June 30, 2020 20:56
@kelvinfan001
Copy link
Member Author

IMO, bad quoting should be hard errors because it likely implies someone mistyped something.

sorry I didn't quite get what you meant by incorrect quoting should be hard errors. Would you suggest that I modify the helper functions such that they return errors when things are misquoted? Also, I'm not sure if I did the unit tests correctly; I added a couple tests that show that nothing breaks when a user misquotes some packages, and show that it just leads to weird "packages" like ' ', or '<=', etc.

@@ -576,10 +576,42 @@ impl TreefileExternals {
/// array elements.
fn whitespace_split_packages(pkgs: &[String]) -> Vec<String> {
pkgs.iter()
.flat_map(|pkg| pkg.split_whitespace().map(String::from))
.flat_map(|element| split_whitespace_unless_quoted(element).map(String::from))
Copy link
Member

Choose a reason for hiding this comment

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

BTW you can drop the .map(String::from) now because the function is already returning String and not &str.

// and returns split Strings in an Iterator, with quoted packages first.
fn split_whitespace_unless_quoted(element: &String) -> impl Iterator<Item = String> {
let mut quoted_pkgs: Vec<String> = vec![];
let mut to_whitespace_split: Vec<String> = vec![];
Copy link
Member

Choose a reason for hiding this comment

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

You can likely drop the : Vec<String> and let type inference handle it.

start_index = i + 1;
}
if i == element.len() - 1 {
to_whitespace_split.push(String::from(&element[start_index..]));
Copy link
Member

Choose a reason for hiding this comment

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

And related to the above...I think you can also drop String::from here and build up instead a Vec<&str> - i.e. pointers into the original element. This saves intermediate allocations.

Performance doesn't matter here really - I'm mostly saying this because a powerful thing about Rust is you can fearlessly build up pointers into other allocated data, not duplicating and not worry about double frees/invalid pointers etc.

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh that's a great point! Thanks :) It's cool how split_whitespace_unless_quoted() can actually just work on the borrowed String and not have to allocate anything at all if we change the return type in this function and keep the .map(String::from) in the outer function.

}
}
let mut ret: Vec<String> = vec![];
ret.extend(quoted_pkgs);
Copy link
Member

Choose a reason for hiding this comment

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

Let's move this to the top - i.e. directly push into ret rather than having a separate vector.

Copy link
Member Author

Choose a reason for hiding this comment

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

Right, this would make a lot more sense.

if i == element.len() - 1 {
to_whitespace_split.push(String::from(&element[start_index..]));
}
}
Copy link
Member

Choose a reason for hiding this comment

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

And then here something like:

if looping_over_quoted_pkg {
  bail!("Missing terminating quote: {}", element);
}

fn test_split_whitespace_unless_quoted() {
// test single quoted package
let single_quoted_pkg = String::from("'foobar >= 1.0'");
let pkgs: Vec<String> = split_whitespace_unless_quoted(&single_quoted_pkg).collect();
Copy link
Member

Choose a reason for hiding this comment

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

Also it would make sense for split_whitespace_unless_quoted to take an &str and not a &String - that way the test cases like this can just pass a static pointer rather than an allocated string.

@@ -1226,6 +1259,66 @@ etc-group-members:
== "table"
);
}

#[test]
fn test_split_whitespace_unless_quoted() {
Copy link
Member

Choose a reason for hiding this comment

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

If we change split_whitespace_unless_quoted to be -> Result<impl Iterator<Item = String>>, then I'd change this function to be -> Result<()> too.


// test multiple quoted packages
let mult_quoted_pkg = String::from("'foobar >= 1.0' 'quuz < 0.5' 'corge > 2'");
let pkgs: Vec<String> = split_whitespace_unless_quoted(&mult_quoted_pkg).collect();
Copy link
Member

Choose a reason for hiding this comment

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

Then once it returns Result<> this call would be:
let pkgs: Vec<String> = split_whitespace_unless_quoted(&mult_quoted_pkg)?.collect();
(Notice the added ?)

let missing_quotes = String::from("foobar >= 1.0 quuz");
let pkgs: Vec<String> = split_whitespace_unless_quoted(&missing_quotes).collect();
assert_ne!("foobar >= 1.0", pkgs[0]);
assert_eq!(">=", pkgs[1]);
Copy link
Member

Choose a reason for hiding this comment

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

Then these tests would be:
assert!(split_whitespace_unless_quoted(&missing_quotes).is_err())

@cgwalters
Copy link
Member

/approve

@jlebon
Copy link
Member

jlebon commented Jul 2, 2020

sorry I didn't quite get what you meant by incorrect quoting should be hard errors. Would you suggest that I modify the helper functions such that they return errors when things are misquoted?

Yeah, essentially rpm-ostree compose tree should error out if a treefile package entry is something like foo'bar. Though... hmm I guess we could simply rely on the fact that no package matching that name will be found. But a "found quoting issues" type error would be clearer.

let single_quoted_pkg = String::from("'foobar >= 1.0'");
let pkgs: Vec<String> = split_whitespace_unless_quoted(&single_quoted_pkg).collect();
let single_quoted_pkg = "'foobar >= 1.0'";
let pkgs: Vec<&str> = split_whitespace_unless_quoted(&single_quoted_pkg)?.collect();
Copy link
Member

Choose a reason for hiding this comment

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

This is completely fine as is, but you can also just use:
let pkgs: Vec<_> = ...
Here the .collect() call needs to know that we're collecting into a Vec<>, but type inference can take over for there and know it must be &str because that's what split_whitespace_unless_quoted() returns.

This is totally fine as is though!

Copy link
Member Author

@kelvinfan001 kelvinfan001 Jul 2, 2020

Choose a reason for hiding this comment

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

Thanks :) Hopefully I'll get the hang of type inference and make better use of it soon. Still in "playing it safe" mode.

Copy link
Member

Choose a reason for hiding this comment

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

Absolutely! I'm writing this feedback not because there's any real problem with the code, but because it's a useful way to help learn!

@cgwalters
Copy link
Member

I think we'll need to wait until CoreOS CI is up to merge, but LGTM!

@kelvinfan001 kelvinfan001 force-pushed the greater-than-less-than-packages-treefile branch from d82a5df to 3bf8e02 Compare July 2, 2020 22:35
@kelvinfan001
Copy link
Member Author

I think we'll need to wait until CoreOS CI is up to merge, but LGTM!

@cgwalters Thanks so much for the detailed feedback! Learned a lot in the process. I'll merge when CI is up :)

Copy link
Member

@jlebon jlebon left a comment

Choose a reason for hiding this comment

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

LGTM as well overall, nice work! Just some comments, but none of them are blockers. :)

}
}
for item in to_whitespace_split.iter() {
ret.extend(item.split_whitespace());
Copy link
Member

Choose a reason for hiding this comment

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

Couldn't we just get rid of to_whitespace_split and just do the ret.extend() directly in the loop above as we encounter it? This also has the property of maintaining the order of packages (it doesn't matter much to rpm-ostree/libdnf really, but it'd make writing the unit tests below slightly easier).

Copy link
Member Author

Choose a reason for hiding this comment

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

This would make a lot more sense, thanks!

@@ -1226,6 +1263,65 @@ etc-group-members:
== "table"
);
}

#[test]
fn test_split_whitespace_unless_quoted() -> Result<()> {
Copy link
Member

Choose a reason for hiding this comment

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

Nice, love all the unit tests! :)


echo gpgcheck=0 >> yumrepo.repo
ln "$PWD/yumrepo.repo" config/yumrepo.repo
# the top-level manifest doesn't have any packages, so just set it
treefile_append "packages" '["foobar"]'
treefile_append "packages" $'["\'foobar >= 0.5\' quuz \'corge < 1.0.0\'"]'
Copy link
Member

Choose a reason for hiding this comment

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

Hmm, I think a more convincing example is building e.g. two corge packages, one at 1.0 and one at 2.0, then have the request be for cargo < 2.0 and verify that corge-1.0 is the one that got pulled in.

@kelvinfan001 kelvinfan001 force-pushed the greater-than-less-than-packages-treefile branch from 89c09e3 to b9fc51b Compare July 3, 2020 19:41
@jlebon
Copy link
Member

jlebon commented Jul 3, 2020

/approve

(Let's wait to /lgtm until CI is fully back up because Prow on its own doesn't run the full tests.)

@jlebon
Copy link
Member

jlebon commented Jul 6, 2020

/lgtm

Add a helper function for whitespace_split_packages() so that it now
splits a String by whitespace only if it is not wrapped between single
quotes.

This should allow RHCOS to use syntax like podman > 1.4 in the treefile.

Also add new unit tests and tweak existing compose tests to test this
functionality.
@cgwalters cgwalters force-pushed the greater-than-less-than-packages-treefile branch from b9fc51b to 408a423 Compare July 8, 2020 17:05
@cgwalters
Copy link
Member

(Rebased this to retrigger the latest CI)
/lgtm

@openshift-ci-robot
Copy link
Collaborator

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: cgwalters, jlebon, kelvinfan001

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-merge-robot openshift-merge-robot merged commit 4d836dd into coreos:master Jul 8, 2020
jlebon added a commit to jlebon/rpm-ostree that referenced this pull request Jul 29, 2020
Thin release. Mostly for a bunch of RHCOS-relevant fixes and
enhancements: 752f4f0 (coreos#2178), 4d836dd (coreos#2151) and f608eb0 (coreos#2158).
@jlebon jlebon mentioned this pull request Jul 29, 2020
openshift-merge-robot pushed a commit that referenced this pull request Jul 29, 2020
Thin release. Mostly for a bunch of RHCOS-relevant fixes and
enhancements: 752f4f0 (#2178), 4d836dd (#2151) and f608eb0 (#2158).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants