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

fix(terraform): update less-than/less-than/equals version constraints #8983

Merged
merged 2 commits into from
Dec 16, 2024

Conversation

bryan-bar
Copy link
Contributor

Issue:
When using the terraform dependency constraints less-than or less-than/equals to set a max version, only the largest semvar version number is updated. This allows the provider version to be set to a non-existent version.

example:
Latest provider version: 0.7.0
Provider version suggested changed: 0.6.1 -> 0.7.1

fixes #8959


Fix:
Update each version index to tighten the constraints:

  • less-than/equals allows provider versions up to the latest version or latest requirement
  • less-than allows for a provider version up the latest minus 1 against the smallest defined semvar number.

@bryan-bar bryan-bar requested a review from a team as a code owner February 5, 2024 14:26
@github-actions github-actions bot added the L: terraform Terraform packages label Feb 5, 2024
@bryan-bar
Copy link
Contributor Author

bryan-bar commented Jul 20, 2024

Hello @caspermeijn @bdragon @sachin-sandhu,

If I could get a reviewer set on this PR or comments on how to move forward with this fix please.

It is similar to the versioning issue with cargo in #9828

@bryan-bar bryan-bar changed the title Update terraform less-than/less-than/equals constraints fix(terraform): update less-than/less-than/equals version constraints Jul 20, 2024
@abdulapopoola
Copy link
Member

Thanks @bryan-bar , can you please resolve the conflicts and we can get this merged. Thanks for fixing this.

else
0
end
version_to_be_permitted.segments[index]
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe the original implementation of update_greatest_version was correct for "<" requirements, but "<=" requirements need to be handles differently.

This is what I did for cargo:

def update_range_requirements(string_reqs)
string_reqs.map do |req|
next req unless req.match?(/[<>]/)
ruby_req = Cargo::Requirement.new(req)
next req if ruby_req.satisfied_by?(target_version)
raise UnfixableRequirement if req.start_with?(">")
req.sub(VERSION_REGEX) do |old_version|
if req.start_with?("<=")
update_version_string(old_version)
else
update_greatest_version(old_version, target_version)

I think you need to focus on update_range for handling "<" and "<=" differently.

Copy link
Contributor Author

@bryan-bar bryan-bar Oct 17, 2024

Choose a reason for hiding this comment

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

@caspermeijn
The less-than operator should still be incremented. I updated the PR to handle both < and <=.

The issue with the function is that < also returned an inconsistent version so I chose to update it.
For example, < 0.3.0 with a latest version of 0.3.7 would return as < 0.4.0
Change the initial version to < 0.3.2 with a latest version of 0.3.7 and the returned version changed to < 0.3.8

It seems index_to_update is setting the wrong index to be incremented and then anything after is set to 0 or skipped over.

Another issue is that terraform does not have wild cards and it also did not expand the version when it has less than 3 segments like it does for some of cargos operators.
For less-than/equals, that would mean incrementing the version and then restricting it (example assuming current version <= 1.6, new version 1.6.5 -> <=1.7, !=1.7) or expanding the version as is ( <= 1.6.5). Another option would be to set a really high version <= 1.6.9999 or converting it to less than < 1.7.

Copy link
Contributor Author

@bryan-bar bryan-bar Oct 18, 2024

Choose a reason for hiding this comment

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

@caspermeijn @abdulapopoola
Also note that Terraform treats non-semantic versions as being expanded out internally. 0.2 == 0.2.0 | 1 == 1.0.0
I added this comment in the code as well.

@Zawadidone
Copy link
Contributor

@bryan-bar are you working on this PR?

@bryan-bar
Copy link
Contributor Author

@bryan-bar are you working on this PR?

Yes, thanks for the reminder. I just updated the PR.

@bryan-bar bryan-bar requested a review from caspermeijn October 17, 2024 13:10
@bryan-bar bryan-bar force-pushed the max-version branch 7 times, most recently from bb6c535 to 556aa41 Compare October 23, 2024 17:39
Copy link
Contributor

@caspermeijn caspermeijn left a comment

Choose a reason for hiding this comment

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

Great, that you can make this work with a simple code addition. I like the additional tests.

The code looks good to me. I don't have any terraform experience, so I can't confirm this is the behavior that is required for terraform.

# When 'less than'/'<',
# increment the last available segment only so that the new version is within the constraint
if op == "<"
new_segments = version.segments.map.with_index do |_, index|
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't fully understand why the old code was not sufficient. Does the old code fail the new tests?

