Skip to content

Commit b50b6ca

Browse files
committed
add onlyif && unless features
1 parent 1abd3c1 commit b50b6ca

10 files changed

Lines changed: 971 additions & 32 deletions

File tree

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ group :release do
2323
gem 'voxpupuli-release', '~> 3.0', :require => false
2424
end
2525

26+
gem 'pry'
2627
gem 'rake', :require => false
2728
gem 'facter', ENV['FACTER_GEM_VERSION'], :require => false, :groups => [:test]
2829

REFERENCE.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,13 +632,19 @@ Parameters
632632
Examples
633633
--------
634634

635+
#### Examples
636+
637+
#####
638+
639+
```puppet
635640
archive::nexus { '/tmp/jtstand-ui-0.98.jar':
636641
url => 'https://oss.sonatype.org',
637642
gav => 'org.codehaus.jtstand:jtstand-ui:0.98',
638643
repository => 'codehaus-releases',
639644
packaging => 'jar',
640645
extract => false,
641646
}
647+
```
642648

643649
#### Parameters
644650

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

896902
Default value: `present`
897903

904+
##### `onlyif`
905+
906+
A test command that checks the state of the target system and restricts
907+
when the `archive` can run. If present, Puppet runs this test command
908+
first, and only runs the main command if the test has an exit code of 0
909+
(success). For example:
910+
911+
```
912+
archive { '/tmp/jta-1.1.jar':
913+
ensure => present,
914+
extract => true,
915+
extract_path => '/tmp',
916+
source => 'http://central.maven.org/maven2/javax/transaction/jta/1.1/jta-1.1.jar',
917+
onlyif => 'test `java -version 2>&1 | awk -F '"' '/version/ {print $2}' | awk -F '.' '{sub("^$", "0", $2); print $1$2}'` -gt 15',
918+
cleanup => true,
919+
env_path => ["/bin", "/usr/bin", "/sbin", "/usr/sbin"],
920+
}
921+
```
922+
923+
Since this command is used in the process of determining whether the
924+
`archive` is already in sync, it must be run during a noop Puppet run.
925+
926+
This parameter can also take an array of commands. For example:
927+
928+
onlyif => ['test -f /tmp/file1', 'test -f /tmp/file2'],
929+
930+
or an array of arrays. For example:
931+
932+
onlyif => [['test', '-f', '/tmp/file1'], 'test -f /tmp/file2']
933+
934+
This `archive` would only run if every command in the array has an
935+
exit code of 0 (success).
936+
937+
##### `unless`
938+
939+
A test command that checks the state of the target system and restricts
940+
when the `archive` can run. If present, Puppet runs this test command
941+
first, then runs the main command unless the test has an exit code of 0
942+
(success). For example:
943+
944+
```
945+
archive { '/tmp/jta-1.1.jar':
946+
ensure => present,
947+
extract => true,
948+
extract_path => '/tmp',
949+
source => 'http://central.maven.org/maven2/javax/transaction/jta/1.1/jta-1.1.jar',
950+
unless => 'test `java -version 2>&1 | awk -F '"' '/version/ {print $2}' | awk -F '.' '{sub("^$", "0", $2); print $1$2}'` -gt 15',
951+
cleanup => true,
952+
env_path => ["/bin", "/usr/bin", "/sbin", "/usr/sbin"],
953+
}
954+
```
955+
956+
Since this command is used in the process of determining whether the
957+
`archive` is already in sync, it must be run during a noop Puppet run.
958+
959+
This parameter can also take an array of commands. For example:
960+
961+
unless => ['test -f /tmp/file1', 'test -f /tmp/file2'],
962+
963+
or an array of arrays. For example:
964+
965+
unless => [['test', '-f', '/tmp/file1'], 'test -f /tmp/file2']
966+
967+
This `archive` would only run if every command in the array has a
968+
non-zero exit code.
969+
898970
#### Parameters
899971

