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

pip resource: support non-default pip locations, such as virtualenvs #2097

Merged
merged 10 commits into from
Aug 30, 2017
Merged

pip resource: support non-default pip locations, such as virtualenvs #2097

merged 10 commits into from
Aug 30, 2017

Conversation

tonybaloney
Copy link
Contributor

@tonybaloney tonybaloney commented Aug 24, 2017

Fixes #516

Implements optional path to pip executable (like when virtualenv is used) instead of using system PATH.
Tested by installing newer version of Django in virtualenv and testing for it

describe pip('django', '/Users/anthonyshaw/repo/wayfinder/wayfinder/bin/pip') do
  it { should be_installed }
  its('version') { should eq('1.11.4')}
end
describe port(80) do
  it { should_not be_listening }
end

Before:

somepersonslaptop:inspec anthonyshaw$ bundle exec bin/inspec exec test.rb

Profile: tests from test.rb
Version: (not specified)
Target:  local://


  Pip Package
     ∅  django version should eq "1.11.4"

     expected: "1.11.4"
          got: nil

     (compared using ==)

  Port 80
     ✔  should not be listening

Test Summary: 1 successful, 2 failures, 0 skipped

After

somepersonslaptop:inspec anthonyshaw$ bundle exec bin/inspec exec test.rb

Profile: tests from test.rb
Version: (not specified)
Target:  local://


  Pip Package
     ✔  django should be installed
     ✔  django version should eq "1.11.4"
  Port 80
     ✔  should not be listening

Test Summary: 3 successful, 0 failures, 0 skipped

@tonybaloney tonybaloney requested a review from a team as a code owner August 24, 2017 08:39
@tonybaloney
Copy link
Contributor Author

sorry. Forgot DCO. I'll amend the commit

Copy link
Contributor

@srenatus srenatus 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. I've got some inline comments, also, an added test case would be awesome, too (https://github.com/chef/inspec/blob/master/test/unit/resources/pip_test.rb)

"

def initialize(package_name)
def initialize(package_name, virtualenv_path=nil)
Copy link
Contributor

Choose a reason for hiding this comment

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

[nit] I think virtualenv_path is a bit confusing here: From looking at this, I'd expect that I was to put /path/to/virtualenv in there, not /path/to/virtualenv/bin/pip. No idea what's the best approach, though.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

agreed. pip_path would make sense. OR having it test for the existence of @virtualenv_path + /bin/pip before executing.

@@ -75,7 +81,7 @@ def pip_cmd
return nil
end
end
pipcmd || 'pip'
pipcmd = @virtualenv_path.nil? ? 'pip' : @virtualenv_path
Copy link
Contributor

Choose a reason for hiding this comment

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

[nit] I think to keep the logic that was there before -- this would maybe better be something like this:

pipcmd || @virtualenv_path || 'pip'

So, the order of precedence would be: if the pip is found (windows), take that. Otherwise, take @virtualenv_path; fall back to 'pip'.

Makes me wonder if this shouldn't be like this, then:

@virtualenv_path || pipcmd || 'pip'

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@srenatus I updated it. let me know if the new one makes more sense? I'm new to this so feel free to [nit]

@@ -75,7 +81,7 @@ def pip_cmd
return nil
end
end
pipcmd || 'pip'
@virtualenv_path.nil? ? pipcmd || 'pip' : @virtualenv_path
Copy link
Contributor

Choose a reason for hiding this comment

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

So, if @virtualenv_path is nil, @virtualenv_path.nil? is true. However, @virtualenv_path will also be understood as "falsy" by ruby, so that line is equivalent to

@virtualenv_path || pipcmd || 'pip'

@tonybaloney
Copy link
Contributor Author

@srenatus decided to solve both problems. it now works with a directory (and adds /bin/pip to the path) and then the flow through logic is cleaner as well

describe pip('django') do
  it { should be_installed }
  its('version') { should eq('1.10.5')}
end
describe pip('django', '/Users/anthonyshaw/repo/wayfinder/wayfinder/') do
  it { should be_installed }
  its('version') { should eq('1.11.4')}
end
describe pip('django', '/Users/anthonyshaw/repo/wayfinder/wayfinder/bin/pip') do
  it { should be_installed }
  its('version') { should eq('1.11.4')}
end

Gives

somepersonslaptop:inspec anthonyshaw$ bundle exec bin/inspec exec test.rb

Profile: tests from test.rb
Version: (not specified)
Target:  local://


  Pip Package
     ✔  django should be installed
     ✔  django version should eq "1.10.5"
  Pip Package
     ✔  django should be installed
     ✔  django version should eq "1.11.4"
  Pip Package
     ✔  django should be installed
     ✔  django version should eq "1.11.4"

Test Summary: 6 successful, 0 failures, 0 skipped

@srenatus
Copy link
Contributor

@tonybaloney I'm not one of @chef/inspec-maintainers, but I'm sure they'd appreciate an added testcase or two? 😉

