Class: Cheftacular::Helper

Inherits:
Object
  • Object
show all
Defined in:
lib/cheftacular/helper.rb

Instance Method Summary collapse

Constructor Details

#initialize(options, config) ⇒ Helper

Returns a new instance of Helper.



4
5
6
# File 'lib/cheftacular/helper.rb', line 4

def initialize options, config
  @options, @config  = options, config
end

Instance Method Details

#check_if_possible_repo_state(repo_state_hash, output_mode = 'silent', git_output = '') ⇒ Object



296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
# File 'lib/cheftacular/helper.rb', line 296

def check_if_possible_repo_state repo_state_hash, output_mode='silent', git_output=''
  revision_to_check = repo_state_hash.has_key?('revision')            ? repo_state_hash['revision']            : nil
  org_name_to_check = repo_state_hash.has_key?('deploy_organization') ? repo_state_hash['deploy_organization'] : @config['cheftacular']['TheCheftacularCookbook']['organization_name']
 
  revision_to_check = nil if revision_to_check == '<use_default>'
  org_name_to_check = @config['cheftacular']['TheCheftacularCookbook']['organization_name'] if org_name_to_check.nil?

  @config['cheftacular']['TheCheftacularCookbook']['chef_environment_to_app_repo_branch_mappings'].each_pair do |chef_env, app_env|
    revision_to_check = app_env if @options['env'] == chef_env && revision_to_check.nil?
  end

  git_output = `git ls-remote --heads [email protected]:#{ org_name_to_check }/#{ @options['repository'] }.git`

  if git_output.blank?
    puts "! The remote organization #{ org_name_to_check } does not have the repository: #{ @options['repository'] }! Please verify your repositories and try again"

    exit
  elsif !git_output.include?(revision_to_check) && output_mode == 'display_for_check'
    puts "WARNING! The commit #{ repo_state_hash['revision'] } is NOT the latest for the branch #{ repo_state_hash['branch'] } in organization #{ repo_state_hash['deploy_organization'] }!"

    puts "Please re-run your deploy again and if this continues to not work, check with your DevOps personel!"

    @options['skip_further_deploy_steps'] = true
  elsif !git_output.include?(revision_to_check)
    puts "! The remote organization #{ org_name_to_check } does not have a revision / branch #{ revision_to_check } for repository: #{ @options['repository'] } !"

    sorted_heads = git_output.scan(/refs\/heads\/(.*)/).flatten.uniq.sort_by { |head| compare_strings(revision_to_check, head)}

    puts "The closest matches for #{ revision_to_check } are:"
    puts "    #{ sorted_heads.at(0) }"
    puts "    #{ sorted_heads.at(1) }"
    puts "    #{ sorted_heads.at(2) }"
    puts "    #{ sorted_heads.at(3) }\n"

    puts "Please verify the correct revision / branch and run this command again."
    
    exit
  elsif git_output.include?(revision_to_check) && output_mode == 'display_for_check'
    puts "\nSuccessfully verified that the commit #{ repo_state_hash['revision'] } is the latest for the branch #{ repo_state_hash['branch'] } in organization #{ repo_state_hash['deploy_organization'] }.\n"
  end
end

#compare_strings(str1, str2) ⇒ Object

compares how close str1 is to str2



159
160
161
162
163
164
# File 'lib/cheftacular/helper.rb', line 159

def compare_strings str1, str2
  str1_chars = str1.split('').uniq
  str2_chars = str2.split('').uniq

  ((str1_chars + str2_chars).uniq.length * 1.0) / (str1_chars.length + str2_chars.length)
end

#compile_chef_repo_cheftacular_yml_as_hashObject



240
241
242
243
244
245
246
247
# File 'lib/cheftacular/helper.rb', line 240

def compile_chef_repo_cheftacular_yml_as_hash
  master_hash = get_cheftacular_yml_as_hash
  master_hash['replace_keys_in_chef_repo'].each_pair do |key, val|
    master_hash[key] = val
  end

  master_hash
end

#compile_documentation_lines(mode, out = []) ⇒ Object

the documentation hashes must be populated before this method runs for it to return anything!



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/cheftacular/helper.rb', line 130

