Skip to content

Commit

Permalink
add onlyif && unless features
Browse files Browse the repository at this point in the history
  • Loading branch information
Zarne committed Aug 6, 2024
1 parent 1abd3c1 commit b50b6ca
Show file tree
Hide file tree
Showing 10 changed files with 971 additions and 32 deletions.
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ group :release do
gem 'voxpupuli-release', '~> 3.0', :require => false
end

gem 'pry'
gem 'rake', :require => false
gem 'facter', ENV['FACTER_GEM_VERSION'], :require => false, :groups => [:test]

Expand Down
88 changes: 88 additions & 0 deletions REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -632,13 +632,19 @@ Parameters
Examples
--------

#### Examples

#####

```puppet
archive::nexus { '/tmp/jtstand-ui-0.98.jar':
url => 'https://oss.sonatype.org',
gav => 'org.codehaus.jtstand:jtstand-ui:0.98',
repository => 'codehaus-releases',
packaging => 'jar',
extract => false,
}
```

#### Parameters

Expand Down Expand Up @@ -895,6 +901,72 @@ whether archive file should be present/absent (default: present)

Default value: `present`

##### `onlyif`

A test command that checks the state of the target system and restricts
when the `archive` can run. If present, Puppet runs this test command
first, and only runs the main command if the test has an exit code of 0
(success). For example:

```
archive { '/tmp/jta-1.1.jar':
ensure => present,
extract => true,
extract_path => '/tmp',
source => 'http://central.maven.org/maven2/javax/transaction/jta/1.1/jta-1.1.jar',
onlyif => 'test `java -version 2>&1 | awk -F '"' '/version/ {print $2}' | awk -F '.' '{sub("^$", "0", $2); print $1$2}'` -gt 15',
cleanup => true,
env_path => ["/bin", "/usr/bin", "/sbin", "/usr/sbin"],
}
```

Since this command is used in the process of determining whether the
`archive` is already in sync, it must be run during a noop Puppet run.

This parameter can also take an array of commands. For example:

onlyif => ['test -f /tmp/file1', 'test -f /tmp/file2'],

or an array of arrays. For example:

onlyif => [['test', '-f', '/tmp/file1'], 'test -f /tmp/file2']

This `archive` would only run if every command in the array has an
exit code of 0 (success).

##### `unless`

A test command that checks the state of the target system and restricts
when the `archive` can run. If present, Puppet runs this test command
first, then runs the main command unless the test has an exit code of 0
(success). For example:

```
archive { '/tmp/jta-1.1.jar':
ensure => present,
extract => true,
extract_path => '/tmp',
source => 'http://central.maven.org/maven2/javax/transaction/jta/1.1/jta-1.1.jar',
unless => 'test `java -version 2>&1 | awk -F '"' '/version/ {print $2}' | awk -F '.' '{sub("^$", "0", $2); print $1$2}'` -gt 15',
cleanup => true,
env_path => ["/bin", "/usr/bin", "/sbin", "/usr/sbin"],
}
```

Since this command is used in the process of determining whether the
`archive` is already in sync, it must be run during a noop Puppet run.

This parameter can also take an array of commands. For example:

unless => ['test -f /tmp/file1', 'test -f /tmp/file2'],

or an array of arrays. For example:

unless => [['test', '-f', '/tmp/file1'], 'test -f /tmp/file2']

This `archive` would only run if every command in the array has a
non-zero exit code.

#### Parameters