@adamleff adamleff changed the title Update pip resource for #516 allow user to set path to pip executable pip resource: support non-default pip locations, such as virtualenvs Aug 24, 2017
@adamleff adamleff added the Type: Enhancement Improves an existing feature label Aug 24, 2017
Copy link
Contributor

@adamleff adamleff left a comment

Choose a reason for hiding this comment

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

@tonybaloney welcome to the InSpec community and congratulations on your first contribution!

I've left you some suggestions in an effort to streamline the resource and provide better error messaging to our users.

As @srenatus mentioned, we should definitely add a test for when a user provides a custom pip path. The process would look something like this:

I'd love to help you with the tests if you have any questions or concerns about them. Just let me know.

Again, THANK YOU for contributing to InSpec! It's great to have you.

"

def initialize(package_name)
def initialize(package_name, virtualenv_path = nil)
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's change this variable to pip_path since you can technically install pip in a "non-PATH" location without using a virtualenv, and the examples given are actually paths to an actual pip command, not the top-level directory of the virtualenv.

@package_name = package_name
@virtualenv_path = virtualenv_path
Copy link
Contributor

Choose a reason for hiding this comment

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

See above for my comment re: virtualenv_path. Instead, I think we should set an instance variable that contains the validated path to pip, and change the #pip_cmd method name to be clearer... maybe resolve_pip_path?

So, assuming you take my advice to change the argument name to pip_path above, it would look something like this:

@pip_cmd = resolve_pip_path(pip_path)
return skip_resource "pip not found" if @pip_cmd.nil?

... and then resolve_pip_path would change a bit to support that. Does that make sense?

@@ -57,7 +64,14 @@ def to_s
def pip_cmd
# Pip is not on the default path for Windows, therefore we do some logic
# to find the binary on Windows
if inspec.os.windows?
if !@virtualenv_path.nil?
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a real good first attempt, @tonybaloney! I think it's a better user experience to just assume the user is going to give us a path to pip directory rather than adding logic to test if the path is a dir and then assume it's a virtualenv.

How about logic like this:

 - if user gave us a pip path, verify it exists, return nil if it doesn't, otherwise return what they gave us
 - if on windows, run the powershell to find the full pip path. Return nil if we can't find anything
 - Otherwise, assume it's just "pip", check to see if it exists with command('pip').exist?... return 'pip' if it does, return nil if it doesn't

Then, in initialize, we can call skip_resource if pip_cmd returns nil with helpful error messaging.

@tonybaloney
Copy link
Contributor Author

@adamleff those changes have all been done. However the tests are failing, I think it's because it's checking for the existence of either these fake files and, the pip command which probably isn't on the Docker image. Or I may have made some other mistake

@adamleff
Copy link
Contributor

@tonybaloney thanks for your patience! I just pushed up a commit to your branch to do a bit of refactoring in order to solve your unit test dilemma.

Here's what happened... the logic in resolve_pip_path was correct, but because you didn't mock out the internal commands InSpec would run to see if even the default pip command would exist in the PATH, the @pip_cmd variable had nil. This cause your pip show mocks to never run because instead of calling pip show django, InSpec was actually calling show django

The way I found this was to use the m gem/command to run a subset of the unit tests... the test output can be super noisy, so getting only the output from a test/file I care about is helpful. You can run these yourself like this:

bundle install
bundle exec m test/unit/resources/pip_test.rb

... this will run only the unit tests located in pip_test.rb - that's how I saw the "command not mocked" output that led me to that discovery.

Please take a look at the commit I sent up and let me know if you have any questions. Basically, I moved some of the default handling back up to initialize and moved any handling of an unfound command to only the skip_resource call. That way, the unit tests will still have a @pip_cmd of pip even if none is available.

If it looks good to you, and you're OK with it, I'll give this another review and we can work on getting this merged.

Thank you again for your help with this pull request!

@tonybaloney
Copy link
Contributor Author

all makes sense. I'll do some more reading before the next change
all ok to merge 👍

Copy link
Contributor

@adamleff adamleff left a comment

Choose a reason for hiding this comment

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

Super work, @tonybaloney! Thank you for your contribution, and congratulations on your first pull request to InSpec! Welcome to the community 🙂

Once another maintainer reviews and approves, we'll merge this in!

@adamleff adamleff requested a review from a team August 29, 2017 11:38
Copy link
Contributor

@arlimus arlimus left a comment

Choose a reason for hiding this comment

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

Amazing work @tonybaloney thank you so much!! 😁

@arlimus arlimus merged commit d5f33f0 into inspec:master Aug 30, 2017
@arlimus
Copy link
Contributor

arlimus commented Aug 30, 2017

And kudos @adamleff and @srenatus for all your help with the review!!

@tonybaloney tonybaloney deleted the pip_516 branch September 5, 2017 23:51
adamleff pushed a commit that referenced this pull request Jan 19, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Type: Enhancement Improves an existing feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants