gaddygaddy 0.1.78

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.
@@ -0,0 +1,396 @@
1
+ #
2
+ # Name:
3
+ # gaddygaddy-client.rb
4
+ #
5
+ # Created by: GaddyGaddy
6
+ #
7
+ # Description:
8
+ #
9
+ #
10
+ #
11
+ # Copyright (c) 2013 GaddyGaddy
12
+ #
13
+ # All rights reserved.
14
+ #
15
+
16
+ APPLICATION_NAME = "gaddygaddy-client"
17
+ CHEF_CMD = '/usr/local/bin/chef-solo'
18
+
19
+ require 'gg_config/gg_config'
20
+ require 'device_info/device_info'
21
+ require 'fileutils'
22
+ require 'utils/hash_monkeypatch'
23
+ require 'json'
24
+ require 'subcommand'
25
+ require 'gaddygaddy-client/chef_files'
26
+ require 'gaddygaddy-client/notification'
27
+ require 'gaddygaddy-client/log_data'
28
+ require 'logging/logging'
29
+ require 'restclient'
30
+ require 'systemu'
31
+ require 'utils/request'
32
+ require 'utils/retriable'
33
+ require 'utils/run'
34
+ require 'error_constants'
35
+ require_relative 'gaddygaddy-client/espeak'
36
+
37
+ REVISION = "$Revision: 1 $"[10..-3].chomp
38
+
39
+ DEFAULT_CHEF_DIR = "/var/chef"
40
+ DEFAULT_CONF_DIR = "/conf"
41
+ DEFAULT_CONF_FILE = "gaddy_*.gcf"
42
+ CONFIG_FILE_NAME = "node.json"
43
+
44
+ class TokenJException < JException;end
45
+ class CreateDeviceJException < JException;end
46
+
47
+ class GaddyGaddy_Client
48
+
49
+ include Logging
50
+ include Subcommands
51
+ include Retriable
52
+ extend Retriable
53
+
54
+ def initialize
55
+ logger.outputters = Log4r::Outputter.stdout
56
+ end
57
+
58
+ def usage
59
+ puts "gaddygaddy-client [OPTIONS]"
60
+ puts "The client to run for the GaddyGaddy service"
61
+ puts "For more information about the commands run gaddygaddy-client --help"
62
+ puts "Revision :#{REVISION}"
63
+ puts
64
+ end
65
+
66
+ def read_options
67
+ # This hash will hold all of the options
68
+ # parsed from the command-line by
69
+ # OptionParser.
70
+ @options = {}
71
+
72
+
73
+ global_options do |opts|
74
+ opts.banner = "Usage: gaddygaddy-client [options] [subcommand [options]]"
75
+ opts.description = "GaddyGaddy client for the GaddyGaddy service"
76
+ opts.separator ""
77
+ opts.separator "Global options are:"
78
+
79
+ @options[:conf_dir] = DEFAULT_CONF_DIR
80
+ opts.on( '--config_dir CONF-DIR', "Directory containing the client configuration files, default is #{DEFAULT_CONF_DIR}" ) do |conf_dir|
81
+ @options[:conf_dir] = conf_dir
82
+ end
83
+
84
+ @options[:conf_file] = DEFAULT_CONF_FILE
85
+ opts.on( '--config_file CONF-FILE', "File name (without) path for the configuration file, default is #{DEFAULT_CONF_FILE}" ) do |conf_file|
86
+ @options[:conf_file] = conf_file
87
+ end
88
+
89
+ @options[:log_file] = nil
90
+ opts.on( '-L', '--logfile FILE', 'Write log to FILE, defaults to STDOUT' ) do|file|
91
+ @options[:log_file] = file
92
+ end
93
+
94
+ @options[:file_host] = "config2.gaddygaddy.com"
95
+ opts.on( '--file_host FILE_HOST', 'The file host to get files like cookbooks from' ) do|file_host|
96
+ @options[:file_host] = file_host
97
+ end
98
+
99
+ @options[:host] = "https://www.gaddygaddy.com:8012"
100
+ opts.on( '-H', '--host HOST', 'The host to connect to' ) do|host|
101
+ @options[:host] = host
102
+ end
103
+
104
+ @options[:log_level] = 'info'
105
+ opts.on( '-l', '--log_level level', 'Set the log level (debug, info, warn, error, fatal)' ) do|level|
106
+ @options[:log_level] = level
107
+ end
108
+
109
+ @options[:test_mode] = false
110
+ opts.on( '--test_mode', 'Used for testing, will only retry once for example' ) do
111
+ @options[:test_mode] = true
112
+ end
113
+
114
+ @options[:token] = nil
115
+ opts.on( '-t', '--token TOKEN', 'The token to be used to access' ) do|token|
116
+ @options[:token] = token
117
+ end
118
+
119
+
120
+
121
+ end
122
+
123
+ add_help_option
124
+
125
+ command :get_cookbooks do |opts|
126
+ opts.banner = "Usage: get_cookbooks [options]"
127
+ opts.description = "Will download the cookbooks from the GaddyGaddy service"
128
+ @options[:chef_dir] = DEFAULT_CHEF_DIR
129
+ opts.on( '--chef_dir CHEF-DIR', "The chef dir to place the files into, default is #{DEFAULT_CHEF_DIR}" ) do |chef_dir|
130
+ @options[:chef_dir] = chef_dir
131
+ end
132
+ @options[:cookbooks_version] = nil
133
+ opts.on( '--cookbooks_version COOKBOOKS_VERSION', "Override the setting of which cookbook version to download" ) do |cookbooks_version|
134
+ @options[:cookbooks_version] = cookbooks_version
135
+ end
136
+ end
137
+
138
+ command :chef_config do |opts|
139
+ opts.banner = "Usage: chef_config [options]"
140
+ opts.description = "Will download the config and place it in a directory"
141
+ end
142
+
143
+ command :send_device_data do |opts|
144
+ opts.banner = "Usage: send_device_data [options]"
145
+ opts.description = "Will send information about the gaddy to gaddygaddy.com"
146
+ end
147
+
148
+ command :upload_log_file do |opts|
149
+ opts.banner = "Usage: verify_installation[options]"
150
+ opts.description = "Will verify the installation and check for gaddy file and network configuration"
151
+ @options[:lines] = 100
152
+ opts.on("-i", "--lines lines", "Number of lines to upload, count from end of file") do |lines|
153
+ @options[:lines] = lines
154
+ end
155
+ opts.on("-f", "--id function_id", "Function id") do |function_id|
156
+ @options[:function_id] = function_id
157
+ end
158
+ @options[:upload_log_file] = nil
159
+ opts.on("-u", "--upload_log_file upload_log_file", "Name of log file to upload, mandatory") do |upload_log_file|
160
+ @options[:upload_log_file] = upload_log_file
161
+ end
162
+ end
163
+
164
+ command :verify_installation do |opts|
165
+ opts.banner = "Usage: verify_installation[options]"
166
+ opts.description = "Will verify the installation and check for gaddy file and network configuration"
167
+ end
168
+
169
+ command :notify do |opts|
170
+ opts.banner = "Usage: verify_installation[options]"
171
+ opts.description = "Will verify the installation and check for gaddy file and network configuration"
172
+ @options[:event] = nil
173
+ opts.on("-e", "--event event", "Event to notify, should be json format") do |event|
174
+ @options[:event] = event
175
+ end
176
+ end
177
+
178
+ cmd = nil
179
+ begin
180
+ cmd = opt_parse
181
+ mandatory = []
182
+ missing = mandatory.select{ |param| @options[param].nil? }
183
+ unless missing.empty?
184
+ puts "Missing options: #{missing.join(', ')}" #
185
+ puts opt_parse #
186
+ exit 1
187
+ end
188
+ unless cmd
189
+ puts "No command is specified\n\n"
190
+ usage
191
+ puts print_actions
192
+ exit 1
193
+ end
194
+ rescue OptionParser::InvalidOption, OptionParser::MissingArgument #
195
+ puts $!.to_s # Friendly output when parsing fails
196
+ puts opt_parse #
197
+ exit 1 #
198
+ end
199
+ cmd
200
+ end
201
+
202
+ def gg_config
203
+ @gg_config ||= GGConfig.new(:conf_dir => @options[:conf_dir], :conf_file => @options[:conf_file])
204
+ end
205
+
206
+ # Get the config directory
207
+ def conf_dir
208
+ @options[:conf_dir]
209
+ end
210
+
211
+ # Should try to get the host from options or config
212
+
213
+ def get_host
214
+ host = @options[:host]
215
+ # This is a temporary fix to handle the move to Amazon
216
+ if host == 'ap.gaddygaddy.com:86'
217
+ host = 'ap-pre.gaddygaddy.com:86'
218
+ end
219
+ host
220
+ end
221
+
222
+
223
+ def get_chef_config_file
224
+ response = Request.client_service_get get_host, "/chef/node_json/1/#{gg_config.user_id_salt}/#{gg_config.device_id}/#{gg_config.token}"
225
+ raise "Could not get config file for device #{gg_config.device_id}" unless response["run_list"].length > 0
226
+ logger.debug "Got config file: #{response}"
227
+ response
228
+ end
229
+
230
+ def get_chef_config
231
+ begin
232
+ FileUtils.mkdir_p conf_dir
233
+ rescue Exception => e
234
+ raise e.inspect
235
+ end
236
+ conf_content = get_chef_config_file
237
+ chef_config = conf_content['variables']
238
+ chef_config['run_list'] = conf_content['run_list']
239
+
240
+ conf_file = File.open File.join(conf_dir, CONFIG_FILE_NAME), "w"
241
+ conf_file.write chef_config.to_json
242
+ conf_file.close
243
+ end
244
+
245
+ # Check that the config file exist and has valid content, this will also be validated toward the server
246
+ def config_valid
247
+ config_ok = false
248
+ config_file_name = Dir.glob(File.join(conf_dir, @options[:conf_file]))[0]
249
+ logger.debug "Will validate config for #{config_file_name}"
250
+ if File.exists?(config_file_name)
251
+ begin
252
+ conf_file = File.open(config_file_name)
253
+ config = JSON.parse(conf_file.read)
254
+ rescue Exception => e
255
+ raise "Could not read from file #{config_file_name}"
256
+ end
257
+ logger.debug "Have read from file #{config_file_name}"
258
+ config_ok = config["device_id"]
259
+ end
260
+ # TODO validate the client against the client service
261
+ config_ok
262
+ end
263
+
264
+ # Get a device id, to start with only implemented for Raspberry
265
+ def hardware_id
266
+ hardware_id = `cat /proc/cpuinfo|grep Serial`.strip[10..-1].to_s.strip
267
+ logger.debug "The device id is #{hardware_id}"
268
+ if hardware_id == ""
269
+ hardware_id = 'test_device'
270
+ end
271
+ raise JNoDeviceIDFound.new({:message => "Could not found a device id for this computer, a device id is needed, see further help"}) unless hardware_id
272
+ hardware_id
273
+ end
274
+
275
+ # Will send ohai device data to gaddygaddy
276
+ def send_device_data
277
+ @device_info.post
278
+ end
279
+
280
+ def upload_log_file
281
+ raise "Missing option upload_log_file" unless @options[:upload_log_file]
282
+ log_file_name = @options[:upload_log_file].split('/').last
283
+ log_file_name = log_file_name[0..-5] if log_file_name[-4..-1] == '.log'
284
+ logger.debug "Log file name is #{log_file_name}"
285
+ full_log_file_name = @options[:upload_log_file]
286
+ url = Request.get_base_url(get_host) + "/device/upload_log_file/1/#{gg_config.user_id_salt}/#{gg_config.device_id}/#{gg_config.token}/#{log_file_name}"
287
+ log_data = LogData.get_log_data(@options[:upload_log_file], @options[:lines])
288
+ log_time_stamp = LogData.get_log_file_time(@options[:upload_log_file])
289
+ params = {:log_data => log_data,
290
+ :log_time_stamp => log_time_stamp,
291
+ :full_log_file_name => full_log_file_name,
292
+ }
293
+ params[:function_id] = @options[:function_id] if @options[:function_id]
294
+ response = Request.client_service_post url, params
295
+ logger.debug "The response for the request is #{response} #{response.class}"
296
+ raise JCouldNotPostClientDataException.new({:message=> "Could not post data to the gaddygaddy service, error code is #{response.body}"}) unless response[:status].to_i == 0
297
+ end
298
+
299
+
300
+ def self.alert(type)
301
+ alert_timing = case type
302
+ when :no_config_file
303
+ {:on => 0.1, :off => 0.1,:alert_text => 'Could not found any gaddy config file in /conf/gaddy_XXXX.gcf'}
304
+ when :no_network
305
+ {:on => 1,:off => 1, :alert_text => 'Could not connect to internet, network configuration seems broken, restart when fixed'}
306
+ end
307
+ begin
308
+ `echo none >/sys/class/leds/led0/trigger`
309
+ count = 0
310
+ send_info_count = 20 / (alert_timing[:on] + alert_timing[:off])
311
+ do_alert = true
312
+ while do_alert do
313
+ if (count % send_info_count) == 0
314
+ puts alert_timing[:alert_text]
315
+ ESpeak.speak alert_timing[:alert_text]
316
+ end
317
+ `echo 1 >/sys/class/leds/led0/brightness`
318
+ sleep alert_timing[:on]
319
+ `echo 0 >/sys/class/leds/led0/brightness`
320
+ sleep alert_timing[:off]
321
+ count += 1
322
+ end
323
+ ensure
324
+ `echo mmc0 >/sys/class/leds/led0/trigger`
325
+ end
326
+ end
327
+
328
+ def self.ip_to_verify
329
+ '8.8.8.8'
330
+ end
331
+
332
+ def self.network_failed?
333
+ ping_result = `ping -w 10 #{ip_to_verify} -c 1`
334
+ ping_result.index("100% packet loss") || ($?.exitstatus !=0)
335
+ end
336
+
337
+ # Will run chef for network config to try to install network
338
+ def install_network
339
+ # chef_cmd = CHEF_CMD + '
340
+ # run_cmd()
341
+ end
342
+
343
+ # Verify that we have a conf file and verify network connection
344
+ def self.verify_installation
345
+ conf_file = Dir.glob(File.join('/','conf', "gaddy*.gcf"))
346
+ alert(:no_config_file) if conf_file.empty?
347
+ if network_failed?
348
+ # install_network
349
+ alert(:no_network)
350
+ end
351
+ end
352
+
353
+ def read_settings
354
+ environment = ENV['RACK_ENV'] || ENV['ENV'] || 'gaddy'
355
+ YAML.load(File.read(File.join(File.dirname(__FILE__), '..','conf', "#{environment}.yml")))
356
+ end
357
+
358
+ def run
359
+ cmd = read_options
360
+ settings = read_settings
361
+ begin
362
+ Retriable.set_test_mode if @options[:test_mode]
363
+ @device_info = DeviceInfo.new(get_host, gg_config)
364
+ set_log_level @options[:log_level]
365
+ set_log_file @options[:log_file] if @options[:log_file]
366
+ logger.info "Will start #{APPLICATION_NAME} with command #{cmd}"
367
+ case cmd
368
+ when "get_cookbooks"
369
+ chef_files = ChefFiles.new(gg_config, get_host, @options[:chef_dir])
370
+ chef_files.get_cookbooks @options[:file_host], @options[:cookbooks_version]
371
+ when "chef_config"
372
+ get_chef_config
373
+ when 'send_device_data'
374
+ send_device_data
375
+ when "verify_installation"
376
+ self.class.verify_installation
377
+ when 'notify'
378
+ Notification::FileNotification.file_dir = settings['file_dir']
379
+ notification = Notification::Send.new(:speech_enabled => settings['speech_enabled'])
380
+ notification.event = @options[:event]
381
+ notification.notify gg_config.device_id
382
+ when 'upload_log_file'
383
+ upload_log_file
384
+ else
385
+ usage
386
+ raise "No valid command entered, the command is #{cmd}"
387
+ end
388
+
389
+ rescue Exception => e
390
+ logger.error e.message
391
+ logger.error "Enable full stack trace with -l DEBUG" unless logger.debug?
392
+ logger.error "Backtrace:\n\t#{e.backtrace.join("\n\t")}" if logger.debug?
393
+ exit -1
394
+ end
395
+ end
396
+ end