kafo 0.6.12 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -37,6 +37,13 @@ module Kafo
37
37
  COLOR_LAYOUT = Logging::Layouts::Pattern.new(:pattern => PATTERN, :color_scheme => 'bright')
38
38
  NOCOLOR_LAYOUT = Logging::Layouts::Pattern.new(:pattern => PATTERN, :color_scheme => nil)
39
39
 
40
+ def self.setup_fatal_logger(layout)
41
+ fatal_logger = Logging.logger['fatal']
42
+ fatal_logger.level = 'fatal'
43
+ fatal_logger.appenders = [::Logging.appenders.stderr(:layout => layout)]
44
+ self.loggers << fatal_logger
45
+ end
46
+
40
47
  def self.setup
41
48
  begin
42
49
  FileUtils.mkdir_p(KafoConfigure.config.app[:log_dir], :mode => 0750)
@@ -45,7 +52,7 @@ module Kafo
45
52
  end
46
53
 
47
54
  logger = Logging.logger['main']
48
- filename = "#{KafoConfigure.config.app[:log_dir]}/#{KafoConfigure.config.app[:log_name] || 'configure.log'}"
55
+ filename = KafoConfigure.config.log_file
49
56
  begin
50
57
  logger.appenders = ::Logging.appenders.rolling_file('configure',
51
58
  :filename => filename,
@@ -59,19 +66,15 @@ module Kafo
59
66
  end
60
67
 
61
68
  logger.level = KafoConfigure.config.app[:log_level]
69
+ self.loggers << logger
62
70
 
63
- fatal_logger = Logging.logger['fatal']
64
- fatal_logger.level = 'fatal'
65
- layout = KafoConfigure.config.app[:colors] ? COLOR_LAYOUT : NOCOLOR_LAYOUT
66
- fatal_logger.appenders = [::Logging.appenders.stderr(:layout => layout)]
67
-
68
- self.loggers = [logger, fatal_logger]
71
+ setup_fatal_logger(color_layout) unless loggers.detect {|l| l.name == 'verbose'}
69
72
  end
70
73
 
71
74
  def self.setup_verbose
72
75
  logger = Logging.logger['verbose']
73
- logger.level = KafoConfigure.config.app[:verbose_log_level]
74
- layout = KafoConfigure.config.app[:colors] ? COLOR_LAYOUT : NOCOLOR_LAYOUT
76
+ logger.level = (KafoConfigure.config && KafoConfigure.config.app[:verbose_log_level]) || :info
77
+ layout = color_layout
75
78
  logger.appenders = [::Logging.appenders.stdout(:layout => layout)]
76
79
  self.loggers<< logger
77
80
  end
@@ -89,8 +92,9 @@ module Kafo
89
92
  end
90
93
 
91
94
  def self.dump_errors
95
+ setup_fatal_logger(color_layout) if loggers.empty?
92
96
  unless self.error_buffer.empty?
93
- loggers.each { |logger| logger.error 'Repeating errors encountered during run:' }
97
+ loggers.each { |logger| logger.error 'Errors encountered during run:' }
94
98
  self.dump_buffer(self.error_buffer)
95
99
  end
96
100
  end
@@ -106,6 +110,10 @@ module Kafo
106
110
  buffer.clear
107
111
  end
108
112
 
113
+ def self.color_layout
114
+ KafoConfigure.use_colors? ? COLOR_LAYOUT : NOCOLOR_LAYOUT
115
+ end
116
+
109
117
  def log(name, *args, &block)
110
118
  if self.class.buffering?
111
119
  self.class.to_buffer(self.class.buffer, name, args, &block)
@@ -0,0 +1,21 @@
1
+ module Kafo
2
+ class MigrationContext
3
+
4
+ attr_accessor :scenario, :answers
5
+
6
+ def self.execute(scenario, answers, &migration)
7
+ context = new(scenario, answers)
8
+ context.instance_eval(&migration)
9
+ return context.scenario, context.answers
10
+ end
11
+
12
+ def initialize(scenario, answers)
13
+ @scenario = scenario
14
+ @answers = answers
15
+ end
16
+
17
+ def logger
18
+ KafoConfigure.logger
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,54 @@
1
+ require 'yaml'
2
+ require 'kafo/migration_context'
3
+
4
+ module Kafo
5
+ class Migrations
6
+
7
+ attr_reader :migrations
8
+
9
+ def initialize(migrations_dir)
10
+ @migrations_dir = migrations_dir
11
+ @migrations = {}
12
+ @applied_file = File.join(@migrations_dir, '.applied')
13
+ load_migrations
14
+ end
15
+
16
+ def applied
17
+ @applied ||= load_applied
18
+ end
19
+
20
+ def load_migrations
21
+ Dir.glob(@migrations_dir + "/*.rb").each do |file|
22
+ next if applied.include?(File.basename(file))
23
+ KafoConfigure.logger.debug "Loading migration #{file}"
24
+ migration = File.read(file)
25
+ migration_block = proc { instance_eval(migration, file, 1) }
26
+ add_migration(file, &migration_block)
27
+ end
28
+ end
29
+
30
+ def add_migration(name, &block)
31
+ @migrations[name] = block
32
+ end
33
+
34
+ def run(scenario, answers)
35
+ @migrations.keys.sort.each do |name|
36
+ KafoConfigure.logger.debug "Executing migration #{name}"
37
+ migration = @migrations[name]
38
+ scenario, answers = Kafo::MigrationContext.execute(scenario, answers, &migration)
39
+ applied << File.basename(name.to_s)
40
+ end
41
+ return scenario, answers
42
+ end
43
+
44
+ def store_applied
45
+ File.open(@applied_file, 'w') { |f| f.write(applied.to_yaml) }
46
+ end
47
+
48
+ private
49
+
50
+ def load_applied
51
+ File.exist?(@applied_file) ? YAML.load_file(@applied_file) : []
52
+ end
53
+ end
54
+ end
@@ -99,7 +99,7 @@ module Kafo
99
99
  end
100
100
 
101
101
  def <=> o
102
- unless KafoConfigure.config.app[:no_prefix]
102
+ unless @module.configuration.app[:no_prefix]
103
103
  r = self.module_name <=> o.module_name
104
104
  return r unless r == 0
105
105
  end
@@ -10,7 +10,7 @@ module Kafo
10
10
  end
11
11
 
12
12
  def validate
13
- return true if KafoConfigure.config.app[:ignore_undocumented]
13
+ return true if @module.configuration.app[:ignore_undocumented]
14
14
 
15
15
  parameters = @data[:parameters].sort
16
16
  docs = @data[:docs].keys.sort
@@ -51,7 +51,7 @@ module Kafo
51
51
  end
52
52
 
53
53
  def phrase
54
- KafoConfigure.config.app[:password]
54
+ @module.configuration.app[:password]
55
55
  end
56
56
 
57
57
  end
@@ -1,7 +1,8 @@
1
1
  # encoding: UTF-8
2
2
  module Kafo
3
3
  class PuppetCommand
4
- def initialize(command, options = [])
4
+ def initialize(command, options = [], configuration = KafoConfigure.config)
5
+ @configuration = configuration
5
6
  @command = command
6
7
 
7
8
  # Expand the modules_path to work around the fact that Puppet doesn't
@@ -21,9 +22,9 @@ module Kafo
21
22
 
22
23
  def command
23
24
  result = [
24
- "echo '$kafo_config_file=\"#{KafoConfigure.config_file}\" #{custom_answer_file} #{add_progress} #{@command}'",
25
+ "echo '$kafo_config_file=\"#{@configuration.config_file}\" #{custom_answer_file} #{add_progress} #{@command}'",
25
26
  '|',
26
- "RUBYLIB=#{["#{KafoConfigure.gem_root}/modules", ::ENV['RUBYLIB']].join(File::PATH_SEPARATOR)}",
27
+ "RUBYLIB=#{["#{@configuration.gem_root}/modules", ::ENV['RUBYLIB']].join(File::PATH_SEPARATOR)}",
27
28
  "puppet apply #{@options.join(' ')} #{@suffix}",
28
29
  ].join(' ')
29
30
  @logger.debug result
@@ -39,9 +40,9 @@ module Kafo
39
40
 
40
41
  def modules_path
41
42
  [
42
- KafoConfigure.modules_dir,
43
- KafoConfigure.kafo_modules_dir,
44
- ].join(':')
43
+ @configuration.module_dirs,
44
+ @configuration.kafo_modules_dir,
45
+ ].flatten.join(':')
45
46
  end
