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

(PUP-4191,PUP-4203) Use execute function to run gem uninstall #3708

Conversation

queeno
Copy link
Contributor

@queeno queeno commented Mar 12, 2015

When attempting to uninstall a gem using a custom gem provider inheriting gem.rb, the wrong gemcmd is issued.

For example, the sensu-puppet module introduces embedded ruby gem support through a custom sensu_gem provider, inheriting puppet's gem.rb.

In sensu-gem, the :gemcmd is declared to match the embedded sensu gem path:
commands :gemcmd => "/opt/sensu/embedded/bin/gem"

When the following resource is run:

package { 'sensu-plugin':
  ensure   => absent,
  provider => 'sensu_gem',
}

/usr/bin/gem uninstall -x -a sensu-plugin is executed rather than
/opt/sensu/embedded/bin/gem uninstall -x -a sensu-plugin.

I noticed that in gem.rb the install and self.gemlist methods build a command array and pass it to the execute function. This results in using the right gemcmd.

This PR makes the uninstall method behave just like the self.gemlist and install methods.
An array of arguments is built and passed to the execute function, which runs the right command and returns the output. Since some versions of gem may return exit code 0 on error, a hard failure is triggered if the output contains the word 'ERROR'. This is compliant with the install method behaviour.

@puppetcla
Copy link

Waiting for CLA signature by @queeno

@queeno - We require a Contributor License Agreement (CLA) for people who contribute to Puppet, but we have an easy click-through license with instructions, which is available at https://cla.puppetlabs.com/

Note: if your contribution is trivial and you think it may be exempt from the CLA, please post a short reply to this comment with details. http://docs.puppetlabs.com/community/trivial_patch_exemption.html

@queeno
Copy link
Contributor Author

queeno commented Mar 12, 2015

@puppetcla CLA signed.

@puppetcla
Copy link

CLA signed by all contributors.

@@ -122,7 +122,12 @@ def query
end

def uninstall
gemcmd "uninstall", "-x", "-a", resource[:name]
command = [command(:gemcmd), "uninstall"]
command << "-x" << "-a" << resource[:name]
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you expand these flags to make the command more clear? (--executables and --all)

@melissa
Copy link
Contributor

melissa commented Mar 12, 2015

@queeno thank you for your contribution!

Could you add tests to verify this behavior?

@melissa
Copy link
Contributor

melissa commented Mar 12, 2015

I'm also not sure if this is the place to bring this up, but this might be a good opportunity to add in uninstall_options to this provider. Something along the lines of

diff --git a/lib/puppet/provider/package/gem.rb b/lib/puppet/provider/package/gem.rb
index 301cfd2..ebb88f0 100644
--- a/lib/puppet/provider/package/gem.rb
+++ b/lib/puppet/provider/package/gem.rb
@@ -8,11 +8,12 @@ Puppet::Type.type(:package).provide :gem, :parent => Puppet::Provider::Package d
     interpreted as the path to a local gem file.  If source is not present at all,
     the gem will be installed from the default gem repositories.

-    This provider supports the `install_options` attribute, which allows command-line flags to be passed to the gem command.
+    This provider supports the `install_options` and `uninstall_options` attribute, which
+    allows command-line flags to be passed to the gem command.
     These options should be specified as a string (e.g. '--flag'), a hash (e.g. {'--flag' => 'value'}),
     or an array where each element is either a string or a hash."

-  has_feature :versionable, :install_options
+  has_feature :versionable, :install_options, :uninstall_options

   commands :gemcmd => "gem"

@@ -124,8 +125,10 @@ Puppet::Type.type(:package).provide :gem, :parent => Puppet::Provider::Package d
   def uninstall
     command = [command(:gemcmd), "uninstall"]
     command << "-x" << "-a" << resource[:name]
+
+    command += uninstall_options if resource[:uninstall_options]
     output = execute(command)
-
+
     # Apparently some stupid gem versions don't exit non-0 on failure
     self.fail "Could not uninstall: #{output.chomp}" if output.include?("ERROR")
   end
@@ -137,4 +140,8 @@ Puppet::Type.type(:package).provide :gem, :parent => Puppet::Provider::Package d
   def install_options
     join_options(resource[:install_options])
   end