def compile_documentation_lines mode, out=[]
  doc_arr = case mode
            when 'action'           then @config['documentation']['action']
            when 'application'      then @config['documentation']['application'].merge(@config['documentation']['action'])
            when 'stateless_action' then @config['documentation']['stateless_action']
            when 'devops'           then @config['documentation']['stateless_action'].merge(@config['documentation']['action'])
            end

  doc_arr = doc_arr.to_a.map { |doc| doc[1]['long_description'] }
  count   = 1

  doc_arr.sort {|a, b| a[0] <=> b[0]}.flatten(1).each do |line|
    out << "#{ count }. #{ line }" if line.class.to_s == 'String'

    out << line if line.class.to_s == 'Array'

    count += 1 if line.class.to_s == 'String'
  end

  out
end

#compile_short_context_descriptions(documentation_hash, padding_length = 25, out = []) ⇒ Object



152
153
154
155
156
# File 'lib/cheftacular/helper.rb', line 152

def compile_short_context_descriptions documentation_hash, padding_length=25, out=[]
  out << documentation_hash.to_a.map { |doc| "#{ doc[0].to_s.ljust(padding_length, '_') }_#{ doc[1]['short_description'] }" }

  out.flatten.sort {|a, b| a[0] <=> b[0]}.join("\n\n")
end

#completion_rate?(percent, mode) ⇒ Boolean

Returns:

  • (Boolean)


54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/cheftacular/helper.rb', line 54

def completion_rate? percent, mode
  case mode.to_s
  when 'initializer'           then print("Fetching initialization chef data for #{ @options['env'] }....0%") if !@options['quiet'] && percent == 0
  when 'get_true_node_objects' then print("Retrieving node data from chef server for #{ @config['chef_nodes'].count } nodes....0%") if !@options['quiet'] && percent == 0
  end

  case percent
  when 1..9   then print("\b\b#{ percent.to_i }%")       unless @options['quiet']
  when 10..99 then print("\b\b\b#{ percent.to_i }%")     unless @options['quiet']
  when 100    then print("\b\b\b\b#{ percent.to_i }%\n") unless @options['quiet']
  end
end

#declassifyObject



8
9
10
11
# File 'lib/cheftacular/helper.rb', line 8

def declassify
  #(self.class::TRUENAME.constantize).to_s.underscore.dasherize
  Cheftacular.to_s.underscore.dasherize
end

#display_cheftacular_config_diffObject



338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
# File 'lib/cheftacular/helper.rb', line 338

def display_cheftacular_config_diff
  diff_hash = @config['initial_cheftacular_yml'].deep_diff(@config['default']['cheftacular_bag_hash'], true).except('mode', 'default_repository', 'default_environment').compact

  recursive_hash_scrub(diff_hash)

  recursive_hash_scrub(diff_hash) unless diff_hash.empty? #scrub out any leftover empty hashes

  if diff_hash.empty?
    puts "No difference detected between your cheftacular.yml and the global environment."
  else
    puts "Difference detected between local cheftacular.yml and data bag cheftacular.yml! Displaying...\n\n"

    ap diff_hash
      
    if @config['helper'].running_in_mode?('application') && @config['default']['cheftacular_bag_hash']['slack']['webhook'] && !diff_hash.empty?
      @config['slack_queue'] << { message: diff_hash.awesome_inspect({plain: true, indent: 2}).prepend('```').insert(-1, '```'), channel: @config['cheftacular']['slack']['notify_on_yaml_sync'] }
    end

    puts("If these are your intended changes you want to sync into the environment, you should run `cft cheftacular_config sync`\n\n") if ARGV[0] != 'cheftacular_config' && ARGV[1] != 'sync'
  end
end

#display_currently_installed_versionObject



276
277
278
# File 'lib/cheftacular/helper.rb', line 276

def display_currently_installed_version
  puts "The current version of cheftacular is #{ Cheftacular::VERSION }"
end

#display_readme(option = "", out = "") ⇒ Object



67
68
69
# File 'lib/cheftacular/helper.rb', line 67

def display_readme option="", out=""
  puts File.read(File.expand_path('../README.md', __FILE__))
end

