wright 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
@@ -0,0 +1,86 @@
1
+ require 'wright/provider'
2
+ require 'fileutils'
3
+
4
+ module Wright
5
+ class Provider
6
+ # Public: Symlink provider. Used as a Provider for Resource::Symlink.
7
+ class Symlink < Wright::Provider
8
+ # Public: Create or update the Symlink.
9
+ #
10
+ # Returns nothing.
11
+ def create
12
+ if exist?
13
+ symlink = symlink_to_s(@resource.name, @resource.to)
14
+ Wright.log.debug "symlink already created: #{symlink}"
15
+ return
16
+ end
17
+
18
+ fail Errno::EEXIST, link_name if regular_file?
19
+ create_link
20
+ @updated = true
21
+ end
22
+
23
+ # Public: Remove the Symlink.
24
+ #
25
+ # Returns nothing.
26
+ def remove
27
+ if ::File.exist?(link_name) && !::File.symlink?(link_name)
28
+ fail "'#{link_name}' is not a symlink"
29
+ end
30
+
31
+ if ::File.symlink?(link_name)
32
+ remove_symlink
33
+ @updated = true
34
+ else
35
+ Wright.log.debug "symlink already removed: '#{@resource.name}'"
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ # Internal: Checks if the specified link exists.
42
+ #
43
+ # Returns true if the link exists and points to the specified target
44
+ # and false otherwise.
45
+ def exist? #:doc:
46
+ ::File.symlink?(link_name) &&
47
+ ::File.readlink(link_name) == link_to
48
+ end
49
+
50
+ def create_link
51
+ symlink = symlink_to_s(@resource.name, @resource.to)
52
+ if Wright.dry_run?
53
+ Wright.log.info "(would) create symlink: #{symlink}"
54
+ else
55
+ Wright.log.info "create symlink: #{symlink}"
56
+ Wright::Util::File.ln_sfn(link_to, link_name)
57
+ end
58
+ end
59
+
60
+ def symlink_to_s(link_name, target)
61
+ "'#{link_name}' -> '#{target}'"
62
+ end
63
+
64
+ def remove_symlink
65
+ if Wright.dry_run?
66
+ Wright.log.info "(would) remove symlink: '#{@resource.name}'"
67
+ else
68
+ Wright.log.info "remove symlink: '#{@resource.name}'"
69
+ FileUtils.rm(link_name)
70
+ end
71
+ end
72
+
73
+ def regular_file?
74
+ ::File.exist?(link_name) && !::File.symlink?(link_name)
75
+ end
76
+
77
+ def link_to
78
+ Wright::Util::File.expand_tilde_path(@resource.to)
79
+ end
80
+
81
+ def link_name
82
+ Wright::Util::File.expand_tilde_path(@resource.name)
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,30 @@
1
+ require 'wright/util/recursive_autoloader'
2
+
3
+ module Wright
4
+ # Public: Provider class.
5
+ class Provider
6
+ # Public: Wright standard provider directory.
7
+ PROVIDER_DIR = File.expand_path('provider', File.dirname(__FILE__))
8
+
9
+ Wright::Util::RecursiveAutoloader.add_autoloads(PROVIDER_DIR, name)
10
+
11
+ # Public: Initialize a Provider.
12
+ #
13
+ # resource - The resource used by the provider, typically a
14
+ # Wright::Resource.
15
+ def initialize(resource)
16
+ @resource = resource
17
+ @updated = false
18
+ end
19
+
20
+ # Public: Checks if the provider was updated since the last call
21
+ # to updated?.
22
+ #
23
+ # Returns true if the provider was updated and false otherwise.
24
+ def updated?
25
+ updated = @updated
26
+ @updated = false
27
+ updated
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,67 @@
1
+ require 'wright/resource'
2
+ require 'wright/dsl'
3
+
4
+ module Wright
5
+ class Resource
6
+ # Public: Directory resource, represents a directory.
7
+ #
8
+ # Examples
9
+ #
10
+ # dir = Wright::Resource::Directory.new('/tmp/foobar')
11
+ # dir.create
12
+ class Directory < Wright::Resource
13
+ # Public: Initialize a Directory.
14
+ #
15
+ # name - The directory's name.
16
+ def initialize(name)
17
+ super
18
+ @mode = nil
19
+ @owner = nil
20
+ @group = nil
21
+ @action = :create
22
+ end
23
+
24
+ # Public: Get/Set the directory's mode.
25
+ attr_accessor :mode
26
+
27
+ # Public: Get the directory's owner.
28
+ attr_reader :owner
29
+
30
+ # Public: Set the directory's owner.
31
+ def owner=(owner)
32
+ target_owner, target_group =
33
+ Wright::Util::User.owner_to_owner_group(owner)
34
+ @owner = target_owner unless target_owner.nil?
35
+ @group = target_group unless target_group.nil?
36
+ end
37
+
38
+ # Public: Get the directory's group.
39
+ attr_reader :group
40
+
41
+ # Public: Set the directory's group
42
+ def group=(group)
43
+ @group = Wright::Util::User.group_to_gid(group)
44
+ end
45
+
46
+ # Public: Create or update the directory.
47
+ #
48
+ # Returns true if the directory was updated and false otherwise.
49
+ def create
50
+ might_update_resource do
51
+ @provider.create
52
+ end
53
+ end
54
+
55
+ # Public: Remove the directory.
56
+ #
57
+ # Returns true if the directory was updated and false otherwise.
58
+ def remove
59
+ might_update_resource do
60
+ @provider.remove
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ Wright::DSL.register_resource(Wright::Resource::Directory)
@@ -0,0 +1,67 @@
1
+ require 'wright/resource'
2
+ require 'wright/dsl'
3
+
4
+ module Wright
5
+ class Resource
6
+ # Public: Symlink resource, represents a symlink.
7
+ #
8
+ # Examples
9
+ #
10
+ # file = Wright::Resource::File.new('/tmp/foo')
11
+ # file.content = 'bar'
12
+ # file.create
13
+ class File < Wright::Resource
14
+ # Public: Get/Set the file's content.
15
+ attr_accessor :content
16
+
17
+ # Public: Get/Set the file's group.
18
+ attr_accessor :group
19
+
20
+ # Public: Get/Set the file's mode.
21
+ attr_accessor :mode
22
+
23
+ # Public: Get the file's owner.
24
+ attr_reader :owner
25
+
26
+ # Public: Initialize a File.
27
+ #
28
+ # name - The file's name.
29
+ def initialize(name)
30
+ super
31
+ @content = nil
32
+ @mode = nil
33
+ @owner = nil
34
+ @group = nil
35
+ @action = :create
36
+ end
37
+
38
+ # Public: Set the file's owner.
39
+ def owner=(owner)
40
+ target_owner, target_group =
41
+ Wright::Util::User.owner_to_owner_group(owner)
42
+ @owner = target_owner unless target_owner.nil?
43
+ @group = target_group unless target_group.nil?
44
+ end
45
+
46
+ # Public: Create or update the File.
47
+ #
48
+ # Returns true if the file was updated and false otherwise.
49
+ def create
50
+ might_update_resource do
51
+ @provider.create
52
+ end
53
+ end
54
+
55
+ # Public: Remove the File.
56
+ #
57
+ # Returns true if the file was updated and false otherwise.
58
+ def remove
59
+ might_update_resource do
60
+ @provider.remove
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ Wright::DSL.register_resource(Wright::Resource::File)
@@ -0,0 +1,70 @@
1
+ require 'wright/resource'
2
+ require 'wright/dsl'
3
+
4
+ module Wright
5
+ class Resource
6
+ # Public: Package resource, represents a package.
7
+ #
8
+ # Examples
9
+ #
10
+ # vim = Wright::Resource::Package.new('vim')
11
+ # vim.installed_versions
12
+ # # => []
13
+ # vim.install
14
+ # vim.installed_versions
15
+ # # => ["2:7.3.547-7"]
16
+ #
17
+ # htop = Wright::Resource::Package.new('htop')
18
+ # htop.installed_versions
19
+ # # => ["1.0.1-1"]
20
+ # htop.remove
21
+ # htop.installed_versions
22
+ # # => []
23
+ class Package < Wright::Resource
24
+ # Public: Get/Set the package version to install/remove.
25
+ attr_accessor :version
26
+
27
+ # Public: Initialize a Package.
28
+ #
29
+ # name - The package name.
30
+ def initialize(name)
31
+ super
32
+ @version = nil
33
+ @action = :install
34
+ end
35
+
36
+ # Public: Get the installed version of a package.
37
+ #
38
+ # Returns an array of installed package version Strings.
39
+ def installed_versions
40
+ @provider.installed_versions
41
+ end
42
+
43
+ # Public: Install the Package.
44
+ #
45
+ # Returns true if the package was updated and false otherwise.
46
+ def install
47
+ might_update_resource do
48
+ @provider.install
49
+ end
50
+ end
51
+
52
+ # Public: Remove the Package.
53
+ #
54
+ # Returns true if the package was updated and false otherwise.
55
+ def remove
56
+ might_update_resource do
57
+ @provider.remove
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ Wright::DSL.register_resource(Wright::Resource::Package)
65
+
66
+ # hard-coded for now
67
+ # TODO: remove this
68
+ Wright::Config[:resources] ||= {}
69
+ Wright::Config[:resources][:package] =
70
+ { provider: 'Wright::Provider::Package::Apt' }
@@ -0,0 +1,47 @@
1
+ require 'wright/resource'
2
+ require 'wright/dsl'
3
+
4
+ module Wright
5
+ class Resource
6
+ # Public: Symlink resource, represents a symlink.
7
+ #
8
+ # Examples
9
+ #
10
+ # link = Wright::Resource::Symlink.new('/tmp/fstab')
11
+ # link.to = '/etc/fstab'
12
+ # link.create
13
+ class Symlink < Wright::Resource
14
+ # Public: Initialize a Symlink.
15
+ #
16
+ # name - The link's name.
17
+ def initialize(name)
18
+ super
19
+ @to = nil
20
+ @action = :create
21
+ end
22
+
23
+ # Public: Get/Set the link's target.
24
+ attr_accessor :to
25
+
26
+ # Public: Create or update the Symlink.
27
+ #
28
+ # Returns true if the symlink was updated and false otherwise.
29
+ def create
30
+ might_update_resource do
31
+ @provider.create
32
+ end
33
+ end
34
+
35
+ # Public: Remove the Symlink.
36
+ #
37
+ # Returns true if the symlink was updated and false otherwise.
38
+ def remove
39
+ might_update_resource do
40
+ @provider.remove
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ Wright::DSL.register_resource(Wright::Resource::Symlink)
@@ -0,0 +1,157 @@
1
+ require 'wright/config'
2
+ require 'wright/util'
3
+ require 'wright/logger'
4
+ require 'wright/dry_run'
5
+
6
+ module Wright
7
+ # Public: Resource base class.
8
+ class Resource
9
+ # Public: Initialize a Resource.
10
+ #
11
+ # name - The resource's name.
12
+ def initialize(name = nil)
13
+ @name = name
14
+ @resource_name = Util.class_to_resource_name(self.class).to_sym
15
+ @provider = provider_for_resource
16
+ @action = nil
17
+ @on_update = nil
18
+ @ignore_failure = false
19
+ end
20
+
21
+ # Public: Get/Set the name Symbol of the method to be run by run_action.
22
+ attr_accessor :action
23
+
24
+ # Public: Get/Set the ignore_failure attribute.
25
+ attr_accessor :ignore_failure
26
+
27
+ # Public: Get/Set the resource's name attribute.
28
+ #
29
+ # Examples
30
+ #
31
+ # foo = Wright::Resource::Symlink.new('/tmp/fstab')
32
+ # foo.name
33
+ # # => "/tmp/fstab"
34
+ #
35
+ # bar = Wright::Resource::Symlink.new
36
+ # bar.name = '/tmp/passwd'
37
+ # bar.name
38
+ # # => "/tmp/passwd"
39
+ attr_accessor :name
40
+
41
+ # Public: Returns a compact resource name Symbol.
42
+ #
43
+ # Examples
44
+ #
45
+ # foo = Wright::Resource::Symlink.new
46
+ # foo.resource_name
47
+ # # => :symlink
48
+ attr_reader :resource_name
49
+
50
+ # Public: Set an update action for a resource.
51
+ #
52
+ # on_update - The block that is called if the resource is
53
+ # updated. Has to respond to :call.
54
+ #
55
+ # Returns nothing.
56
+ # Raises ArgumentError if on_update is not callable
57
+ def on_update=(on_update)
58
+ if on_update.respond_to?(:call) || on_update.nil?
59
+ @on_update = on_update
60
+ else
61
+ fail ArgumentError, "#{on_update} is not callable"
62
+ end
63
+ end
64
+
65
+ # Public: Run the resource's current action.
66
+ #
67
+ # Examples
68
+ #
69
+ # fstab = Wright::Resource::Symlink.new('/tmp/fstab')
70
+ # fstab.action = :remove
71
+ # fstab.run_action
72
+ def run_action
73
+ send @action if @action
74
+ end
75
+
76
+ private
77
+
78
+ # Public: Mark a code block that might update a resource.
79
+ #
80
+ # Usually this method is called in the definition of a new
81
+ # resource class in order to mark those methods that should be
82
+ # able to trigger update actions. Runs the current update action
83
+ # if the provider was updated by the block method.
84
+ #
85
+ # Examples
86
+ #
87
+ # class BalloonAnimal < Wright::Provider
88
+ # def inflate
89
+ # puts "It's a giraffe!"
90
+ # @updated = true
91
+ # end
92
+ # end
93
+ #
94
+ # class Balloon < Wright::Resource
95
+ # def inflate
96
+ # might_update_resource { @provider.inflate }
97
+ # end
98
+ # end
99
+ # Wright::Config[:resources] = { balloon: { provider: 'BalloonAnimal' } }
100
+ #
101
+ # balloon = Balloon.new.inflate
102
+ # # => true
103
+ #
104
+ # Returns true if the provider was updated and false otherwise.
105
+ def might_update_resource #:doc:
106
+ begin
107
+ yield
108
+ rescue => e
109
+ log_error(e)
110
+ raise e unless @ignore_failure
111
+ end
112
+ updated = @provider.updated?
113
+ run_update_action if updated
114
+ updated
115
+ end
116
+
117
+ def log_error(exception)
118
+ resource = "#{@resource_name}"
119
+ resource << " '#{@name}'" if @name
120
+ Wright.log.error "#{resource}: #{exception}"
121
+ end
122
+
123
+ def run_update_action
124
+ return if @on_update.nil?
125
+
126
+ if Wright.dry_run?
127
+ resource = "#{@resource_name} '#{@name}'"
128
+ Wright.log.info "Would trigger update action for #{resource}"
129
+ else
130
+ @on_update.call
131
+ end
132
+ end
133
+
134
+ def resource_class
135
+ Util::ActiveSupport.camelize(@resource_name)
136
+ end
137
+
138
+ def provider_name
139
+ if Wright::Config.nested_key?(:resources, @resource_name, :provider)
140
+ Wright::Config[:resources][@resource_name][:provider]
141
+ else
142
+ "Wright::Provider::#{resource_class}"
143
+ end
144
+ end
145
+
146
+ def provider_for_resource
147
+ klass = Util::ActiveSupport.safe_constantize(provider_name)
148
+ if klass
149
+ klass.new(self)
150
+ else
151
+ warning = "Could not find a provider for resource #{resource_class}"
152
+ Wright.log.warn warning
153
+ nil
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,51 @@
1
+ module Wright
2
+ module Util
3
+ # Internal: ANSI color helpers.
4
+ module Color
5
+ # Internal: Colorize a string (red).
6
+ #
7
+ # string - The string to colorize.
8
+ #
9
+ # Returns the colorized String.
10
+ def self.red(string)
11
+ colorize(string, :red)
12
+ end
13
+
14
+ # Internal: Colorize a string (yellow).
15
+ #
16
+ # string - The string to colorize.
17
+ #
18
+ # Returns the colorized String.
19
+ def self.yellow(string)
20
+ colorize(string, :yellow)
21
+ end
22
+
23
+ # Internal: Colorize a string.
24
+ #
25
+ # string - The string to colorize.
26
+ # color - The color that should be used.
27
+ #
28
+ # Examples
29
+ #
30
+ # Wright::Util::Color.colorize('Hello world', :red)
31
+ # # => "\e[31mHello world\e[0m"
32
+ #
33
+ # Wright::Util::Color.colorize('Hello world', :yellow)
34
+ # # => "\e[32mHello world\e[0m"
35
+ #
36
+ # Returns the colorized String.
37
+ def self.colorize(string, color)
38
+ no_color = COLOR_MAP[:none]
39
+ color = COLOR_MAP.fetch(color, no_color)
40
+ "#{color}#{string}#{no_color}"
41
+ end
42
+
43
+ COLOR_MAP = { #:nodoc:
44
+ none: "\e[0m",
45
+ red: "\e[31m",
46
+ yellow: "\e[32m"
47
+ }
48
+ private_constant :COLOR_MAP
49
+ end
50
+ end
51
+ end