kafo 0.6.12 → 0.7.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.
@@ -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