46
47
  end
47
48
  end
@@ -9,16 +9,23 @@ module Kafo
9
9
  PRIMARY_GROUP_NAME = 'Parameters'
10
10
 
11
11
  attr_reader :name, :identifier, :params, :dir_name, :class_name, :manifest_name, :manifest_path,
12
- :groups, :params_path, :params_class_name
12
+ :groups, :params_path, :params_class_name, :configuration
13
13
 
14
- def initialize(identifier, parser = KafoParsers::PuppetModuleParser)
14
+ def initialize(identifier, parser = KafoParsers::PuppetModuleParser, configuration = KafoConfigure.config)
15
15
  @identifier = identifier
16
+ @configuration = configuration
16
17
  @name = get_name
17
18
  @dir_name = get_dir_name
18
19
  @manifest_name = get_manifest_name
19
20
  @class_name = get_class_name
20
21
  @params = []
21
- @manifest_path = File.join(KafoConfigure.modules_dir, module_manifest_path)
22
+ if @configuration.module_dirs.count == 1
23
+ module_dir = @configuration.module_dirs.first
24
+ else
25
+ module_dir = @configuration.module_dirs.find { |dir| File.exists?(File.join(dir, module_manifest_path)) } ||
26
+ warn("Manifest #{module_manifest_path} was not found in #{@configuration.module_dirs.join(', ')}")
27
+ end
28
+ @manifest_path = File.join(module_dir, module_manifest_path)
22
29
  @parser = parser
