wright 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +2 -2
  3. data/NEWS +8 -0
  4. data/README.md +70 -0
  5. data/Rakefile +19 -0
  6. data/lib/wright/config.rb +64 -0
  7. data/lib/wright/dry_run.rb +33 -0
  8. data/lib/wright/dsl.rb +63 -0
  9. data/lib/wright/logger.rb +73 -0
  10. data/lib/wright/provider/directory.rb +78 -0
  11. data/lib/wright/provider/file.rb +104 -0
  12. data/lib/wright/provider/package/apt.rb +93 -0
  13. data/lib/wright/provider/package.rb +38 -0
  14. data/lib/wright/provider/symlink.rb +86 -0
  15. data/lib/wright/provider.rb +30 -0
  16. data/lib/wright/resource/directory.rb +67 -0
  17. data/lib/wright/resource/file.rb +67 -0
  18. data/lib/wright/resource/package.rb +70 -0
  19. data/lib/wright/resource/symlink.rb +47 -0
  20. data/lib/wright/resource.rb +157 -0
  21. data/lib/wright/util/color.rb +51 -0
  22. data/lib/wright/util/file.rb +258 -0
  23. data/lib/wright/util/file_permissions.rb +129 -0
  24. data/lib/wright/util/recursive_autoloader.rb +91 -0
  25. data/lib/wright/util/stolen_from_activesupport.rb +202 -0
  26. data/lib/wright/util/user.rb +75 -0
  27. data/lib/wright/util.rb +72 -0
  28. data/lib/wright/version.rb +3 -2
  29. data/lib/wright.rb +10 -1
  30. data/spec/config_spec.rb +37 -0
  31. data/spec/dsl_spec.rb +65 -0
  32. data/spec/logger_spec.rb +65 -0
  33. data/spec/provider/directory_spec.rb +114 -0
  34. data/spec/provider/file_spec.rb +130 -0
  35. data/spec/provider/package/apt/apt-get_install_-qy_abcde=2.5.4-1.return +1 -0
  36. data/spec/provider/package/apt/apt-get_install_-qy_abcde=2.5.4-1.stderr +0 -0
  37. data/spec/provider/package/apt/apt-get_install_-qy_abcde=2.5.4-1.stdout +15 -0
  38. data/spec/provider/package/apt/apt-get_install_-qy_htop.return +1 -0
  39. data/spec/provider/package/apt/apt-get_install_-qy_htop.stderr +0 -0
  40. data/spec/provider/package/apt/apt-get_install_-qy_htop.stdout +19 -0
  41. data/spec/provider/package/apt/apt-get_install_-qy_unknown-package.return +1 -0
  42. data/spec/provider/package/apt/apt-get_install_-qy_unknown-package.stderr +1 -0
  43. data/spec/provider/package/apt/apt-get_install_-qy_unknown-package.stdout +3 -0
  44. data/spec/provider/package/apt/apt-get_remove_-qy_abcde.return +1 -0
  45. data/spec/provider/package/apt/apt-get_remove_-qy_abcde.stderr +0 -0
  46. data/spec/provider/package/apt/apt-get_remove_-qy_abcde.stdout +14 -0
  47. data/spec/provider/package/apt/dpkg-query_-s_abcde.return +1 -0
  48. data/spec/provider/package/apt/dpkg-query_-s_abcde.stderr +0 -0
  49. data/spec/provider/package/apt/dpkg-query_-s_abcde.stdout +23 -0
  50. data/spec/provider/package/apt/dpkg-query_-s_htop.return +1 -0
  51. data/spec/provider/package/apt/dpkg-query_-s_htop.stderr +0 -0
  52. data/spec/provider/package/apt/dpkg-query_-s_htop.stdout +19 -0
  53. data/spec/provider/package/apt/dpkg-query_-s_unknown-package.return +1 -0
  54. data/spec/provider/package/apt/dpkg-query_-s_unknown-package.stderr +3 -0
  55. data/spec/provider/package/apt/dpkg-query_-s_unknown-package.stdout +0 -0
  56. data/spec/provider/package/apt/dpkg-query_-s_vlc.return +1 -0
  57. data/spec/provider/package/apt/dpkg-query_-s_vlc.stderr +3 -0
  58. data/spec/provider/package/apt/dpkg-query_-s_vlc.stdout +0 -0
  59. data/spec/provider/package/apt_spec.rb +297 -0
  60. data/spec/provider/package_spec.rb +63 -0
  61. data/spec/provider/symlink_spec.rb +122 -0
  62. data/spec/provider_spec.rb +19 -0
  63. data/spec/recursive_autoloader/foo/bar/baz.rb +12 -0
  64. data/spec/recursive_autoloader/identically_named_dir_and_file/this_should_not_be_loaded.rb +1 -0
  65. data/spec/recursive_autoloader/identically_named_dir_and_file.rb +8 -0
  66. data/spec/recursive_autoloader/loaded_on_demand.rb +8 -0
  67. data/spec/recursive_autoloader/raises_exception.rb +1 -0
  68. data/spec/recursive_autoloader_spec.rb +41 -0
  69. data/spec/resource/directory_spec.rb +187 -0
  70. data/spec/resource/file_spec.rb +213 -0
  71. data/spec/resource/symlink_spec.rb +117 -0
  72. data/spec/resource_spec.rb +184 -0
  73. data/spec/spec_helper.rb +35 -0
  74. data/spec/spec_helpers/fake_capture3.rb +47 -0
  75. data/spec/util/activesupport_spec.rb +24 -0
  76. data/spec/util/file_permissions_spec.rb +141 -0
  77. data/spec/util/file_spec.rb +127 -0
  78. data/spec/util/user_spec.rb +46 -0
  79. data/spec/util_spec.rb +80 -0
  80. metadata +253 -20
  81. data/.gitignore +0 -17
  82. data/Gemfile +0 -3
  83. data/wright.gemspec +0 -16
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4644d39c9505dda9fce4adb9a0a5bd5a5e0dfe0a
4
+ data.tar.gz: 33c6ed57fc54bff76b3cfd9c91f2969f8b978d1e
5
+ SHA512:
6
+ metadata.gz: 350373a47cdcd38005c8deb56ab835130c2a946aaf69751344318765797e388f4785d8be34b79fbfccdb85d3cae878312f531d30912b1d6d022f86c77fe5c284
7
+ data.tar.gz: 63e0cff72088b5d73174c3dd75f26dbb7ad48b97bede920c2625b7c63746a8873c01c46da1c25a9bfa9363494ee255a4b4e8ba3926989855efe01bb7be438932
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2013 Sebastian Boehm
1
+ Copyright (c) 2012, 2013, 2014, 2015 Sebastian Boehm
2
2
 