+
+  def uninstall_options
+    join_options(resource[:uninstall_options])
+  end
 end

@queeno
Copy link
Contributor Author

queeno commented Mar 12, 2015

No problem @melissa, it's a pleasure contributing to such an amazing product.

I think all your suggestions make sense, will work on them tomorrow morning 😄

@@ -122,7 +122,12 @@ def query
end

def uninstall
gemcmd "uninstall", "-x", "-a", resource[:name]
command = [command(:gemcmd), "uninstall"]
Copy link
Contributor

Choose a reason for hiding this comment

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

It is unfortunate that you have to do this... When you declare a command in your provider, puppet automatically creates a method, e.g. gemcmd, which ultimately calls into Puppet::Util::Execution.execute to execute the command. Unfortunately, it appears sensu_gem is able to override the value of command(:gem), but not the path used in gemcmd. So it's surprising when you use one vs the other:

0 ~/work/puppet (master)  $ bundle exec irb
...
irb(main):012:0> require 'puppet'
=> true
irb(main):013:0> Puppet::Type.type(:package).provide :sensu_gem, :parent => :gem do
irb(main):014:1*   desc "Sensu Embedded Ruby Gem support. If a URL is passed via `source`, then
irb(main):015:1"     that URL is used as the remote gem repository; if a source is present but is
irb(main):016:1"     not a valid URL, it will be interpreted as the path to a local gem file.  If
irb(main):017:1"     source is not present at all, the gem will be installed from the default gem
irb(main):018:1"     repositories."
irb(main):019:1>
irb(main):020:1*   has_feature :versionable, :install_options
irb(main):021:1>
irb(main):022:1*   commands :gemcmd => "/opt/sensu/embedded/bin/gem"
irb(main):023:1> end
=> Puppet::Type::Package::ProviderSensu_gem
...
irb(main):028:0> provider = Puppet::Type.type(:package).provider(:sensu_gem)
=> Puppet::Type::Package::ProviderSensu_gem
irb(main):029:0> provider.command(:gemcmd)
=> nil

This is because I don't have /opt/sensu/embedded/bin/gem, so this is returning the overridden value as expected. But

irb(main):030:0> provider.gemcmd
Puppet::ExecutionFailure: Execution of '/Users/josh/.rbenv/versions/2.1.5/bin/gem' returned 1: RubyGems is a sophisticated package manager for Ruby.

Is executing using the gem command from the base class.

So I'm 👍 with the changes, just sad puppet is not consistent in how commands are overridden.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@joshcooper I totally agree with you.

@queeno
Copy link
Contributor Author

queeno commented Mar 13, 2015

@melissa I believe I addressed all the issues raised.
I introduced uninstalled_options and I expanded the gem.rb unit tests, so we now test for uninstalling gems as well.

@kylog
Copy link

kylog commented Mar 13, 2015

This all seems like goodness to me (thanks @queeno!), but this feels like two different tickets/PRs:

  1. fix uninstall
  2. add uninstall_options
    I think that separation will be clearer to someone who bumps into the 'gem uninstall' bug, since they would only be interested in the bugfix addressed in the original commit of this PR.

Thoughts?

@melissa
Copy link
Contributor

melissa commented Mar 13, 2015

@kylog would separate commits be acceptable? It's a nice addition to have, even if it isn't necessarily related to the original topic of this PR.

@kylog
Copy link

kylog commented Mar 13, 2015

Yes, definitely!

Sorry I wasn't clear. Both the bug fix and the feature add of install_options are definitely nice additions.

However, IMHO it's clearer to a future reader (e.g. someone who is interested in just the bugfix or just the addition of install_options) if there are two tickets and if each commit maps to just one of the two tickets. (I don't really care if it's separate PRs although that's what I suggested above.)

Does that make any sense? It's Friday afternoon so I may be uttering gibberish ;)

@melissa
Copy link
Contributor

melissa commented Mar 13, 2015

Cool! My bad for tacking things on. @queeno apologies for giving you the extra work. Feel free to take the :uninstall_options work out if you want to. I can add that in later in that case. Otherwise, if you want to reference https://tickets.puppetlabs.com/browse/PUP-4203 for that work, that'd be awesome!