23
30
  @validations = []
24
31
  @logger = KafoConfigure.logger
@@ -28,7 +35,7 @@ module Kafo
28
35
  end
29
36
 
30
37
  def enabled?
31
- @enabled.nil? ? @enabled = KafoConfigure.config.module_enabled?(self) : @enabled
38
+ @enabled.nil? ? @enabled = @configuration.module_enabled?(self) : @enabled
32
39
  end
33
40
 
34
41
  def disable
@@ -78,7 +85,7 @@ module Kafo
78
85
  end
79
86
 
80
87
  def <=> o
81
- KafoConfigure.config.app[:low_priority_modules].each do |module_name|
88
+ @configuration.app[:low_priority_modules].each do |module_name|
82
89
  return 1 if self.name.include?(module_name) && !o.name.include?(module_name)
83
90
  return -1 if !self.name.include?(module_name) && o.name.include?(module_name)
84
91
  if self.name.include?(module_name) && o.name.include?(module_name)
@@ -103,7 +110,7 @@ module Kafo
103
110
 
104
111
  # mapping from configuration with stringified keys
105
112
  def mapping
106
- @mapping ||= Hash[KafoConfigure.config.app[:mapping].map { |k, v| [k.to_s, v] }]
113
+ @mapping ||= Hash[@configuration.app[:mapping].map { |k, v| [k.to_s, v] }]
107
114
  end
108
115
 
109
116
  # custom module directory name