#does_cheftacular_config_have?(key_array) ⇒ Boolean

Returns:

  • (Boolean)


174
175
176
177
178
179
180
181
182
183
184
# File 'lib/cheftacular/helper.rb', line 174

def does_cheftacular_config_have? key_array
  cheftacular = @config['cheftacular']
  key_array   = [key_array] if key_array.is_a?(String)
  key_checks  = []

  key_array.each do |key|
    key_checks << recursive_hash_check(key.split(':'), @config['cheftacular']).to_s
  end

  !key_checks.include?('false')
end

#fetch_remote_versionObject

TODO, fix for clients that block amazon hosted rubygems?



48
49
50
51
52
# File 'lib/cheftacular/helper.rb', line 48

def fetch_remote_version
  puts "Checking remote #{ declassify } version..."

  `gem list #{ declassify } --remote`[/([\d\.]+)/]
end

#gen_pass(length = 20, mode = "truepass") ⇒ Object



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/cheftacular/helper.rb', line 71

def gen_pass length=20, mode="truepass"
  lowercase = 'a'..'z'
  uppercase = 'A'..'Z'
  numbers   = 0..9

  length = @config['cheftacular']['server_pass_length'] if length.to_i <= @config['cheftacular']['server_pass_length']

  sets = case mode
         when "truepass"  then [lowercase, uppercase, numbers]
         when "numsonly"  then [numbers]
         when "lowernum"  then [lowercase, numbers]
         when "uppernum"  then [uppercase, numbers]
         when "lowercase" then [lowercase]
         when "uppercase" then [uppercase]
         end

  o = sets.flatten.map { |i| i.to_a }.flatten

  (0...length.to_i).map { o[rand(o.length)] }.join
end

#get_cheftacular_yml_as_hash(filename = 'cheftacular.yml') ⇒ Object

this must be in helpers because getter class is not yet loaded at the time this method is needed.



223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/cheftacular/helper.rb', line 223

def get_cheftacular_yml_as_hash filename='cheftacular.yml'
  config_location = if File.exist?(File.join( Dir.getwd, 'config', filename ))
                      File.join( Dir.getwd, 'config', filename )
                    elsif File.exist?("/root/#{ filename }")
                      "/root/#{ filename }"
                    else
                      raise "cheftacular.yml configuration file could not be found in either #{ File.join( Dir.getwd, 'config', filename ) } or /root/#{ filename }"
                    end

  YAML::load(ERB.new(IO.read(File.open(config_location))).result)
rescue StandardError => e
  puts "The cheftacular.yml configuration file could not be parsed."
  puts "Error message: #{ e }\n#{ e.backtrace.join("\n") }"
  
  exit
end

#get_cheftacular_yml_override(filename = 'override.cheftacular.yml', ret_hash = {}) ⇒ Object



216
217
218
219
220
# File 'lib/cheftacular/helper.rb', line 216

def get_cheftacular_yml_override filename='override.cheftacular.yml', ret_hash={}
  ret_hash = get_cheftacular_yml_as_hash(filename) if File.exist?(File.join( Dir.getwd, 'config', filename ))

  ret_hash
end

#is_command?(command = '') ⇒ Boolean

Returns:

  • (Boolean)


13
14
15
16
17
# File 'lib/cheftacular/helper.rb', line 13

def is_command? command=''
  command ||= ''

  @config['action'].public_methods(false).include?(command.to_sym)
end

#is_higher_version?(vstr1, vstr2) ⇒ Boolean

Returns:

  • (Boolean)


121
122
123
# File 'lib/cheftacular/helper.rb', line 121

def is_higher_version? vstr1, vstr2
  Gem::Version.new(vstr1) > Gem::Version.new(vstr2)
end

#is_initialization_command?(command = '') ⇒ Boolean

Returns:

  • (Boolean)


31
32
33
34
35
# File 'lib/cheftacular/helper.rb', line 31

def is_initialization_command? command=''
  command ||= ''

  @config['initialization_action'].public_methods(false).include?(command.to_sym) || command.blank?
end

#is_not_command_or_stateless_command?(command = '') ⇒ Boolean

Returns:

  • (Boolean)