The following parameters are available in the `archive` type.
Expand All @@ -910,6 +982,8 @@ The following parameters are available in the `archive` type.
* [`digest_type`](#-archive--digest_type)
* [`digest_url`](#-archive--digest_url)
* [`download_options`](#-archive--download_options)
* [`env_path`](#-archive--env_path)
* [`environment`](#-archive--environment)
* [`extract`](#-archive--extract)
* [`extract_command`](#-archive--extract_command)
* [`extract_flags`](#-archive--extract_flags)
Expand Down Expand Up @@ -998,6 +1072,20 @@ archive file checksum source (instead of specifying checksum)

provider download options (affects curl, wget, gs, and only s3 downloads for ruby provider)

##### <a name="-archive--env_path"></a>`env_path`

The search path used for check execution.
Commands must be fully qualified if no path is specified. Paths
can be specified as an array or as a '

##### <a name="-archive--environment"></a>`environment`

An array of any additional environment variables you want to set for a
command, such as `[ 'HOME=/root', '[email protected]']`.
Note that if you use this to set PATH, it will override the `path`
attribute. Multiple environment variables should be specified as an
array.

##### <a name="-archive--extract"></a>`extract`

Valid values: `true`, `false`
Expand Down
142 changes: 128 additions & 14 deletions lib/puppet/provider/archive/ruby.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@

require 'securerandom'
require 'tempfile'
require 'puppet/util/execution'

require 'pry'
# This provider implements a simple state-machine. The following attempts to #
# document it. In general, `def adjective?` implements a [state], while `def
# verb` implements an {action}.
Expand Down Expand Up @@ -59,6 +61,7 @@
#

Puppet::Type.type(:archive).provide(:ruby) do
include Puppet::Util::Execution
optional_commands aws: 'aws'
optional_commands gsutil: 'gsutil'
defaultfor feature: :microsoft_windows
Expand Down Expand Up @@ -95,18 +98,6 @@ def tempfile_name
end
end

def creates
if resource[:extract] == :true
extracted? ? resource[:creates] : 'archive not extracted'
else
resource[:creates]
end
end

def creates=(_value)
extract
end

def checksum
resource[:checksum] || (resource[:checksum] = remote_checksum if resource[:checksum_url])
end
Expand All @@ -127,7 +118,7 @@ def remote_checksum
# returns boolean
def checksum?(store_checksum = true)
return false unless File.exist? archive_filepath
return true if resource[:checksum_type] == :none
return true if resource[:checksum_type] == :none

archive = PuppetX::Bodeco::Archive.new(archive_filepath)
archive_checksum = archive.checksum(resource[:checksum_type])
Expand Down Expand Up @@ -156,7 +147,7 @@ def extract
end

def extracted?
resource[:creates] && File.exist?(resource[:creates])
resource.check_all_attributes
end

def transfer_download(archive_filepath)
Expand Down Expand Up @@ -258,4 +249,127 @@ def optional_switch(value, option)
[]
end
end

# Verify that we have the executable
def checkexe(command)
exe = extractexe(command)
if Facter.value(:osfamily) == 'windows'
if absolute_path?(exe)
if !Puppet::FileSystem.exist?(exe)
raise ArgumentError, _("Could not find command '%{exe}'") % { exe: exe }

Check failure on line 259 in lib/puppet/provider/archive/ruby.rb

View workflow job for this annotation

GitHub Actions / Puppet / Static validations

Style/FormatString: Favor `format` over `String#%`. (https://rubystyle.guide#sprintf)
elsif !File.file?(exe)
raise ArgumentError, _("'%{exe}' is a %{klass}, not a file") % { exe: exe, klass: File.ftype(exe) }

Check failure on line 261 in lib/puppet/provider/archive/ruby.rb

View workflow job for this annotation

GitHub Actions / Puppet / Static validations

Style/FormatString: Favor `format` over `String#%`. (https://rubystyle.guide#sprintf)
end
end
else
if File.expand_path(exe) == exe

Check failure on line 265 in lib/puppet/provider/archive/ruby.rb

View workflow job for this annotation

GitHub Actions / Puppet / Static validations

Style/IfInsideElse: Convert `if` nested inside `else` to `elsif`.
if !Puppet::FileSystem.exist?(exe)
raise ArgumentError, _("Could not find command '%{exe}'") % { exe: exe }

Check failure on line 267 in lib/puppet/provider/archive/ruby.rb

View workflow job for this annotation

GitHub Actions / Puppet / Static validations

Style/FormatString: Favor `format` over `String#%`. (https://rubystyle.guide#sprintf)
elsif !File.file?(exe)
raise ArgumentError, _("'%{exe}' is a %{klass}, not a file") % { exe: exe, klass: File.ftype(exe) }

Check failure on line 269 in lib/puppet/provider/archive/ruby.rb

View workflow job for this annotation

GitHub Actions / Puppet / Static validations

Style/FormatString: Favor `format` over `String#%`. (https://rubystyle.guide#sprintf)
elsif !File.executable?(exe)
raise ArgumentError, _("'%{exe}' is not executable") % { exe: exe }

Check failure on line 271 in lib/puppet/provider/archive/ruby.rb

View workflow job for this annotation

GitHub Actions / Puppet / Static validations

Style/FormatString: Favor `format` over `String#%`. (https://rubystyle.guide#sprintf)
end
end
end

if resource[:env_path]
Puppet::Util.withenv :PATH => resource[:env_path].join(File::PATH_SEPARATOR) do

Check failure on line 277 in lib/puppet/provider/archive/ruby.rb

View workflow job for this annotation

GitHub Actions / Puppet / Static validations

Style/HashSyntax: Use the new Ruby 1.9 hash syntax. (https://rubystyle.guide#hash-literals)
return if which(exe)
end
end

# 'which' will only return the command if it's executable, so we can't
# distinguish not found from not executable
raise ArgumentError, _("Could not find command '%{exe}'") % { exe: exe }

Check failure on line 284 in lib/puppet/provider/archive/ruby.rb

View workflow job for this annotation

GitHub Actions / Puppet / Static validations

Style/FormatString: Favor `format` over `String#%`. (https://rubystyle.guide#sprintf)
end
def environment

Check failure on line 286 in lib/puppet/provider/archive/ruby.rb

View workflow job for this annotation

GitHub Actions / Puppet / Static validations

Layout/EmptyLineBetweenDefs: Expected 1 empty line between method definitions; found 0. (https://rubystyle.guide#empty-lines-between-methods)
env = {}

if (path = resource[:env_path])
env[:PATH] = path.join(File::PATH_SEPARATOR)
end

return env unless (envlist = resource[:environment])

envlist = [envlist] unless envlist.is_a? Array
envlist.each do |setting|
unless (match = /^(\w+)=((.|\n)*)$/.match(setting))

Check failure on line 297 in lib/puppet/provider/archive/ruby.rb

View workflow job for this annotation

GitHub Actions / Puppet / Static validations

Style/RegexpLiteral: Use `%r` around regular expression. (https://rubystyle.guide#percent-r)
warning _("Cannot understand environment setting %{setting}") % { setting: setting.inspect }
next
end
var = match[1]
value = match[2]

if env.include?(var) || env.include?(var.to_sym)
warning _("Overriding environment setting '%{var}' with '%{value}'") % { var: var, value: value }
end

if value.nil? || value.empty?
msg = _("Empty environment setting '%{var}'") % { var: var }
Puppet.warn_once('undefined_variables', "empty_env_var_#{var}", msg, resource.file, resource.line)
end

env[var] = value
end

env
end

def run(command, check = false)
output = nil
checkexe(command)

debug "Executing#{check ? " check": ""} #{command}"

cwd = resource[:extract] ? resource[:extract_path] : File.dirname(resource[:path])
# It's ok if cwd is nil. In that case Puppet::Util::Execution.execute() simply will not attempt to
# change the working directory, which is exactly the right behavior when no cwd parameter is
# expressed on the resource. Moreover, attempting to change to the directory that is already
# the working directory can fail under some circumstances, so avoiding the directory change attempt
# is preferable to defaulting cwd to that directory.

# note that we are passing "false" for the "override_locale" parameter, which ensures that the user's
# default/system locale will be respected. Callers may override this behavior by setting locale-related
# environment variables (LANG, LC_ALL, etc.) in their 'environment' configuration.
output = Puppet::Util::Execution.execute(
command,
failonfail: false,
combine: true,
cwd: cwd,
uid: resource[:user],
gid: resource[:group],
override_locale: false,
custom_environment: environment,
sensitive: false
)
# The shell returns 127 if the command is missing.
if output.exitstatus == 127
raise ArgumentError, output
end
# Return output twice as processstatus was returned before, but only exitstatus was ever called.
# Output has the exitstatus on it so it is returned instead. This is here twice as changing this
# would result in a change to the underlying API.
[output, output]
end

def extractexe(command)
if command.is_a? Array
command.first
else
match = /^"([^"]+)"|^'([^']+)'/.match(command)
if match
# extract whichever of the two sides matched the content.
match[1] or match[2]
else
command.split(/ /)[0]
end
end
end

def validatecmd(command)
exe = extractexe(command)
# if we're not fully qualified, require a path
self.fail _("'%{exe}' is not qualified and no path was specified. Please qualify the command or specify a path.") % { exe: exe } if !absolute_path?(exe) and resource[:path].nil?
end
end
Loading

0 comments on commit b50b6ca

Please sign in to comment.