@@ -0,0 +1,221 @@
1
+ # encoding: UTF-8
2
+ require 'kafo_wizards'
3
+
4
+ module Kafo
5
+ class ScenarioManager
6
+ attr_reader :config_dir, :last_scenario_link, :previous_scenario
7
+
8
+ def initialize(config, last_scenario_link_name='last_scenario.yaml')
9
+ @config_dir = File.file?(config) ? File.dirname(config) : config
10
+ @last_scenario_link = File.join(config_dir, last_scenario_link_name)
11
+ @previous_scenario = File.realpath(last_scenario_link) if File.exists?(last_scenario_link)
12
+ end
13
+
14
+ def available_scenarios
15
+ # assume that *.yaml file in config_dir that has key :name is scenario definition
16
+ @available_scenarios ||= Dir.glob(File.join(config_dir, '*.yaml')).reject { |f| f =~ /#{last_scenario_link}$/ }.inject({}) do |scns, scn_file|
17
+ begin
18
+ content = YAML.load_file(scn_file)
19
+ if content.is_a?(Hash) && content.has_key?(:answer_file)
20
+ # add scenario name for legacy configs
21
+ content[:name] = File.basename(scn_file, '.yaml') unless content.has_key?(:name)
22
+ scns[scn_file] = content
23
+ end
24
+ rescue Psych::SyntaxError => e
25
+ warn "Warning: #{e}"
26
+ end
27
+ scns
28
+ end
29
+ end
30
+
31
+ def list_available_scenarios
32
+ say ::HighLine.color("Available scenarios", :info)
33
+ available_scenarios.each do |config_file, content|
34
+ scenario = File.basename(config_file, '.yaml')
35
+ use = (File.expand_path(config_file) == @previous_scenario ? 'INSTALLED' : "use: --scenario #{scenario}")
36
+ say ::HighLine.color(" #{content[:name]} ", :title)
37
+ say "(#{use})"
38
+ say " " + content[:description] if !content[:description].nil? && !content[:description].empty?
39
+ end
40
+ say " No available scenarios found in #{config_dir}" if available_scenarios.empty?
41
+ KafoConfigure.exit(0)
42
+ end
43
+
44
+ def scenario_selection_wizard
45
+ wizard = KafoWizards.wizard(:cli, 'Select installation scenario',
46
+ :description => "Please select one of the pre-set installation scenarios. You can customize your setup later during the installation.")
47
+ f = wizard.factory
48
+ available_scenarios.keys.each do |scn|
49
+ label = available_scenarios[scn][:name].to_s
50
+ label += ": #{available_scenarios[scn][:description]}" if available_scenarios[scn][:description]
51
+ wizard.entries << f.button(scn, :label => label, :default => true)
52
+ end
53
+ wizard.entries << f.button(:cancel, :label => 'Cancel Installation', :default => false)
54
+ wizard
55
+ end
56
+
57
+ def select_scenario_interactively
58
+ # let the user select if in interactive mode
59
+ if (ARGV & ['--interactive', '-i']).any?
60
+ res = scenario_selection_wizard.run
61
+ if res == :cancel
62
+ say 'Installation was cancelled by user'
63
+ KafoConfigure.exit(0)
64
+ end
65
+ res
66
+ end
67
+ end
68
+
69
+ def scenario_changed?(scenario)
70
+ scenario = File.realpath(scenario) if File.symlink?(scenario)
71
+ !!previous_scenario && scenario != previous_scenario
72
+ end
73
+
74
+ def configured?
75
+ !!(defined?(CONFIG_DIR) && CONFIG_DIR)
76
+ end
77
+
78
+ def scenario_from_args
79
+ # try scenario provided in the args via -S or --scenario
80
+ parsed = ARGV.join(" ").match /(--scenario|-S)(\s+|[=]?)(\S+)/
81
+ if parsed
82
+ scenario_file = File.join(config_dir, "#{parsed[3]}.yaml")
83
+ return scenario_file if File.exists?(scenario_file)
84
+ KafoConfigure.logger.fatal "Scenario (#{scenario_file}) was not found, can not continue"
85
+ KafoConfigure.exit(:unknown_scenario)
86
+ end
87
+ end
88
+
89
+ def select_scenario
90
+ scenario = scenario_from_args || previous_scenario ||
91
+ (available_scenarios.keys.count == 1 && available_scenarios.keys.first) ||
92
+ select_scenario_interactively
93
+ if scenario.nil?
94
+ fail_now("Scenario was not selected, can not continue. Use --list-scenarios to list available options.", :unknown_scenario)
95
+ end
96
+ scenario
97
+ end
98
+
99
+ def scenario_from_args
100
+ # try scenario provided in the args via -S or --scenario
101
+ parsed = ARGV.join(" ").match /(--scenario|-S)(\s+|[=]?)(\S+)/
102
+ if parsed
103
+ scenario_file = File.join(config_dir, "#{parsed[3]}.yaml")
104
+ return scenario_file if File.exists?(scenario_file)
105
+ fail_now("Scenario (#{scenario_file}) was not found, can not continue", :unknown_scenario)
106
+ end
107
+ end
108
+
109
+ def show_scenario_diff(prev_scenario, new_scenario)
110
+ say ::HighLine.color("Scenarios are being compared, that may take a while...", :info)
111
+ prev_conf = load_and_setup_configuration(prev_scenario)
112
+ new_conf = load_and_setup_configuration(new_scenario)
113
+ print_scenario_diff(prev_conf, new_conf)
114
+ end
115
+
116
+ def check_scenario_change(scenario)
117
+ if scenario_changed?(scenario)
118
+ if ARGV.include? '--compare-scenarios'
119
+ show_scenario_diff(@previous_scenario, scenario)
120
+ dump_log_and_exit(0)
121
+ else
122
+ confirm_scenario_change(scenario)
123
+ KafoConfigure.logger.info "Scenario #{scenario} was selected"
124
+ end
125
+ end
126
+ end
127
+
128
+ def confirm_scenario_change(new_scenario)
129
+ unless ARGV.include?('--force')
130
+ if (ARGV & ['--interactive', '-i']).any?
131
+ show_scenario_diff(@previous_scenario, new_scenario)
132
+
133
+ wizard = KafoWizards.wizard(:cli, 'Confirm installation scenario selection',
134
+ :description => "You are trying to replace existing installation with different scenario. This may lead to unpredictable states. Please confirm that you want to proceed.")
135
+ wizard.entries << wizard.factory.button(:proceed, :label => 'Proceed with selected installation scenario', :default => false)
136
+ wizard.entries << wizard.factory.button(:cancel, :label => 'Cancel Installation', :default => true)
137
+ result = wizard.run
138
+ if result == :cancel
139
+ say 'Installation was cancelled by user'
140
+ dump_log_and_exit(0)
141
+ end
142
+ else
143
+ message = "You are trying to replace existing installation with different scenario. This may lead to unpredictable states. " +
144
+ "Use --force to override. You can use --compare-scenarios to see the differences"
145
+ KafoConfigure.logger.error(message)
146
+ dump_log_and_exit(:scenario_error)
147
+ end
148
+ end
149
+ end
150
+
151
+ def print_scenario_diff(prev_conf, new_conf)
152
+ missing = new_conf.params_missing(prev_conf)
153
+ changed = new_conf.params_changed(prev_conf)
154
+
155
+ say "\n" + ::HighLine.color("Overview of modules used in the scenarios (#{prev_conf.app[:name]} -> #{new_conf.app[:name]}):", :title)
156
+ modules = Hash.new { |h, k| h[k] = {} }
157
+ modules = prev_conf.modules.inject(modules) { |mods, mod| mods[mod.name][:prev] = mod.enabled?; mods }
158
+ modules = new_conf.modules.inject(modules) { |mods, mod| mods[mod.name][:new] = mod.enabled?; mods }
159
+ printables = { "" => 'N/A', 'true' => 'ENABLED', 'false' => 'DISABLED' }
160
+ modules.each do |mod, status|
161
+ module_line = "%-50s: %-09s -> %s" % [mod, printables[status[:prev].to_s], printables[status[:new].to_s]]
162
+ # highlight modules that will be disabled
163
+ module_line = ::HighLine.color(module_line, :important) if status[:prev] == true && (status[:new] == false || status[:new].nil?)
164
+ say module_line
165
+ end
166
+
167
+ say "\n" + ::HighLine.color("Defaults that will be updated with values from previous installation:", :title)
168
+ if changed.empty?
169
+ say " No values will be updated from previous scenario"
170
+ else
171
+ changed.each { |param| say " #{param.module.class_name}::#{param.name}: #{param.value} -> #{prev_conf.param(param.module.class_name, param.name).value}" }
172
+ end
173
+ say "\n" + ::HighLine.color("Values from previous installation that will be lost by scenario change:", :title)
174
+ if missing.empty?
175
+ say " No values from previous installation will be lost"
176
+ else
177
+ missing.each { |param| say " #{param.module.class_name}::#{param.name}: #{param.value}\n" }
178
+ end
179
+ end
180
+
181
+ def link_last_scenario(config_file)
182
+ link_path = last_scenario_link
183
+ if last_scenario_link
184
+ File.delete(last_scenario_link) if File.exist?(last_scenario_link)
185
+ File.symlink(config_file, last_scenario_link)
186
+ end
187
+ end
188
+
189
+ def load_and_setup_configuration(config_file)
190
+ conf = load_configuration(config_file)
191
+ conf.preset_defaults_from_puppet
192
+ conf.preset_defaults_from_yaml
193
+ conf
194
+ end
195
+
196
+ def load_configuration(config_file)
197
+ Configuration.new(config_file)
198
+ end
199
+
200
+ private
201
+
202
+ def fail_now(message, exit_code)
203
+ say "ERROR: #{message}"
204
+ KafoConfigure.logger.error message
205
+ KafoConfigure.exit(exit_code)
206
+ end
207
+
208
+ def dump_log_and_exit(code)
209
+ if Logger.buffering? && Logger.buffer.any?
210
+ Logger.setup_verbose
211
+ KafoConfigure.verbose = true
212
+ if !KafoConfigure.config.nil?
213
+ Logger.setup
214
+ KafoConfigure.logger.info("Log was be written to #{KafoConfigure.config.log_file}")
215
+ end
216
+ KafoConfigure.logger.info('Logs flushed')
217
+ end
218
+ KafoConfigure.exit(code)
219
+ end
220
+ end
221
+ end