dopi 0.17.0

Sign up to get free protection for your applications and to get access to all the features.
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