@queeno
Copy link
Contributor Author

queeno commented Mar 13, 2015

@kylog I know exactly how that feels. It's Friday night here in London and I've already gone into the next phase: I'm totally out of control 😛

I wouldn't mind opening a new PR for the install_options addition, however I would then need to rebase it once this PR is merged. I quite agree with @melissa, let's keep this nice and simple in this PR.

Thanks @melissa for opening a new issue in Puppet Jira for me. Will make the necessary changes in my branch and report back.

@queeno queeno force-pushed the use_execute_function_to_run_gem_uninstall branch from 228bd8b to 44ab6b2 Compare March 13, 2015 22:36
@queeno
Copy link
Contributor Author

queeno commented Mar 13, 2015

@kylog @melissa all is well 👍
I've updated the commits and the relative jira issue PUP-4203.

Fingers crossed for the Travis build!

If you can't spot anything else, this PR should be good to go 😄

@queeno queeno changed the title (PUP-4191) Use execute function to run gem uninstall (PUP-4191,PUP-4203) Use execute function to run gem uninstall Mar 13, 2015
@kylog
Copy link

kylog commented Mar 14, 2015

Last nit: can you put your commit messages in the imperative? (https://github.com/puppetlabs/puppet/blame/master/CONTRIBUTING.md#L53 for example/rationale.)

Other than that I'm 👍

Thanks!!

@queeno queeno force-pushed the use_execute_function_to_run_gem_uninstall branch from 44ab6b2 to d8f23dd Compare March 14, 2015 21:32
queeno added 5 commits March 14, 2015 21:34
The gemcmd method in gem.rb uninstall runs the wrong command when invoked
by a custom provider, which also overrides the gemcmd path. For this
reason, command(:gemcmd) is used to retrieve the right path, which is
then passed to the execute method with the relevant parameters for
execution.
The gem uninstall flags (-x, -a) used in the gem.rb uninstall method do
not inform the user about their behaviour. This commit expands their
names for readibility purposes.
The gem_spec unit test does not allow tests for scenarios other than
installing gems. This commit segregates the tests in an rspec context in
order to tests for gem removal.
No unit tests are in place to check the uninstall behaviour of the
gem provider. This commit adds unit tests to verify the gem.rb
uninstall method functionality.
A user cannot pass extra flags to the gem provider in order to uninstall
ruby gems. The uninstall method in the gem provider now consumes the
uninstall_options attribute of the package resource, which may be used
to specify extra flags to uninstall ruby gems.
@queeno queeno force-pushed the use_execute_function_to_run_gem_uninstall branch from d8f23dd to 78f8308 Compare March 14, 2015 21:37
@queeno
Copy link
Contributor Author

queeno commented Mar 14, 2015

No problem @kylog!
I've just rewritten my commit messages, hope they're compliant with the guidelines now 😄

@kylog
Copy link

kylog commented Mar 14, 2015

One last imperative: can you amend 78f8308 so it's first line reads "(PUP-4203) Add unit tests for gem.rb uninstall_options"? Thanks!

Btw, timing wise I should mention that we're holding off on merges for a couple days while we tidy up our initial 4.0.0 release candidate. But after we open master back up for merges, this looks good. Thanks!!

The newly introduced uninstall_option functionality in the gem provider
uninstall method is untested. Unit tests have been added in order to test
this new functionality.
@queeno queeno force-pushed the use_execute_function_to_run_gem_uninstall branch from 78f8308 to dd21713 Compare March 14, 2015 23:06
@queeno
Copy link
Contributor Author

queeno commented Mar 14, 2015

Ooops... no problem! 😄
All done and Travis passed! Please let me know if you spot anything else.

Thanks @kylog for your time reviewing this PR!

@kylog
Copy link

kylog commented Mar 18, 2015

👍

@melissa
Copy link
Contributor

melissa commented Mar 30, 2015

Merged in at a018038

@melissa melissa closed this Mar 30, 2015
@MikaelSmith
Copy link
Contributor

We manually merged with git rebase master --whitespace=fix to clean up some trailing whitespaces.

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

Successfully merging this pull request may close these issues.

7 participants