25
26
27
28
29
# File 'lib/cheftacular/helper.rb', line 25

def is_not_command_or_stateless_command? command=''
  command ||= ''

  !@config['action'].public_methods(false).include?(command.to_sym) && !@config['stateless_action'].public_methods(false).include?(command.to_sym)
end

#is_stateless_command?(command = '') ⇒ Boolean

Returns:

  • (Boolean)


19
20
21
22
23
# File 'lib/cheftacular/helper.rb', line 19

def is_stateless_command? command=''
  command ||= ''
  
  @config['stateless_action'].public_methods(false).include?(command.to_sym)
end

#output_run_statsObject



104
105
106
# File 'lib/cheftacular/helper.rb', line 104

def output_run_stats
  puts("\nDone in #{ Time.now - @config['start_time'] } seconds at #{ Time.now.strftime('%Y-%m-%d %l:%M:%S %P') }.") unless @options['quiet']
end

#parse_node_name_from_client_file(ret = "") ⇒ Object

this must be in helpers because parser class is not yet loaded at the time this method is needed.



206
207
208
209
210
211
212
213
214
# File 'lib/cheftacular/helper.rb', line 206

def parse_node_name_from_client_file ret=""
  config = File.read(File.expand_path("#{ @config['locs']['chef'] }/client.rb"))

  config.split("\n").each do |line|
    next unless line.include?('node_name')

    return line.split('node_name').last.strip.chomp.gsub('"', '')
  end
end

#recursive_hash_check(keys, hash) ⇒ Object



186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/cheftacular/helper.rb', line 186

def recursive_hash_check keys, hash
  if hash.has_key?(keys[0]) 
    case hash[keys[0]].class.to_s
    when 'Hash'
      if !hash[keys[0]].empty?
        recursive_hash_check keys[1..keys.count-1], hash[keys[0]] 
      else
        return true
      end
    when 'String'
      return !hash[keys[0]].blank?
    when 'Array'
      return !hash[keys[0]].empty?
    end
  else
    return false
  end
end

#recursive_hash_scrub(hash) ⇒ Object



360
361
362
363
364
365
366
367
368
369
370
371
372
# File 'lib/cheftacular/helper.rb', line 360

def recursive_hash_scrub hash
  hash.each_pair do |key, value|
    if value.nil?
      hash.delete(key)
    elsif value.class == Hash && value.empty?
      hash.delete(key)
    elsif value.class == Hash && value[value.keys.first].empty?
      hash.delete(key)
    elsif value.class == Hash
      recursive_hash_scrub(hash[key])
    end
  end
end

#return_options_as_hash(options_array, return_hash = {}) ⇒ Object



288
289
290
291
292
293
294
# File 'lib/cheftacular/helper.rb', line 288

def return_options_as_hash options_array, return_hash={}
  options_array.each do |key|
    return_hash[key] = @options[key]
  end

  return_hash
end

#running_in_mode?(mode) ⇒ Boolean

Returns:

  • (Boolean)


43
44
45
# File 'lib/cheftacular/helper.rb', line 43

def running_in_mode? mode
  @config['cheftacular']['mode'] == mode
end

#running_on_chef_node?(ret = false) ⇒ Boolean

Returns:

  • (Boolean)


37
38
39
40
41
# File 'lib/cheftacular/helper.rb', line 37

def running_on_chef_node? ret = false
  Dir.entries('/etc').include?('chef') && File.exist?('/etc/chef/client.rb') && !File.size?('/etc/chef/client.rb').nil?
rescue StandardError => e
  @config['error'].exception_output "An error occurred while trying to see if this system is a chef node. Assuming the system is not a chef node.", e, false
end

#send_log_bag_hash_slack_notification(logs_bag_hash, method, on_failing_exit_status_message = '') ⇒ Object



249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/cheftacular/helper.rb', line 249

def send_log_bag_hash_slack_notification logs_bag_hash, method, on_failing_exit_status_message=''
  if @config['cheftacular']['slack']['webhook']
    logs_bag_hash.each_pair do |key, hash|
      next unless key.include?(method.to_s)

      if hash['exit_status'] && hash['exit_status'] == 1
        @config['slack_queue'] << { message: hash['text'].prepend('```').insert(-1, '```') }

        if !on_failing_exit_status_message.blank?
          @config['queue_master'].work_off_slack_queue

          @config['error'].exception_output(on_failing_exit_status_message)
        end
      end
    end
  end
