dopi 0.17.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 (149) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -0
  3. data/.rspec +2 -0
  4. data/.ruby-version +1 -0
  5. data/CHANGELOG.md +322 -0
  6. data/Gemfile +7 -0
  7. data/Gemfile.lock +102 -0
  8. data/LICENSE.txt +177 -0
  9. data/README.md +309 -0
  10. data/Rakefile +44 -0
  11. data/Vagrantfile +64 -0
  12. data/bin/dopi +4 -0
  13. data/doc/getting_started.md +247 -0
  14. data/doc/getting_started_examples/001_hello_world.yaml +17 -0
  15. data/doc/getting_started_examples/002_connecting_over_ssh.yaml +35 -0
  16. data/doc/plugins/custom.md +88 -0
  17. data/doc/plugins/mco/rpc.md +82 -0
  18. data/doc/plugins/ssh/custom.md +141 -0
  19. data/doc/plugins/ssh/file_contains.md +37 -0
  20. data/doc/plugins/ssh/file_deploy.md +52 -0
  21. data/doc/plugins/ssh/file_exists.md +31 -0
  22. data/doc/plugins/ssh/file_replace.md +37 -0
  23. data/doc/plugins/ssh/puppet_agent_run.md +50 -0
  24. data/doc/plugins/ssh/reboot.md +22 -0
  25. data/doc/plugins/ssh/wait_for_login.md +53 -0
  26. data/doc/plugins/winrm/cmd.md +161 -0
  27. data/doc/plugins/winrm/file_contains.md +39 -0
  28. data/doc/plugins/winrm/file_exists.md +31 -0
  29. data/doc/plugins/winrm/powershell.md +27 -0
  30. data/doc/plugins/winrm/puppet_agent_run.md +49 -0
  31. data/doc/plugins/winrm/reboot.md +17 -0
  32. data/doc/plugins/winrm/wait_for_login.md +55 -0
  33. data/dopi.gemspec +42 -0
  34. data/lib/dopi/cli/command_add.rb +35 -0
  35. data/lib/dopi/cli/command_list.rb +19 -0
  36. data/lib/dopi/cli/command_remove.rb +31 -0
  37. data/lib/dopi/cli/command_reset.rb +27 -0
  38. data/lib/dopi/cli/command_run.rb +68 -0
  39. data/lib/dopi/cli/command_show.rb +109 -0
  40. data/lib/dopi/cli/command_update.rb +37 -0
  41. data/lib/dopi/cli/command_validate.rb +27 -0
  42. data/lib/dopi/cli/global_options.rb +55 -0
  43. data/lib/dopi/cli/log.rb +33 -0
  44. data/lib/dopi/cli.rb +57 -0
  45. data/lib/dopi/command/custom.rb +52 -0
  46. data/lib/dopi/command/dummy.rb +27 -0
  47. data/lib/dopi/command/mco/rpc.rb +158 -0
  48. data/lib/dopi/command/ssh/custom.rb +48 -0
  49. data/lib/dopi/command/ssh/file_contains.rb +70 -0
  50. data/lib/dopi/command/ssh/file_deploy.rb +71 -0
  51. data/lib/dopi/command/ssh/file_exists.rb +54 -0
  52. data/lib/dopi/command/ssh/file_replace.rb +96 -0
  53. data/lib/dopi/command/ssh/puppet_agent_run.rb +63 -0
  54. data/lib/dopi/command/ssh/reboot.rb +50 -0
  55. data/lib/dopi/command/ssh/wait_for_login.rb +68 -0
  56. data/lib/dopi/command/winrm/cmd.rb +44 -0
  57. data/lib/dopi/command/winrm/file_contains.rb +66 -0
  58. data/lib/dopi/command/winrm/file_exists.rb +51 -0
  59. data/lib/dopi/command/winrm/powershell.rb +16 -0
  60. data/lib/dopi/command/winrm/puppet_agent_run.rb +61 -0
  61. data/lib/dopi/command/winrm/reboot.rb +33 -0
  62. data/lib/dopi/command/winrm/wait_for_login.rb +49 -0
  63. data/lib/dopi/command.rb +239 -0
  64. data/lib/dopi/command_parser/arguments.rb +38 -0
  65. data/lib/dopi/command_parser/credentials.rb +59 -0
  66. data/lib/dopi/command_parser/env.rb +37 -0
  67. data/lib/dopi/command_parser/exec.rb +27 -0
  68. data/lib/dopi/command_parser/exit_code.rb +73 -0
  69. data/lib/dopi/command_parser/output.rb +126 -0
  70. data/lib/dopi/command_set.rb +66 -0
  71. data/lib/dopi/connector/local.rb +77 -0
  72. data/lib/dopi/connector/ssh.rb +170 -0
  73. data/lib/dopi/connector/winrm.rb +167 -0
  74. data/lib/dopi/error.rb +43 -0
  75. data/lib/dopi/log.rb +18 -0
  76. data/lib/dopi/node.rb +70 -0
  77. data/lib/dopi/plan.rb +99 -0
  78. data/lib/dopi/pluginmanager.rb +62 -0
  79. data/lib/dopi/state.rb +226 -0
  80. data/lib/dopi/state_store.rb +155 -0
  81. data/lib/dopi/step.rb +227 -0
  82. data/lib/dopi/step_set.rb +70 -0
  83. data/lib/dopi/version.rb +3 -0
  84. data/lib/dopi.rb +165 -0
  85. data/spec/command_helper.rb +11 -0
  86. data/spec/fixtures/mco_client.cfg +26 -0
  87. data/spec/fixtures/plans/fail_on_timeout.yaml +20 -0
  88. data/spec/fixtures/plans/hello_world.yaml +34 -0
  89. data/spec/fixtures/plans/non_existing_node.yaml +26 -0
  90. data/spec/fixtures/plans/test_role_variable.yaml +29 -0
  91. data/spec/fixtures/puppet/Puppetfile +8 -0
  92. data/spec/fixtures/puppet/Puppetfile.lock +57 -0
  93. data/spec/fixtures/puppet/hiera.yaml +6 -0
  94. data/spec/fixtures/puppet/manifests/site.pp +52 -0
  95. data/spec/fixtures/test_configuration.yaml +54 -0
  96. data/spec/fixtures/test_credentials.yaml +11 -0
  97. data/spec/fixtures/test_deloyed_file.txt +5 -0
  98. data/spec/fixtures/test_infrastructure.yaml +12 -0
  99. data/spec/fixtures/test_nodes.yaml +45 -0
  100. data/spec/fixtures/testenv_plan.yaml +159 -0
  101. data/spec/integration/dopi/addrun_spec.rb +31 -0
  102. data/spec/integration/dopi/cli/command_run_spec.rb +38 -0
  103. data/spec/integration/dopi/cli/global_options_spec.rb +128 -0
  104. data/spec/integration/dopi/command_spec.rb +66 -0
  105. data/spec/integration/dopi/fail_check_plans/file_exists_fails.yaml +38 -0
  106. data/spec/integration/dopi/fail_check_plans/output_parser.yaml +39 -0
  107. data/spec/integration/dopi/fail_check_plans/powershell_fail.yaml +25 -0
  108. data/spec/integration/dopi/fail_check_plans/timeout.yaml +29 -0
  109. data/spec/integration/dopi/fail_check_plans/verify_commands.yaml +33 -0
  110. data/spec/integration/dopi/failplan.rb +27 -0
  111. data/spec/integration/dopi/plan.rb +27 -0
  112. data/spec/integration/dopi/plans/dummy.yaml +29 -0
  113. data/spec/integration/dopi/plans/max_per_role.yaml +55 -0
  114. data/spec/integration/dopi/plans/no_timeout.yaml +29 -0
  115. data/spec/integration/dopi/plans/node_and_role_patterns.yaml +58 -0
  116. data/spec/integration/dopi/plans/node_by_config.yaml +116 -0
  117. data/spec/integration/dopi/plans/plugin_defaults.yaml +86 -0
  118. data/spec/integration/dopi/plans/plugins/mco/rpc.yaml +33 -0
  119. data/spec/integration/dopi/plans/plugins/ssh/custom.yaml +97 -0
  120. data/spec/integration/dopi/plans/plugins/ssh/file_contains.yaml +51 -0
  121. data/spec/integration/dopi/plans/plugins/ssh/file_deploy.yaml +82 -0
  122. data/spec/integration/dopi/plans/plugins/ssh/file_exists.yaml +69 -0
  123. data/spec/integration/dopi/plans/plugins/ssh/file_replace.yaml +55 -0
  124. data/spec/integration/dopi/plans/plugins/ssh/puppet_agent_run.yaml +45 -0
  125. data/spec/integration/dopi/plans/plugins/ssh/reboot.yaml +43 -0
  126. data/spec/integration/dopi/plans/plugins/ssh/wait_for_login.yaml +45 -0
  127. data/spec/integration/dopi/plans/plugins/winrm/cmd.yaml +39 -0
  128. data/spec/integration/dopi/plans/plugins/winrm/file_contains.yaml +51 -0
  129. data/spec/integration/dopi/plans/plugins/winrm/file_exists.yaml +69 -0
  130. data/spec/integration/dopi/plans/plugins/winrm/reboot.yaml +31 -0
  131. data/spec/integration/dopi/plans/resolve_roles_on_validate.yaml +23 -0
  132. data/spec/integration/dopi/plans/ssh_parallel.yaml +37 -0
  133. data/spec/integration/dopi/plans/verify_commands.yaml +49 -0
  134. data/spec/spec_helper.rb +104 -0
  135. data/spec/unit/dopi/command/custom_spec.rb +58 -0
  136. data/spec/unit/dopi/command/mco/rpc_spec.rb +157 -0
  137. data/spec/unit/dopi/command/ssh/custom_spec.rb +30 -0
  138. data/spec/unit/dopi/command/ssh/file_deploy_spec.rb +42 -0
  139. data/spec/unit/dopi/command/ssh/file_replace_spec.rb +35 -0
  140. data/spec/unit/dopi/command_parser/credentials_spec.rb +53 -0
  141. data/spec/unit/dopi/command_parser/exit_code_spec.rb +63 -0
  142. data/spec/unit/dopi/command_parser/output_spec.rb +129 -0
  143. data/spec/unit/dopi/command_spec.rb +14 -0
  144. data/spec/unit/dopi/connector/winrm_spec.rb +111 -0
  145. data/spec/unit/dopi/node_spec.rb +24 -0
  146. data/spec/unit/dopi/plan_spec.rb +31 -0
  147. data/spec/unit/dopi/state_spec.rb +109 -0
  148. data/spec/unit/dopi/step_spec.rb +13 -0
  149. metadata +448 -0