3
3
  MIT License
4
4
 
@@ -19,4 +19,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
19
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
20
  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
21
  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/NEWS ADDED
@@ -0,0 +1,8 @@
1
+ * wright NEWS
2
+ ** 0.1.0 (2015-01-16)
3
+
4
+ - First public release
5
+
6
+ # Local Variables:
7
+ # mode: org
8
+ # End:
data/README.md ADDED
@@ -0,0 +1,70 @@
1
+ wright
2
+ ======
3
+
4
+ Lightweight configuration management.
5
+
6
+ Requirements
7
+ ------------
8
+
9
+ - Ruby ≥1.9
10
+
11
+ Documentation
12
+ -------------
13
+
14
+ There is not too much useful documentation that is targeted towards
15
+ users at the moment.
16
+
17
+ Run `bundle exec rake rdoc` to generate HTML docs for wright
18
+ developers.
19
+
20
+ Hacking
21
+ -------
22
+
23
+ To get started with wright, simply install the development
24
+ dependencies via bundler:
25
+
26
+ - `bundle install --path .bundle`
27
+ - `bundle exec rake test`
28
+
29
+ All tests should pass.
30
+
31
+ Getting Started
32
+ ---------------
33
+
34
+ To start a wright IRB session, simply run:
35
+
36
+ $ bundle console
37
+
38
+ In order to create some resources using the wright DSL:
39
+
40
+ extend Wright::DSL
41
+
42
+ foo_dir = directory '/tmp/foo'
43
+ fstab = symlink '/tmp/foo/fstab' do |s|
44
+ s.to = '/etc/fstab'
45
+ end
46
+
47
+ puts File.directory? '/tmp/foo'
48
+ puts File.symlink? '/tmp/foo/fstab'
49
+
50
+ fstab.remove
51
+ foo_dir.remove
52
+
53
+ If you don't want to use the DSL:
54
+
55
+ foo_dir = Wright::Resource::Directory.new('/tmp/foo')
56
+ foo_dir.create
57
+ fstab = Wright::Resource::Symlink.new('/tmp/foo/fstab')
58
+ fstab.to = '/etc/fstab'
59
+ fstab.create
60
+
61
+ puts File.directory? '/tmp/foo'
62
+ puts File.symlink? '/tmp/foo/fstab'
63
+
64
+ fstab.remove
65
+ foo_dir.remove
66
+
67
+ Copyright
68
+ ---------
69
+
70
+ Copyright (c) 2012-2015 Sebastian Boehm. See LICENSE for details.
data/Rakefile CHANGED
@@ -1,2 +1,21 @@
1
1
  #!/usr/bin/env rake