end

#set_cloud_optionsObject



166
167
168
169
170
171
172
# File 'lib/cheftacular/helper.rb', line 166

def set_cloud_options
  @options['preferred_cloud']        = @options['preferred_cloud'].nil? ?        @config['cheftacular']['preferred_cloud'].downcase        : @options['preferred_cloud'].downcase
  @options['preferred_cloud_image']  = @options['preferred_cloud_image'].nil? ?  @config['cheftacular']['preferred_cloud_image']           : @options['preferred_cloud_image']
  @options['preferred_cloud_region'] = @options['preferred_cloud_region'].nil? ? @config['cheftacular']['preferred_cloud_region']          : @options['preferred_cloud_region']
  @options['virtualization_mode']    = @options['virtualization_mode'].nil? ?    @config['cheftacular']['virtualization_mode']             : @options['virtualization_mode']
  @options['route_dns_changes_via']  = @options['route_dns_changes_via'].nil? ?  @config['cheftacular']['route_dns_changes_via'].downcase  : @options['route_dns_changes_via'].downcase
end

#set_detected_cheftacular_versionObject



280
281
282
283
284
285
286
# File 'lib/cheftacular/helper.rb', line 280

def set_detected_cheftacular_version
  @config['detected_cheftacular_version'] ||= if File.exists?( @config['filesystem'].current_version_file_path )
                                                File.read( @config['filesystem'].current_version_file_path )
                                              else 
                                                @config['helper'].fetch_remote_version
                                              end
end

#set_local_instance_varsObject



108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/cheftacular/helper.rb', line 108

def set_local_instance_vars
  [ 
    @options, 
    @config['locs'],
    @config['ridley'],
    @config[@options['env']]['logs_bag_hash'],
    @config[@options['env']]['chef_passwords_bag_hash'],
    @config['bundle_command'],
    @config['cheftacular'],
    @config['server_passwords']
  ]
end

#set_location_if_app(ret = "") ⇒ Object



92
93
94
95
96
97
98
# File 'lib/cheftacular/helper.rb', line 92

def set_location_if_app ret=""
  if get_codebase_from_role_name(Dir.getwd.split('/').last, "has_key?")
    ret = Dir.getwd.split('/').last
  end

  ret
end

#set_log_loc_and_timestampObject



125
126
127
# File 'lib/cheftacular/helper.rb', line 125

def set_log_loc_and_timestamp
  @config['dummy_sshkit'].set_log_loc_and_timestamp @config['locs']
end

#slack_current_deploy_argumentsObject



267
268
269
270
271
272
273
274
# File 'lib/cheftacular/helper.rb', line 267

def slack_current_deploy_arguments
  msg  = "#{ Socket.gethostname }(#{ @config['locs']['root'] }) just set for the repository #{ @config['getter'].get_repository_from_role_name(@options['role']) }:\n"
  msg << "the organization to #{ @options['deploy_organization'] }\n" if @options['deploy_organization']
  msg << "the revision to #{ @options['target_revision'] }\n"         if @options['target_revision']
  msg << "In the environment: #{ @options['env'] }"
  
  @config['slack_queue'] << { message: msg.prepend('```').insert(-1, '```'), channel: @config['cheftacular']['slack']['notify_on_deployment_args'] }
end

#sudo(ip_address) ⇒ Object



100
101
102
# File 'lib/cheftacular/helper.rb', line 100

def sudo ip_address
  "echo #{ @config['server_passwords'][ip_address] } | sudo -S"
end

#unset_repository_if_role_has_no_repositoryObject

at this point, we should of already verified that the role exists so the repository key is useless data



374
375
376
# File 'lib/cheftacular/helper.rb', line 374

def unset_repository_if_role_has_no_repository #at this point, we should of already verified that the role exists so the repository key is useless data
  @options.delete('repository') if @config['getter'].get_repository_from_role_name(@options['role'], 'do_not_raise_on_unknown').nil?
end