data/lib/dopi/plan.rb ADDED
@@ -0,0 +1,99 @@
1
+ #
2
+ # This class loads a deployment plan
3
+ #
4
+ require 'forwardable'
5
+ require 'yaml'
6
+ require 'dop_common'
7
+ require 'fileutils'
8
+
9
+ module Dopi
10
+ class Plan
11
+ extend Forwardable
12
+ include Dopi::State
13
+
14
+ attr_reader :plan_parser, :version, :context_logger
15
+
16
+ def initialize(plan_parser)
17
+ @version = Dopi::VERSION
18
+ @plan_parser = plan_parser
19
+
20
+ step_sets.each{|step_set| state_add_child(step_set)}
21
+ end
22
+
23
+ def_delegators :@plan_parser,
24
+ :name,
25
+ :configuration,
26
+ :credentials,
27
+ :max_in_flight,
28
+ :max_per_role,
29
+ :canary_host
30
+
31
+ def run(options = {})
32
+ options_defaults = {
33
+ :run_for_nodes => :all,
34
+ :noop => false,
35
+ :step_set => 'default',
36
+ :node_info => {},
37
+ :run_id => Time.now.strftime('%Y%m%d-%H%M%S'),
38
+ }
39
+ run_options = options_defaults.merge(options)
40
+
41
+ context_log_path = File.join(DopCommon.config.log_dir, "#{run_options[:run_id]}-#{name}")
42
+ node_names = nodes.map{|n| n.name}
43
+ @context_logger = DopCommon::ThreadContextLogger.new(context_log_path, node_names)
44
+
45
+ nodes.each{|node| node.node_info = run_options[:node_info][node.fqdn] || {}}
46
+ step_set = step_sets.find{|s| s.name == run_options[:step_set]}
47
+ raise "Plan: Step set #{run_options[:step_set]} does not exist" if step_set.nil?
48
+ step_set.run(run_options)
49
+ ensure
50
+ @context_logger.cleanup
51
+ end
52
+
53
+ # The main validation work is done in the dop_common
54
+ # parser. We just add the command plugin parsers
55
+ def valid?
56
+ validity = @plan_parser.valid?
57
+ validity = false unless step_sets.all?{|step_set| step_set.valid? }
58
+ validity
59
+ rescue => e
60
+ DopCommon.config.trace ? Dopi.log.error(e) : Dopi.log.error(e.message)
61
+ Dopi.log.warn("Plan: Can't validate the command plugins because of a previous error")
62
+ end
63
+
64
+ def nodes
65
+ @nodes ||= parsed_nodes.map do |parsed_node|
66
+ ::Dopi::Node.new(parsed_node, self)
67
+ end
68
+ end
69
+
70
+ def step_sets
71
+ @step_sets ||= parsed_step_sets.map do |parsed_step_set|
72
+ ::Dopi::StepSet.new(parsed_step_set, self)
73
+ end
74
+ end
75
+
76
+ def load_state(state_hash)
77
+ if state_hash[:step_sets].kind_of?(Hash)
78
+ step_sets.each do |step_set|
79
+ step_set_state = state_hash[:step_sets][step_set.name] || []
80
+ step_set.load_state(step_set_state)
81
+ end
82
+ end
83
+ end
84
+
85
+ def state_hash
86
+ step_sets_hash = {}
87
+ step_sets.each do |step_set|
88
+ step_sets_hash[step_set.name] = step_set.state_hash
89
+ end
90
+ {:step_sets => step_sets_hash}
91
+ end
92
+
93
+ private
94
+
95
+ def_delegator :@plan_parser, :nodes, :parsed_nodes
96
+ def_delegator :@plan_parser, :step_sets, :parsed_step_sets
97
+
98
+ end
99
+ end
@@ -0,0 +1,62 @@
1
+ #
2
+ # This class registers the plugins as they get loaded by ruby
3
+ # and can create instances based on the plugin name
4
+ #
5
+
6
+ module Dopi
7
+ module PluginManager
8
+
9
+ @plugins = {}
10
+
11
+ def self.<<(plugin_klass)
12
+ plugin_name = get_plugin_name(plugin_klass)
13
+
14
+ raise Dopi::PluginLoaderError,
15
+ "Plugin class #{plugin_klass.to_s} (#{plugin_name}) already loaded" if @plugins[plugin_name]
16
+
17
+ @plugins[plugin_name] = plugin_klass
18
+ end
19
+
20
+ def self.create_instance(plugin_name, *args)
21
+ begin
22
+ @plugins[plugin_name].new(*args)
23
+ rescue Exception => e
24
+ raise PluginLoaderError, "Could not create instance of plugin #{plugin_name}: #{e.message}"
25
+ end
26
+ end
27
+
28
+ def self.get_plugin_name(plugin_klass)
29
+ plugin_klass.to_s.
30
+ gsub(/::/, '/').
31
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
32
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
33
+ tr("-", "_").
34
+ downcase
35
+ end
36
+
37
+ # Expects a regular expression as a plugin filter
38
+ def self.plugin_list(filter = nil)
39
+ if filter
40
+ regexp = Regexp.new(filter)
41
+ @plugins.select{|p| p =~ regexp}
42
+ else
43
+ @plugins
44
+ end
45
+ end
46
+
47
+ # Expects a regular expression as a plugin filter
48
+ def self.plugin_name_list(filter = nil)
49
+ plugin_list(filter).keys
50
+ end
51
+
52
+ # Expects a regular expression as a plugin filter
53
+ def self.plugin_klass_list(filter = nil)
54
+ plugin_list(filter).values
55
+ end
56
+
57
+ def self.plugin_klass(plugin_name)
58
+ @plugins[plugin_name]
59
+ end
60
+
61
+ end
62
+ end
data/lib/dopi/state.rb ADDED
@@ -0,0 +1,226 @@
1
+ #
2
+ # This is a simple hierarchical state tracker which chan keep
3
+ # track of it's state based on it's children
4
+ #
5
+ require 'observer'
6
+
7
+ module Dopi
8
+ module State
9
+ include Observable
10
+
11
+ def to_yaml_properties
12
+ instance_variables - [:@signal_procs]
13
+ end
14
+
15
+ def state
16
+ @state ||= :ready
17
+ end
18
+
19
+ def state_auto_evaluate_children
20
+ @auto_evaluate_children = true if @auto_evaluate_children.nil?
21
+ @auto_evaluate_children
22
+ end
23
+
24
+ def state_auto_evaluate_children=(value)
25
+ @auto_evaluate_children = value
26
+ end
27
+
28
+ def state_children
29
+ @state_children ||= []
30
+ end
31
+
32
+ def state_add_child(child)
33
+ state_children << child
34
+ child.add_observer(self)
35
+ end
36
+
37
+ def state_children_ready?
38
+ state_children.any? {|child| child.state_ready?}
39
+ end
40
+
41
+ def state_children_starting?
42
+ state_children.any? {|child| child.state_starting?}
43
+ end
44
+
45
+ def state_children_running_noop?
46
+ state_children.any? {|child| child.state_running_noop?}
47
+ end
48
+
49
+ def state_children_running?
50
+ state_children.any? {|child| child.state_running?}
51
+ end
52
+
53
+ def state_children_failed?
54
+ state_children.any? {|child| child.state_failed?}
55
+ end
56
+
57
+ def state_children_done?
58
+ state_children.all? {|child| child.state_done?}
59
+ end
60
+
61
+ def state_children_partial?
62
+ state_partial? || state_children.any?{|c| c.state_children_partial?}
63
+ end
64
+
65
+ def update_mutex
66
+ @upate_mutex || Mutex.new
67
+ end
68
+
69
+ def update(notify_only)
70
+ update_mutex.synchronize do
71
+ unless notify_only
72
+ old_state = @state
73
+ Dopi.log.debug("Checking if state of '#{name}' needs to be updated")
74
+ unless state_children.empty? || !state_auto_evaluate_children
75
+ if state_children_failed? then @state = :failed
76
+ elsif state_children_done? then @state = :done
77
+ elsif state_children_running? then @state = :running
78
+ elsif state_children_running_noop? then @state = :running_noop
79
+ elsif state_children_starting? then @state = :starting
80
+ elsif state_children_ready? then @state = :ready
81
+ end
82
+ if old_state == @state
83
+ state_changed(true)
84
+ else
85
+ state_changed(false)
86
+ end
87
+ end
88
+ else
89
+ state_changed(true)
90
+ end
91
+ end
92
+ end
93
+
94
+ def state_reset_with_children(force = false)
95
+ state_reset(force) if state_failed? or force
96
+ if state_children_failed? or force
97
+ state_children.each {|child| child.state_reset_with_children(force) }
98
+ end
99
+ end
100
+
101
+ def state_ready?
102
+ state == :ready
103
+ end
104
+
105
+ def state_starting?
106
+ state == :starting
107
+ end
108
+
109
+ def state_running?
110
+ state == :running
111
+ end
112
+
113
+ def state_running_noop?
114
+ state == :running_noop
115
+ end
116
+
117
+ def state_done?
118
+ state == :done
119
+ end
120
+
121
+ def state_failed?
122
+ state == :failed
123
+ end
124
+
125
+ def state_partial?
126
+ [:failed, :done, :running, :running_noop, :starting, :ready].each do |s|
127
+ return false if state_children.all?{|c| c.state == s}
128
+ end
129
+ return true
130
+ end
131
+
132
+ def state_start
133
+ return if state == :starting
134
+ raise Dopi::StateTransitionError, "Can't switch to running from #{state.to_s}" unless state == :ready
135
+ @state = :starting
136
+ state_changed
137
+ end
138
+
139
+ def state_run
140
+ return if state == :running
141
+ raise Dopi::StateTransitionError, "Can't switch to running from #{state.to_s}" unless state == :ready || state == :starting
142
+ @state = :running
143
+ state_changed
144
+ end
145
+
146
+ def state_run_noop
147
+ return if state == :running_noop
148
+ raise Dopi::StateTransitionError, "Can't switch to running_noop from #{state.to_s}" unless state == :ready || state == :starting
149
+ @state = :running_noop
150
+ state_changed
151
+ end
152
+
153
+ def state_ready
154
+ return if state == :ready
155
+ raise Dopi::StateTransitionError, "Can't switch to ready from #{state.to_s}" unless state == :running_noop
156
+ @state = :ready
157
+ state_changed
158
+ end
159
+
160
+ def state_finish
161
+ return if state == :done
162
+ raise Dopi::StateTransitionError, "Can't switch to done from #{state.to_s}" unless state == :running
163
+ @state = :done
164
+ state_changed
165
+ end
166
+
167
+ def state_fail
168
+ return if state == :failed
169
+ raise Dopi::StateTransitionError, "Can't switch to failed from #{state.to_s}" unless state == :running
170
+ @state = :failed
171
+ state_changed
172
+ end
173
+
174
+ def state_reset(force = false)
175
+ if force
176
+ state_children.each {|child| child.state_reset(force)}
177
+ @state = :ready
178
+ state_changed
179
+ else
180
+ raise Dopi::StateTransitionError, "Can't switch to ready from #{state.to_s}" unless state == :failed || state == :ready
181
+ if state_children.any?
182
+ state_children.each {|child| child.state_reset unless (child.state_done? || child.state_ready?)}
183
+ else
184
+ @state = :ready
185
+ state_changed
186
+ end
187
+ end
188
+ end
189
+
190
+ def state_changed(notify_only = false)
191
+ Dopi.log.debug("State of '#{name}' updated to #{@state.to_s}, notifying observers")
192
+ changed
193
+ notify_observers(notify_only)
194
+ end
195
+
196
+ def signals
197
+ @signals ||= Hash.new(false)
198
+ end
199
+
200
+ def reset_signals
201
+ @signals = Hash.new(false)
202
+ state_children.each {|child| child.reset_signals}
203
+ end
204
+
205
+ def signal_procs
206
+ @signal_procs ||= []
207
+ end
208
+
209
+ def on_signal(a_proc)
210
+ signal_procs << a_proc
211
+ end
212
+
213
+ def delete_on_signal(a_proc)
214
+ signal_procs.delete(a_proc)
215
+ end
216
+
217
+ def send_signal(signal)
218
+ unless signals[signal] == true
219
+ signal_procs.each {|p| p.call(signal)}
220
+ state_children.each {|child| child.send_signal(signal)}
221
+ signals[signal] = true
222
+ end
223
+ end
224
+
225
+ end
226
+ end
@@ -0,0 +1,155 @@
1
+ #
2
+ # This is the DOPi state store which persists the state
3
+ # of the steps between and during runs.
4
+ #
5
+ module Dopi
6
+ class StateStore
7
+
8
+ def initialize(plan_name, plan_store)
9
+ @plan_store = plan_store
10
+ @plan_name = plan_name
11
+ @state_store = @plan_store.state_store(plan_name, 'dopi')
12
+ end
13
+
14
+ def update(options = {})
15
+ if options[:clear]
16
+ clear(options)
17
+ elsif options[:ignore]
18
+ ignore(options)
19
+ else
20
+ update_state(options)
21
+ end
22
+ rescue DopCommon::UnknownVersionError => e
23
+ Dopi.log.warn("The state has an unknown plan version #{e.message}.")
24
+ Dopi.log.warn("Please update with the 'clear' or 'ignore' option")
25
+ rescue => e
26
+ Dopi.log.error("An error occured during update: #{e.message}")
27
+ Dopi.log.error("Please update with the 'clear' or 'ignore' option")
28
+ end
29
+
30
+ def persist_state(plan)
31
+ @state_store.transaction do
32
+ plan_state = plan.state_hash
33
+ Dopi.log.debug('Persisting plan state:')
34
+ Dopi.log.debug(plan_state)
35
+ @state_store[:state] = plan_state
36
+ end
37
+ end
38
+
39
+ def state_hash
40
+ @state_store.transaction(true) do
41
+ @state_store[:state] || {}
42
+ end
43
+ end
44
+
45
+ def method_missing(m, *args, &block)
46
+ @state_store.send(m, *args, &block)
47
+ end
48
+
49
+ private
50
+
51
+ def clear(options)
52
+ @state_store.transaction do
53
+ Dopi.log.debug("Clearing the state for plan #{@plan_name}")
54
+ ver = @plan_store.show_versions(@plan_name).last
55
+ plan = Dopi::Plan.new(@plan_store.get_plan(@plan_name))
56
+ @state_store[:state] = plan.state_hash
57
+ @state_store[:version] = ver
58
+ end
59
+ end
60
+
61
+ def ignore(options)
62
+ @state_store.transaction do
63
+ ver = @plan_store.show_versions(@plan_name).last
64
+ Dopi.log.debug("Ignoring update and setting state version of plan #{@plan_name} to #{ver}")
65
+ @state_store[:version] = ver
66
+ end
67
+ end
68
+
69
+ def update_state(options)
70
+ @state_store.update do |plan_diff|
71
+ Dopi.log.debug("Updating plan #{@plan_name}. This is the diff:")
72
+ Dopi.log.debug(plan_diff.to_s)
73
+
74
+ plan_diff.each do |patch|
75
+ match ||= update_rule_steps(patch)
76
+ unless match
77
+ Dopi.log.debug("No rule matched, ignoring patch: #{patch.to_s}")
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ def update_rule_steps(patch)
84
+ match = /^steps\.?(\w*)\[(\d+)\](.*)/.match(patch[1])
85
+ if match
86
+ step_set = match[1].empty? ? 'default' : match[1]
87
+ step_nr = match[2].to_i
88
+ rest = match[3]
89
+ unless update_rule_commands(patch, step_set, step_nr, rest)
90
+ case patch[0]
91
+ when '+' then add_step(step_set, step_nr)
92
+ when '-' then del_step(step_set, step_nr)
93
+ else
94
+ Dopi.log.debug("Step changed, ignoring patch: #{patch.to_s}")
95
+ end
96
+ end
97
+ return true
98
+ end
99
+ return false
100
+ end
101
+
102
+ def update_rule_commands(patch, step_set, step_nr, rest)
103
+ match = /^\.commands?\[(\d+)\](.*)/.match(rest)
104
+ if match
105
+ command_nr = match[1]
106
+ if /^\.verify_command/.match(rest)
107
+ Dopi.log.debug("Change in verify_command only, ignoring patch: #{patch.to_s}")
108
+ else
109
+ case patch[0]
110
+ when '+' then add_command(step_set, step_nr)
111
+ when '-' then del_command(step_set, step_nr)
112
+ else
113
+ Dopi.log.debug("Command changed, ignoring patch: #{patch.to_s}")
114
+ end
115
+ end
116
+ return true
117
+ end
118
+ return false
119
+ end
120
+
121
+ def add_step(step_set, step_nr)
122
+ @state_store[:state][:step_sets][step_set].insert(step_nr, {})
123
+ end
124
+
125
+ def del_step(step_set, step_nr)
126
+ @state_store[:state][:step_sets][step_set].delete_at(step_nr)
127
+ end
128
+
129
+ def add_command(step_set, step_nr, command_nr)
130
+ @state_store[:state][:step_sets][step_set][step_nr].each do |node|
131
+ node.insert(command_nr, {:command_state => :ready})
132
+ end
133
+ end
134
+
135
+ def del_command(step_set, step_nr, command_nr)
136
+ @state_store[:state][:step_sets][step_set][step_nr].each do |node|
137
+ node.delete_at(command_nr)
138
+ end
139
+ end
140
+
141
+ end
142
+
143
+ class StateStoreObserver
144
+
145
+ def initialize(plan, state_store)
146
+ @plan = plan
147
+ @state_store = state_store
148
+ end
149
+
150
+ def update(notify_only = false)
151
+ @state_store.persist_state(@plan)
152
+ end
153
+
154
+ end
155
+ end