2
2
  require 'bundler/gem_tasks'
3
+ require 'rake/testtask'
4
+ require 'rdoc/task'
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.pattern = 'spec/**/*_spec.rb'
8
+ end
9
+
10
+ RDoc::Task.new(clobber_rdoc: 'rdoc:clobber',
11
+ rerdoc: 'rdoc:force') do |t|
12
+ t.rdoc_files.include('lib/**/*.rb')
13
+ t.options << '--markup=tomdoc'
14
+ end
15
+
16
+ namespace :rdoc do
17
+ desc 'Show RDoc coverage report'
18
+ task :coverage do
19
+ exec 'rdoc --markup=tomdoc --coverage-report lib/'
20
+ end
21
+ end
@@ -0,0 +1,64 @@
1
+ require 'forwardable'
2
+
3
+ module Wright
4
+ # Public: Configuration container, wraps a regular Ruby hash.
5
+ #
6
+ # Useful for getting and setting configuration values, such as
7
+ # logging verbosity, colour output and provider configuration.
8
+ #
9
+ # Examples
10
+ #
11
+ # Wright::Config[:foo] = { bar: :baz }
12
+ # Wright::Config[:foo][:bar]
13
+ # # => :baz
14
+ class Config
15
+ @config_hash = {}
16
+ class << self
17
+ extend Forwardable
18
+ def_delegators :@config_hash, :[], :[]=, :size
19
+ end
20
+ private_class_method :new
21
+
22
+ # Public: Check if a (nested) configuration value is set.
23
+ #
24
+ # path - The configuration item as an argument list.
25
+ #
26
+ # Examples
27
+ #
28
+ # Wright::Config[:foo] = { bar: :baz }
29
+ # Wright::Config.nested_key?(:foo, :bar)
30
+ # # => true
31
+ #
32
+ # Wright::Config.nested_key?(:this, :doesnt, :exist)
33
+ # # => false
34
+ #
35
+ # Returns true if the configuration value is set and false
36
+ # otherwise.
37
+ def self.nested_key?(*path)
38
+ last_key = path.pop
39
+ last_hash = path.reduce(@config_hash) do |hash, key|
40
+ return false unless hash.respond_to?(:fetch)
41
+ hash.fetch(key, {})
42
+ end
43
+ last_hash.respond_to?(:key?) && last_hash.key?(last_key)
44
+ end
45
+
46
+ # Public: Retrieve a (nested) configuration value.
47
+ #
48
+ # path - The configuration item as an argument list.
49
+ #
50
+ # Examples
51
+ #
52
+ # Wright::Config[:foo] = { bar: :baz }
53
+ # Wright::Config.nested_value(:foo, :bar)
54
+ # # => :baz
55
+ #
56
+ # Wright::Config.nested_value(:this, :doesnt, :exist)
57
+ # # => nil
58
+ #
59
+ # Returns the configuration value or nil if the value is not set.
60
+ def self.nested_value(*path)
61
+ nested_key?(*path) ? path.reduce(@config_hash) { |a, e| a[e] } : nil
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,33 @@
1
+ module Wright # rubocop:disable Documentation
2
+ @dry_run = false
3
+
4
+ # Public: Checks if dry-run mode is currently active.
5
+ #
6
+ # Examples
7
+ #
8
+ # puts 'Just a dry-run...' if Wright.dry_run?
9
+ #
10
+ # Returns true if dry-run mode is currently active and false otherwise.
11
+ def self.dry_run?
12
+ @dry_run
13
+ end
14
+
15
+ # Public: Runs a block in dry-run mode.
16
+ #
17
+ # Examples
18
+ #
19
+ # Wright.dry_run do
20
+ # symlink '/tmp/fstab' do |s|
21
+ # s.to = '/etc/fstab'
22
+ # end
23
+ # end
24
+ #
25
+ # Returns the block's return value.
26
+ def self.dry_run
27
+ saved_dry_run = @dry_run
28
+ @dry_run = true
29
+ yield
30
+ ensure
31
+ @dry_run = saved_dry_run
32
+ end
33
+ end
data/lib/wright/dsl.rb ADDED
@@ -0,0 +1,63 @@
1
+ require 'wright/util'
2
+
3
+ module Wright
4
+ # Public: Includable Wright script DSL.
5
+ #
6
+ # Contains resource methods for all registered resources.
7
+ #
8
+ # Examples
9
+ #
10
+ # # define a new resource at runtime
11
+ # class KitchenSink < Wright::Resource; end
12
+ #
13
+ # # register the resource
14
+ # Wright::DSL.register_resource KitchenSink
15
+ #
16
+ # extend Wright::DSL
17
+ #
18
+ # kitchen_sink "I don't do anything"
19
+ #
20
+ # # resource methods accept blocks
21
+ # kitchen_sink 'I am sooo useful' do |k|
22
+ # puts k.name
23
+ # end
24
+ # # output: I am sooo useful
25
+ #
26
+ # # save resource for later use
27
+ # a_sink_to_remember = kitchen_sink 'Me too, me too!'
28
+ # a_sink_to_remember.class
29
+ # # => KitchenSink
30
+ module DSL
31
+ # Public: Register a class as a resource.
32
+ #
33
+ # Creates a resource method in the DSL module. Uses the
34
+ # snake-cased class name as method name.
35
+ #
36
+ # resource_class - The resource class. Usually a subclass of
37
+ # Wright::Resource. Will be initialized with the
38
+ # resource's name as an argument.
39
+ #
40
+ # Returns nothing.
41
+ def self.register_resource(resource_class)
42
+ method_name = Util.class_to_resource_name(resource_class)
43
+ this_module = self
44
+ define_method(method_name) do |name = nil, &block|
45
+ this_module.yield_resource(resource_class, name, &block)
46
+ end
47
+ end
48
+
49
+ # Internal: Instantiate a resource_class object and perform its
50
+ # default action.
51
+ #
52
+ # Implicitly invoking a block from within another block does not
53
+ # work: http://blog.sidu.in/2007/11/ruby-blocks-gotchas.html
54
+ #
55
+ # Returns nothing.
56
+ def self.yield_resource(resource_class, name)
57
+ r = resource_class.new(name)
58
+ yield(r) if block_given?
59
+ r.run_action if r.respond_to?(:run_action)
60
+ r
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,73 @@
1
+ require 'logger'
2
+
3
+ require 'wright/config'
4
+ require 'wright/util/color'
5
+
6
+ module Wright # rubocop:disable Documentation
7
+ # Public: Default logger for Wright.
8
+ class Logger < ::Logger
9
+ # Public: Default formatter for Wright log messages.
10
+ class Formatter < ::Logger::Formatter
11
+ # Internal: Called by Wright::Logger to format log messages.
12
+ #
13
+ # severity - The String log severity.
14
+ # time - The time for the log entry. Ignored.
15
+ # progname - The program name for the log entry. Ignored.
16
+ # message - The actual log message.
17
+ #
18
+ # Returns the formatted String log entry.
19
+ def call(severity, _time, _progname, message)
20
+ log_entry = "#{severity}: #{message}\n"
21
+ if Wright::Config[:log][:colorize]
22
+ colorize(log_entry, severity)
23
+ else
24
+ log_entry
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ # Internal: ANSI-Colorize a log message according to its
31
+ # severity.
32
+ #
33
+ # string - The String log message to be colorized.
34
+ # severity - The String severity of the log message.
35
+ #
36
+ # Returns the colorized String.
37
+ def colorize(string, severity)
38
+ case severity
39
+ when 'ERROR', 'FATAL'
40
+ Wright::Util::Color.red(string)
41
+ when 'WARN'
42
+ Wright::Util::Color.yellow(string)
43
+ when 'INFO'
44
+ string
45
+ else
46
+ string
47
+ end
48
+ end
49
+ end
50
+
51
+ # Public: Initialize a Logger.
52
+ #
53
+ # Enables log colorization if the log device is a TTY and
54
+ # colorization was not disabled before initialization.
55
+ #
56
+ # logdev - The log device used by the Logger.
57
+ def initialize(logdev = $stdout)
58
+ super
59
+ Wright::Config[:log] ||= {}
60
+ return unless Wright::Config[:log][:colorize].nil?
61
+
62
+ Wright::Config[:log][:colorize] = logdev.tty?
63
+ end
64
+ end
65
+
66
+ class << self
67
+ # Public: Get/Set Wright's Logger.
68
+ attr_accessor :log
69
+ end
70
+ @log = Wright::Logger.new
71
+ @log.formatter = Wright::Logger::Formatter.new
72
+ @log.level = Wright::Logger::INFO
73
+ end
@@ -0,0 +1,78 @@
1
+ require 'fileutils'
2
+ require 'wright/provider'
3
+ require 'wright/util/file'
4
+ require 'wright/util/user'
5
+ require 'wright/util/file_permissions'
6
+
7
+ module Wright
8
+ class Provider
9
+ # Public: Directory provider. Used as a Provider for Resource::Directory.
10
+ class Directory < Wright::Provider
11
+ # Public: Create or update the directory.
12
+ #
13
+ # Returns nothing.
14
+ def create
15
+ if ::File.directory?(dirname) && permissions.uptodate?
16
+ Wright.log.debug "directory already created: '#{@resource.name}'"
17
+ return
18
+ end
19
+
20
+ fail Errno::EEXIST, dirname if regular_file?
21
+ create_directory
22
+ @updated = true
23
+ end
24
+
25
+ # Public: Remove the directory.
26
+ #
27
+ # Returns nothing.
28
+ def remove
29
+ if ::File.exist?(dirname) && !::File.directory?(dirname)
30
+ fail "'#{dirname}' exists but is not a directory"
31
+ end
32
+
33
+ if ::File.directory?(dirname)
34
+ remove_directory
35
+ @updated = true
36
+ else
37
+ Wright.log.debug "directory already removed: '#{@resource.name}'"
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def permissions
44
+ Wright::Util::FilePermissions.create_from_resource(@resource,
45
+ :directory)
46
+ end
47
+
48
+ def create_directory
49
+ dir_permissions = permissions
50
+
51
+ if Wright.dry_run?
52
+ Wright.log.info "(would) create directory: '#{@resource.name}'"
53
+ else
54
+ Wright.log.info "create directory: '#{@resource.name}'"
55
+ FileUtils.mkdir_p(dirname)
56
+ dir_permissions.update
57
+ end
58
+ end
59
+
60
+ def remove_directory
61
+ if Wright.dry_run?
62
+ Wright.log.info "(would) remove directory: '#{@resource.name}'"
63
+ else
64
+ Wright.log.info "remove directory: '#{@resource.name}'"
65
+ FileUtils.rmdir(dirname)
66
+ end
67
+ end
68
+
69
+ def regular_file?
70
+ ::File.exist?(dirname) && !::File.directory?(dirname)
71
+ end
72
+
73
+ def dirname
74
+ ::File.expand_path(@resource.name)
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,104 @@
1
+ require 'wright/provider'
2
+ require 'wright/util/file_permissions'
3
+ require 'wright/util/user'
4
+ require 'fileutils'
5
+ require 'digest'
6
+ require 'tempfile'
7
+ require 'tmpdir'
8
+
9
+ module Wright
10
+ class Provider
11
+ # Public: File provider. Used as a Provider for Resource::File.
12
+ class File < Wright::Provider
13
+ # Public: Create or update the File.
14
+ #
15
+ # Returns nothing.
16
+ def create
17
+ fail Errno::EISDIR, filename if ::File.directory?(filename)
18
+
19
+ if uptodate?
20
+ Wright.log.debug "file already created: '#{@resource.name}'"
21
+ return
22
+ end
23
+
24
+ create_file
25
+ @updated = true
26
+ end
27
+
28
+ # Public: Remove the File.
29
+ #
30
+ # Returns nothing.
31
+ def remove
32
+ fail Errno::EISDIR, filename if ::File.directory?(filename)
33
+
34
+ if ::File.exist?(filename) || ::File.symlink?(filename)
35
+ remove_file
36
+ @updated = true
37
+ else
38
+ Wright.log.debug "file already removed: '#{@resource.name}'"
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def create_file
45
+ file_permissions = permissions
46
+
47
+ if Wright.dry_run?
48
+ Wright.log.info "(would) create file: '#{@resource.name}'"
49
+ else
50
+ Wright.log.info "create file: '#{@resource.name}'"
51
+ write_content_to_file
52
+ file_permissions.update
53
+ end
54
+ end
55
+
56
+ def write_content_to_file
57
+ tempfile = Tempfile.new(::File.basename(filename))
58
+ tempfile.write(@resource.content) if @resource.content
59
+ move_tempfile(tempfile)
60
+ ensure
61
+ tempfile.close!
62
+ end
63
+
64
+ def move_tempfile(tempfile)
65
+ # do not overwrite existing files if content was not specified
66
+ return if @resource.content.nil? && ::File.exist?(filename)
67
+ FileUtils.mv(tempfile.path, filename)
68
+ end
69
+
70
+ def remove_file
71
+ if Wright.dry_run?
72
+ Wright.log.info "(would) remove file: '#{@resource.name}'"
73
+ else
74
+ Wright.log.info "remove file: '#{@resource.name}'"
75
+ FileUtils.rm(filename)
76
+ end
77
+ end
78
+
79
+ def permissions
80
+ Wright::Util::FilePermissions.create_from_resource(@resource, :file)
81
+ end
82
+
83
+ def checksum(content)
84
+ Digest::SHA256.hexdigest(content)
85
+ end
86
+
87
+ def content_uptodate?
88
+ return false unless ::File.exist?(filename)
89
+ content = @resource.content || ''
90
+ target_checksum = checksum(content)
91
+ current_checksum = checksum(::File.read(filename))
92
+ current_checksum == target_checksum
93
+ end
94
+
95
+ def uptodate?
96
+ content_uptodate? && permissions.uptodate?
97
+ end
98
+
99
+ def filename
100
+ ::File.expand_path(@resource.name)
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,93 @@
1
+ require 'open3'
2
+
3
+ require 'wright/dry_run'
4
+ require 'wright/provider'
5
+ require 'wright/provider/package'
6
+
7
+ module Wright
8
+ class Provider
9
+ class Package
10
+ # Public: AptPackage provider. Used as a Provider for
11
+ # Resource::Package on Debian-based systems.
12
+ class Apt < Wright::Provider::Package
13
+ # Public: Get the installed package version.
14
+ #
15
+ # Returns an array of installed package version Strings.
16
+ def installed_versions
17
+ cmd = "dpkg-query -s #{@resource.name}"
18
+ cmd_stdout, _cmd_stderr, cmd_status = Open3.capture3(env, cmd)
19
+ installed_re = /^Status: install ok installed$/
20
+
21
+ if cmd_status.success? && installed_re =~ cmd_stdout
22
+ /^Version: (?<version>.*)$/ =~ cmd_stdout
23
+ [version]
24
+ else
25
+ []
26
+ end
27
+ end
28
+
29
+ # Public: Install the package.
30
+ #
31
+ # Returns nothing.
32
+ def install
33
+ if uptodate?(:install)
34
+ Wright.log.debug "package already installed: '#{@resource.name}'"
35
+ return
36
+ end
37
+
38
+ install_package
39
+ @updated = true
40
+ end
41
+
42
+ # Public: Remove the package.
43
+ #
44
+ # Returns nothing.
45
+ def remove
46
+ if uptodate?(:remove)
47
+ Wright.log.debug "package already removed: '#{@resource.name}'"
48
+ return
49
+ end
50
+
51
+ remove_package
52
+ @updated = true
53
+ end
54
+
55
+ private
56
+
57
+ def install_package
58
+ package = @resource.name
59
+ if Wright.dry_run?
60
+ Wright.log.info "(would) install package: '#{package}'"
61
+ else
62
+ Wright.log.info "install package: '#{package}'"
63
+ apt_get(:install, package, @resource.version)
64
+ end
65
+ end
66
+
67
+ def remove_package
68
+ package = @resource.name
69
+ if Wright.dry_run?
70
+ Wright.log.info "(would) remove package: '#{package}'"
71
+ else
72
+ Wright.log.info "remove package: '#{package}'"
73
+ apt_get(:remove, package)
74
+ end
75
+ end
76
+
77
+ def apt_get(action, package, version = nil)
78
+ package_version = version.nil? ? '' : "=#{version}"
79
+ apt_cmd = "apt-get #{action} -qy #{package}#{package_version}"
80
+ _cmd_stdout, cmd_stderr, cmd_status = Open3.capture3(env, apt_cmd)
81
+ return if cmd_status.success?
82
+
83
+ apt_error = cmd_stderr.chomp
84
+ fail %(cannot #{action} package '#{package}': "#{apt_error}")
85
+ end
86
+
87
+ def env
88
+ { 'DEBIAN_FRONTEND' => 'noninteractive' }
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,38 @@
1
+ require 'wright/provider'
2
+
3
+ module Wright
4
+ class Provider
5
+ # Public: Package provider. Used as a Provider base class for
6
+ # Resource::Package.
7
+ class Package < Wright::Provider
8
+ private
9
+
10
+ # Public: Check if the package is up-to-date for a given
11
+ # action.
12
+ #
13
+ # action - The action symbol. Currently supports :install and
14
+ # :remove.
15
+ #
16
+ # Returns true if the package is up-to-date and false otherwise.
17
+ # Raises ArgumentError if the action is invalid.
18
+ def uptodate?(action)
19
+ case action
20
+ when :install
21
+ package_installed?
22
+ when :remove
23
+ !package_installed?
24
+ else
25
+ fail ArgumentError, "invalid action '#{action}'"
26
+ end
27
+ end
28
+
29
+ def package_installed?
30
+ if @resource.version
31
+ installed_versions.include?(@resource.version)
32
+ else
33
+ !installed_versions.empty?
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end