The new code is simpler and seems to do the trick.

Copy link
Contributor Author

@bryan-bar bryan-bar Nov 27, 2024

Choose a reason for hiding this comment

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

Yes, when removing b946d23 the test that fails in relation to the less-than/< operator is this test since it jumps a minor version to 0.4.0 instead of incrementing the patch version when a patch is set to 0, ">= 0.2.1, < 0.3.0, <= 0.3.0":

        context "when not satisfied, 0 patch version" do
          let(:requirement) { ">= 0.2.1, < 0.3.0, <= 0.3.0" }
          let(:latest_version) { "0.3.7" }

          its([:requirement]) { is_expected.to eq(">= 0.2.1, < 0.3.8, <= 0.3.7") }
        end

Error:

  17) Dependabot::Terraform::RequirementsUpdater#updated_requirements when there is a latest version when a =>,<,<= requirement was previously specified when not satisfied, 0 patch version [:requirement] is expected to eq ">= 0.2.1, < 0.3.8, <= 0.3.7"
      Failure/Error: its([:requirement]) { is_expected.to eq(">= 0.2.1, < 0.3.8, <= 0.3.7") }
      
        expected: ">= 0.2.1, < 0.3.8, <= 0.3.7"
             got: ">= 0.2.1, < 0.4.0, <= 0.4.0"
      
        (compared using ==)
      # ./spec/dependabot/terraform/requirements_updater_spec.rb:134:in `block (6 levels) in <top (required)>'
      # /home/dependabot/common/spec/spec_helper.rb:66:in `block (2 levels) in <top (required)>'
      # /home/dependabot/dependabot-updater/vendor/ruby/3.3.0/gems/webmock-3.23.1/lib/webmock/rspec.rb:39:in `block (2 levels) in <top (required)>'

This revealed a second issue where a non-zero patch would be incremented as expected, ">= 0.2.1, < 0.3.2, <= 0.3.2", besides the original <= issue, unlike the zero-patch version:

        context "when not satisfied, non-0 patch version" do
          let(:requirement) { ">= 0.2.1, < 0.3.2, <= 0.3.2" }
          let(:latest_version) { "0.3.7" }

          its([:requirement]) { is_expected.to eq(">= 0.2.1, < 0.3.8, <= 0.3.7") }
        end

Error:

  16) Dependabot::Terraform::RequirementsUpdater#updated_requirements when there is a latest version when a =>,<,<= requirement was previously specified when not satisfied, non-0 patch version [:requirement] is expected to eq ">= 0.2.1, < 0.3.8, <= 0.3.7"
      Failure/Error: its([:requirement]) { is_expected.to eq(">= 0.2.1, < 0.3.8, <= 0.3.7") }
      
        expected: ">= 0.2.1, < 0.3.8, <= 0.3.7"
             got: ">= 0.2.1, < 0.3.8, <= 0.3.8"
      
        (compared using ==)
      # ./spec/dependabot/terraform/requirements_updater_spec.rb:141:in `block (6 levels) in <top (required)>'
      # /home/dependabot/common/spec/spec_helper.rb:66:in `block (2 levels) in <top (required)>'
      # /home/dependabot/dependabot-updater/vendor/ruby/3.3.0/gems/webmock-3.23.1/lib/webmock/rspec.rb:39:in `block (2 levels) in <top (required)>'

@bryan-bar
Copy link
Contributor Author

bryan-bar commented Dec 2, 2024

Great, that you can make this work with a simple code addition. I like the additional tests.

The code looks good to me. I don't have any terraform experience, so I can't confirm this is the behavior that is required for terraform.

@caspermeijn @abdulapopoola
Who would be the proper person to tag for this PR?

@bryan-bar bryan-bar requested a review from caspermeijn December 2, 2024 17:43
@abdulapopoola
Copy link
Member

Thanks @bryan-bar , I'm tagging @randhircs so we can review this week and deploy it if it meets the bar.

…ndle both less than and less-than/equal operators

- 'index_to_update' would sometimes pick the middle or first segement
  instead of the last segment leading to the wrong version
segment being incremented
- less-than/equals would always get incremented instead of taking the
  version as-is
- minor or patch version would sometimes get set to 0 once the
  'index_to_update' was set
@randhircs randhircs merged commit 44b2066 into dependabot:main Dec 16, 2024
37 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
L: terraform Terraform packages
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Terraform manager applies non-existent version number when using less-than/equals (<=)
5 participants