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.
- checksums.yaml +7 -0
- data/bin/gaddygaddy-client +10 -0
- data/bin/interval +8 -0
- data/conf/development.yml +0 -0
- data/conf/gaddy.yml +2 -0
- data/conf/log4r.yml +47 -0
- data/conf/test.yml +1 -0
- data/lib/device_info/device_info.rb +60 -0
- data/lib/gaddygaddy-client/chef_files.rb +98 -0
- data/lib/gaddygaddy-client/device_config.rb +0 -0
- data/lib/gaddygaddy-client/espeak.rb +26 -0
- data/lib/gaddygaddy-client/log_data.rb +42 -0
- data/lib/gaddygaddy-client/notification/espeak.rb +32 -0
- data/lib/gaddygaddy-client/notification/file_notification.rb +43 -0
- data/lib/gaddygaddy-client/notification/file_notification.rb~ +36 -0
- data/lib/gaddygaddy-client/notification/sensu.rb +40 -0
- data/lib/gaddygaddy-client/notification/wall.rb +25 -0
- data/lib/gaddygaddy-client/notification.rb +73 -0
- data/lib/gaddygaddy-client/svn-commit.tmp~ +4 -0
- data/lib/gaddygaddy-client.rb +396 -0
- data/lib/gaddygaddy-client.rb~ +425 -0
- data/lib/gg_config/gg_config.rb +162 -0
- data/lib/interval.rb +280 -0
- data/lib/logging/logging.rb +62 -0
- data/lib/utils/hash_monkeypatch.rb +34 -0
- data/lib/utils/request.rb +52 -0
- data/lib/utils/retriable.rb +63 -0
- data/lib/utils/run.rb +20 -0
- data/locales/en.yml +24 -0
- data/locales/sv.yml +11 -0
- metadata +160 -0
@@ -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
|