900972
The following parameters are available in the `archive` type.
@@ -910,6 +982,8 @@ The following parameters are available in the `archive` type.
910982
* [`digest_type`](#-archive--digest_type)
911983
* [`digest_url`](#-archive--digest_url)
912984
* [`download_options`](#-archive--download_options)
985+
* [`env_path`](#-archive--env_path)
986+
* [`environment`](#-archive--environment)
913987
* [`extract`](#-archive--extract)
914988
* [`extract_command`](#-archive--extract_command)
915989
* [`extract_flags`](#-archive--extract_flags)
@@ -998,6 +1072,20 @@ archive file checksum source (instead of specifying checksum)
9981072

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

1075+
##### <a name="-archive--env_path"></a>`env_path`
1076+
1077+
The search path used for check execution.
1078+
Commands must be fully qualified if no path is specified. Paths
1079+
can be specified as an array or as a '
1080+
1081+
##### <a name="-archive--environment"></a>`environment`
1082+
1083+
An array of any additional environment variables you want to set for a
1084+
command, such as `[ 'HOME=/root', '[email protected]']`.
1085+
Note that if you use this to set PATH, it will override the `path`
1086+
attribute. Multiple environment variables should be specified as an
1087+
array.
1088+
10011089
##### <a name="-archive--extract"></a>`extract`
10021090

10031091
Valid values: `true`, `false`

lib/puppet/provider/archive/ruby.rb

Lines changed: 128 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55

66
require 'securerandom'
77
require 'tempfile'
8+
require 'puppet/util/execution'
89

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

6163
Puppet::Type.type(:archive).provide(:ruby) do
64+
include Puppet::Util::Execution
6265
optional_commands aws: 'aws'
6366
optional_commands gsutil: 'gsutil'
6467
defaultfor feature: :microsoft_windows
@@ -95,18 +98,6 @@ def tempfile_name
9598
end
9699
end
97100

98-
def creates
99-
if resource[:extract] == :true
100-
extracted? ? resource[:creates] : 'archive not extracted'
101-
else
102-
resource[:creates]
103-
end
104-
end
105-
106-
def creates=(_value)
107-
extract
108-
end
109-
110101
def checksum
111102
resource[:checksum] || (resource[:checksum] = remote_checksum if resource[:checksum_url])
112103
end
@@ -127,7 +118,7 @@ def remote_checksum
127118
# returns boolean
128119
def checksum?(store_checksum = true)
129120
return false unless File.exist? archive_filepath
130-
return true if resource[:checksum_type] == :none
121+
return true if resource[:checksum_type] == :none
131122

132123
archive = PuppetX::Bodeco::Archive.new(archive_filepath)
133124
archive_checksum = archive.checksum(resource[:checksum_type])
@@ -156,7 +147,7 @@ def extract
156147
end
157148

158149
def extracted?
159-
resource[:creates] && File.exist?(resource[:creates])
150+
resource.check_all_attributes
160151
end
161152

162153
def transfer_download(archive_filepath)
@@ -258,4 +249,127 @@ def optional_switch(value, option)
258249
[]
259250
end
260251
end
252+
253+
# Verify that we have the executable
254+
def checkexe(command)
255+
exe = extractexe(command)
256+
if Facter.value(:osfamily) == 'windows'
257+
if absolute_path?(exe)
258+
if !Puppet::FileSystem.exist?(exe)
259+
raise ArgumentError, _("Could not find command '%{exe}'") % { exe: exe }
260+
elsif !File.file?(exe)
261+
raise ArgumentError, _("'%{exe}' is a %{klass}, not a file") % { exe: exe, klass: File.ftype(exe) }
262+
end
263+
end
264+
else
265+
if File.expand_path(exe) == exe
266+
if !Puppet::FileSystem.exist?(exe)
267+
raise ArgumentError, _("Could not find command '%{exe}'") % { exe: exe }
268+
elsif !File.file?(exe)
269+
raise ArgumentError, _("'%{exe}' is a %{klass}, not a file") % { exe: exe, klass: File.ftype(exe) }
270+
elsif !File.executable?(exe)
271+
raise ArgumentError, _("'%{exe}' is not executable") % { exe: exe }
272+
end
273+
end
274+
end
275+
276+
if resource[:env_path]
277+
Puppet::Util.withenv :PATH => resource[:env_path].join(File::PATH_SEPARATOR) do
278+
return if which(exe)
279+
end
280+
end
281+
282+
# 'which' will only return the command if it's executable, so we can't
283+
# distinguish not found from not executable
284+
raise ArgumentError, _("Could not find command '%{exe}'") % { exe: exe }
285+
end
286+
def environment
287+
env = {}
288+
289+
if (path = resource[:env_path])
290+
env[:PATH] = path.join(File::PATH_SEPARATOR)
291+
end
292+
293+
return env unless (envlist = resource[:environment])
294+
295+
envlist = [envlist] unless envlist.is_a? Array
296+
envlist.each do |setting|
297+
unless (match = /^(\w+)=((.|\n)*)$/.match(setting))
298+
warning _("Cannot understand environment setting %{setting}") % { setting: setting.inspect }
299+
next
300+
end
301+
var = match[1]
302+
value = match[2]
303+
304+
if env.include?(var) || env.include?(var.to_sym)
305+
warning _("Overriding environment setting '%{var}' with '%{value}'") % { var: var, value: value }
306+
end
307+
308+
if value.nil? || value.empty?
309+
msg = _("Empty environment setting '%{var}'") % { var: var }
310+
Puppet.warn_once('undefined_variables', "empty_env_var_#{var}", msg, resource.file, resource.line)
311+
end
312+
313+
env[var] = value
314+
end
315+
316+
env
317+
end
318+
319+
def run(command, check = false)
320+
output = nil
321+
checkexe(command)
322+
323+
debug "Executing#{check ? " check": ""} #{command}"
324+
325+
cwd = resource[:extract] ? resource[:extract_path] : File.dirname(resource[:path])
326+
# It's ok if cwd is nil. In that case Puppet::Util::Execution.execute() simply will not attempt to
327+
# change the working directory, which is exactly the right behavior when no cwd parameter is
328+
# expressed on the resource. Moreover, attempting to change to the directory that is already
329+
# the working directory can fail under some circumstances, so avoiding the directory change attempt
330+
# is preferable to defaulting cwd to that directory.
331+
332+
# note that we are passing "false" for the "override_locale" parameter, which ensures that the user's
333+
# default/system locale will be respected. Callers may override this behavior by setting locale-related
334+
# environment variables (LANG, LC_ALL, etc.) in their 'environment' configuration.
335+
output = Puppet::Util::Execution.execute(
336+
command,
337+
failonfail: false,
338+
combine: true,
339+
cwd: cwd,
340+
uid: resource[:user],
341+
gid: resource[:group],
342+
override_locale: false,
343+
custom_environment: environment,
344+
sensitive: false
345+
)
346+
# The shell returns 127 if the command is missing.
347+
if output.exitstatus == 127
348+
raise ArgumentError, output
349+
end
350+
# Return output twice as processstatus was returned before, but only exitstatus was ever called.
351+
# Output has the exitstatus on it so it is returned instead. This is here twice as changing this
352+
# would result in a change to the underlying API.
353+
[output, output]
354+
end
355+
356+
def extractexe(command)
357+
if command.is_a? Array
358+
command.first
359+
else
360+
match = /^"([^"]+)"|^'([^']+)'/.match(command)
361+
if match
362+
# extract whichever of the two sides matched the content.
363+
match[1] or match[2]
364+
else
365+
command.split(/ /)[0]
366+
end
367+
end
368+
end
369+
370+
def validatecmd(command)
371+
exe = extractexe(command)
372+
# if we're not fully qualified, require a path
373+
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?
374+
end
261375
end

0 commit comments

Comments
 (0)