cloud66_agent 0.0.1.pre2
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.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/bin/cloud66_agent +80 -0
- data/cloud66_agent.gemspec +35 -0
- data/lib/cloud66_agent/commands/address.rb +21 -0
- data/lib/cloud66_agent/commands/configure.rb +34 -0
- data/lib/cloud66_agent/commands/pulse.rb +16 -0
- data/lib/cloud66_agent/commands/vitals.rb +21 -0
- data/lib/cloud66_agent/plugins/backup.rb +92 -0
- data/lib/cloud66_agent/plugins/plugin.rb +11 -0
- data/lib/cloud66_agent/utils/config.rb +92 -0
- data/lib/cloud66_agent/utils/server.rb +147 -0
- data/lib/cloud66_agent/utils/version.rb +53 -0
- data/lib/cloud66_agent/utils/vital_signs.rb +172 -0
- data/lib/cloud66_agent.rb +20 -0
- metadata +96 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Cloud 66
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Cloud66
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'cloud66_agent'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install cloud66_agent
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/cloud66_agent
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'optparse'
|
3
|
+
require 'logger'
|
4
|
+
require 'cloud66_agent/utils/config'
|
5
|
+
require 'cloud66_agent/utils/version'
|
6
|
+
require 'cloud66_agent'
|
7
|
+
|
8
|
+
# global config variable
|
9
|
+
$config = Cloud66::Utils::Config.new
|
10
|
+
|
11
|
+
OptionParser.new do |opts|
|
12
|
+
opts.banner = "Cloud 66 Agent v#{Cloud66::Utils::Version.current}"
|
13
|
+
|
14
|
+
# following opts only for registration
|
15
|
+
opts.on('--url URL', 'Server URL') do |v|
|
16
|
+
$config.url = v
|
17
|
+
end
|
18
|
+
opts.on('--sockets SOCKETS', 'Sockets URL') do |v|
|
19
|
+
$config.faye_url = v
|
20
|
+
end
|
21
|
+
opts.on('--api-key APIKEY', 'API key') do |v|
|
22
|
+
$config.api_key = v
|
23
|
+
end
|
24
|
+
opts.on('--secret-key SECRETKEY', 'Secret Key') do |v|
|
25
|
+
$config.secret_key = v
|
26
|
+
end
|
27
|
+
opts.on('--server SERVERUID', 'Server id') do |v|
|
28
|
+
@server_uid = v
|
29
|
+
end
|
30
|
+
|
31
|
+
# respond to version requests
|
32
|
+
opts.on('--version') do |v|
|
33
|
+
puts "Cloud 66 Agent #{Cloud66::Utils::Version.current}"
|
34
|
+
exit 0
|
35
|
+
end
|
36
|
+
|
37
|
+
# logging configuration
|
38
|
+
opts.on('--log LOG', 'Log file path') do |v|
|
39
|
+
v == "STDOUT" ? $config.log_path = STDOUT : $config.log_path = v
|
40
|
+
end
|
41
|
+
opts.on('--log_level LOGLEVEL', 'Log level (int)') do |v|
|
42
|
+
$config.log_level = v.to_i
|
43
|
+
end
|
44
|
+
end.parse!
|
45
|
+
|
46
|
+
# prepare the global logger
|
47
|
+
$logger = Logger.new($config.log_path)
|
48
|
+
$logger.level = $config.log_level
|
49
|
+
|
50
|
+
if $config.disabled
|
51
|
+
# no other commands allowed while disabled
|
52
|
+
$logger.error "This agent had been disabled. Please contact Cloud 66 if you think this is in error"
|
53
|
+
exit -1
|
54
|
+
end
|
55
|
+
|
56
|
+
#pick up the command used
|
57
|
+
command = ARGV[0].downcase unless ARGV[0].nil?
|
58
|
+
if command.nil? || command.empty?
|
59
|
+
$logger.debug("Cloud 66 Agent v#{Cloud66::Utils::Version.current} (#{$config.is_agent_configured? ? 'Configured' : 'Not Configured'})")
|
60
|
+
exit 0
|
61
|
+
end
|
62
|
+
|
63
|
+
if !$config.is_agent_configured? && command != 'configure'
|
64
|
+
# no other commands allowed while not configured
|
65
|
+
$logger.error "Can only do command \"configure\" (until its been configured!)"
|
66
|
+
exit -1
|
67
|
+
end
|
68
|
+
|
69
|
+
# handle commands
|
70
|
+
$logger.debug "Performing: \"#{command}\""
|
71
|
+
if command == "configure"
|
72
|
+
Cloud66Agent.configure @server_uid
|
73
|
+
elsif Cloud66Agent.respond_to? command
|
74
|
+
Cloud66Agent.send command
|
75
|
+
else
|
76
|
+
$logger.error "Invalid command: #{command}"
|
77
|
+
exit -1
|
78
|
+
end
|
79
|
+
exit 0
|
80
|
+
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'cloud66_agent/utils/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "cloud66_agent"
|
8
|
+
gem.version = Cloud66::Utils::Version.current
|
9
|
+
gem.platform = Gem::Platform::RUBY
|
10
|
+
gem.date = '2013-04-02'
|
11
|
+
gem.authors = ["Cloud 66"]
|
12
|
+
gem.email = ['hello@cloud66.com']
|
13
|
+
gem.description = "See https://cloud66.com for more info"
|
14
|
+
gem.summary = "Cloud 66 server component"
|
15
|
+
gem.homepage = 'http://cloud66.com'
|
16
|
+
# Manifest
|
17
|
+
gem.files = `git ls-files`.split("\n")
|
18
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
20
|
+
gem.require_paths = ["lib"]
|
21
|
+
# dependencies
|
22
|
+
gem.add_dependency('httparty', '>= 0.8.1')
|
23
|
+
gem.add_dependency('sys-filesystem', '~>1.0.0')
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
# gem.add_dependency('json', '>= 1.6.3')
|
28
|
+
# gem.add_dependency('eventmachine', '>=0.12.0')
|
29
|
+
# gem.add_dependency('faye', '>=0.8.0')
|
30
|
+
# gem.add_dependency('open4', '>=1.3.0')
|
31
|
+
# gem.add_dependency('fog', '~>1.4.0')
|
32
|
+
# gem.add_dependency('cloud66-backup', '~>3.0.25')
|
33
|
+
# gem.add_dependency('highline', '~>1.6.11')
|
34
|
+
|
35
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'cloud66_agent/utils/vital_signs'
|
2
|
+
require 'cloud66_agent/utils/server'
|
3
|
+
|
4
|
+
module Cloud66
|
5
|
+
module Commands
|
6
|
+
class Address
|
7
|
+
def self.perform
|
8
|
+
begin
|
9
|
+
data = Utils::VitalSigns.address_info
|
10
|
+
rescue => exc
|
11
|
+
data = { error: exc.message }
|
12
|
+
end
|
13
|
+
Utils::Server.send_address data
|
14
|
+
exit 0
|
15
|
+
rescue => exc
|
16
|
+
$logger.error "Address Failed: #{exc.message}"
|
17
|
+
exit -1
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'cloud66_agent/utils/vital_signs'
|
2
|
+
require 'cloud66_agent/utils/version'
|
3
|
+
require 'cloud66_agent/utils/server'
|
4
|
+
|
5
|
+
module Cloud66
|
6
|
+
module Commands
|
7
|
+
class Configure
|
8
|
+
def self.perform(server_uid)
|
9
|
+
begin
|
10
|
+
address_info = Utils::VitalSigns.address_info
|
11
|
+
data = {
|
12
|
+
:timezone => Time.new.zone,
|
13
|
+
:server_uid => server_uid,
|
14
|
+
:ext_ip4 => address_info[:ext_ip4],
|
15
|
+
:int_ip4 => address_info[:int_ip4],
|
16
|
+
:ext_ip6 => address_info[:ext_ip6],
|
17
|
+
:int_ip6 => address_info[:int_ip6],
|
18
|
+
:version => Utils::Version.current,
|
19
|
+
:system => Utils::VitalSigns.system_info }
|
20
|
+
rescue => exc
|
21
|
+
data = { error: exc.message }
|
22
|
+
end
|
23
|
+
result = Utils::Server.send_configure data
|
24
|
+
$config.agent_uid = result['uid']
|
25
|
+
$config.save
|
26
|
+
exit 0
|
27
|
+
rescue => exc
|
28
|
+
$logger.error "Configure Failed: #{exc.message}"
|
29
|
+
exit -1
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'cloud66_agent/utils/vital_signs'
|
2
|
+
require 'cloud66_agent/utils/server'
|
3
|
+
|
4
|
+
module Cloud66
|
5
|
+
module Commands
|
6
|
+
class Pulse
|
7
|
+
def self.perform
|
8
|
+
Utils::Server.send_pulse
|
9
|
+
exit 0
|
10
|
+
rescue => exc
|
11
|
+
$logger.error "Pulse Failed: #{exc.message}"
|
12
|
+
exit -1
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'cloud66_agent/utils/vital_signs'
|
2
|
+
require 'cloud66_agent/utils/server'
|
3
|
+
|
4
|
+
module Cloud66
|
5
|
+
module Commands
|
6
|
+
class Vitals
|
7
|
+
def self.perform
|
8
|
+
begin
|
9
|
+
data = Utils::VitalSigns.vitals_info
|
10
|
+
rescue => exc
|
11
|
+
data = { error: exc.message }
|
12
|
+
end
|
13
|
+
Utils::Server.send_vitals data
|
14
|
+
exit 0
|
15
|
+
rescue => exc
|
16
|
+
$logger.error "Vitals Failed: #{exc.message}"
|
17
|
+
exit -1
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'cloud66_agent/server'
|
2
|
+
require 'cloud66_agent/utils/vital_signs'
|
3
|
+
require 'cloud66_agent/utils/version'
|
4
|
+
|
5
|
+
module Cloud66
|
6
|
+
module Plugins
|
7
|
+
class Backup < Plugin
|
8
|
+
def self.perform(server_uid)
|
9
|
+
rescue => exc
|
10
|
+
$logger.error exc
|
11
|
+
exit -1
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
#require File.join(File.dirname(__FILE__), 'quartz_plugin')
|
20
|
+
#require 'tempfile'
|
21
|
+
#require 'fileutils'
|
22
|
+
#
|
23
|
+
#class Backup < QuartzPlugin
|
24
|
+
#
|
25
|
+
# @@version_major = 0
|
26
|
+
# @@version_minor = 0
|
27
|
+
# @@version_revision = 1
|
28
|
+
#
|
29
|
+
# def info
|
30
|
+
# { :uid => "64cec894b98b43ce9e6047a9284a4b7d", :name => "Backup", :version => get_version }
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# def run(message)
|
34
|
+
#
|
35
|
+
# pl = payload(message)
|
36
|
+
#
|
37
|
+
# job_name = pl['job_name']
|
38
|
+
# backup_script = "#Script generated by Cloud66 job '#{job_name}'\n\n" + pl['script']
|
39
|
+
# script_name = pl['script_name']
|
40
|
+
#
|
41
|
+
# random_file = Tempfile.new('cloud66').path + '.rb'
|
42
|
+
# File.open(random_file, 'w') do |f|
|
43
|
+
# f.puts backup_script
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# backup_root_directory = File.expand_path('~/.cloud66/backup')
|
47
|
+
# FileUtils.mkdir_p(backup_root_directory)
|
48
|
+
#
|
49
|
+
# command = "backup perform --trigger #{script_name} --root-path #{backup_root_directory} --config_file #{random_file} 2>&1"
|
50
|
+
# @log.info "Shell command '#{command}'"
|
51
|
+
#
|
52
|
+
# begin
|
53
|
+
# result = run_shell("#{command}")
|
54
|
+
# data = result[:message]
|
55
|
+
#
|
56
|
+
# #regex to identify all the "error" lines
|
57
|
+
# errorRegex = Regexp.new('(?<line>\[\d{4}\/\d{2}\/\d{2}\s\d{2}\:\d{2}\:\d{2}\]\[\\e\[31merror\\e\[0m\].*?)(\\n|$)')
|
58
|
+
# errors = data.scan(errorRegex)
|
59
|
+
#
|
60
|
+
# if errors.size > 0
|
61
|
+
#
|
62
|
+
# #log all the errors
|
63
|
+
# @log.info errors
|
64
|
+
#
|
65
|
+
# #get rid of the backtrace
|
66
|
+
# removeRegex = Regexp.new('(Backtrace\:|\d{2}\:\d{2}\]\[\\e\[31merror\\e\[0m\]\s*\/)')
|
67
|
+
#
|
68
|
+
# #subregex to get rid of nasty timestamp and [\e[31merror\e[0m]
|
69
|
+
# replaceRegex = Regexp.new('^.*\[\\e\[31merror\\e\[0m\]')
|
70
|
+
#
|
71
|
+
# errorResult = []
|
72
|
+
# errors.each do |errorArr|
|
73
|
+
# error = errorArr[0]
|
74
|
+
# errorResult << error.gsub(replaceRegex,'').chomp unless error =~ removeRegex
|
75
|
+
# end
|
76
|
+
#
|
77
|
+
# run_result(false, errorResult.join("\n"))
|
78
|
+
# else
|
79
|
+
# if (result[:ok])
|
80
|
+
# run_result(true, "Backup completed successfully!")
|
81
|
+
# else
|
82
|
+
# run_result(true, "An error occurred!")
|
83
|
+
# end
|
84
|
+
# end
|
85
|
+
#
|
86
|
+
# rescue => ex
|
87
|
+
# run_result(false, "Failed to execute backup due to #{ex}")
|
88
|
+
# ensure
|
89
|
+
# File.delete random_file if File.exists? random_file
|
90
|
+
# end
|
91
|
+
# end
|
92
|
+
#end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module Cloud66
|
2
|
+
module Utils
|
3
|
+
class Config
|
4
|
+
|
5
|
+
# default conf dir
|
6
|
+
CONFIG_PATH = "/etc/cloud66/cloud66_agent.yml"
|
7
|
+
|
8
|
+
attr_accessor :api_key,
|
9
|
+
:agent_uid,
|
10
|
+
:secret_key,
|
11
|
+
:log_path,
|
12
|
+
:log_level,
|
13
|
+
:url,
|
14
|
+
:faye_url,
|
15
|
+
:disabled
|
16
|
+
|
17
|
+
# load up the config at startup
|
18
|
+
def initialize
|
19
|
+
load if File.exists?(CONFIG_PATH)
|
20
|
+
|
21
|
+
# set defaults
|
22
|
+
@log_path = @log_path.nil? || @log_path.to_s.strip.empty? || @log_path == "STDOUT" ? STDOUT : @log_path
|
23
|
+
@log_level ||= 2
|
24
|
+
@url ||= 'https://api.cloud66.com'
|
25
|
+
@faye_url ||= 'https://sockets.cloud66.com/push'
|
26
|
+
@disabled ||= false
|
27
|
+
end
|
28
|
+
|
29
|
+
def is_agent_configured?
|
30
|
+
return !@agent_uid.nil? && !@agent_uid.empty?
|
31
|
+
end
|
32
|
+
|
33
|
+
def save
|
34
|
+
Dir.mkdir(CONFIG_PATH) if !FileTest::directory?(File.dirname(CONFIG_PATH))
|
35
|
+
|
36
|
+
File.open(CONFIG_PATH, 'w+') do |out|
|
37
|
+
data = {
|
38
|
+
'api_key' => @api_key,
|
39
|
+
'agent_uid' => @agent_uid,
|
40
|
+
'secret_key' => @secret_key,
|
41
|
+
'log_path' => @log_path == STDOUT ? "STDOUT" : @log_path,
|
42
|
+
'log_level' => @log_level,
|
43
|
+
'disabled' => @disabled
|
44
|
+
}
|
45
|
+
# store the url if it is different
|
46
|
+
data['url'] = @url if @url != 'https://api.cloud66.com'
|
47
|
+
# store the faye url if it is different
|
48
|
+
data['faye_url'] = @faye_url if @faye_url != 'https://sockets.cloud66.com/push'
|
49
|
+
|
50
|
+
YAML::dump(data, out)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def delete
|
55
|
+
File.delete(CONFIG_PATH) if File.exists?(CONFIG_PATH)
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def load
|
61
|
+
raise "config not found" unless File.exists?(CONFIG_PATH)
|
62
|
+
config = YAML::load(File.open(CONFIG_PATH))
|
63
|
+
|
64
|
+
@api_key = config['api_key']
|
65
|
+
@agent_uid = config['agent_uid']
|
66
|
+
@secret_key = config['secret_key']
|
67
|
+
|
68
|
+
# get if it exists in the config
|
69
|
+
config_url = config['url']
|
70
|
+
@url = config_url if !config_url.nil? && !config_url.strip.empty?
|
71
|
+
|
72
|
+
# get if it exists in the config
|
73
|
+
config_faye_url = config['faye_url']
|
74
|
+
@faye_url = config_faye_url if !config_faye_url.nil? && !config_faye_url.strip.empty?
|
75
|
+
|
76
|
+
# get if it exists in the config
|
77
|
+
config_log_path = config['log_path']
|
78
|
+
@log_path = config_log_path if !config_log_path.nil? && !config_log_path.strip.empty?
|
79
|
+
|
80
|
+
# get if it exists in the config
|
81
|
+
config_log_level = config['log_level']
|
82
|
+
@log_level = config_log_level.to_i
|
83
|
+
|
84
|
+
disabled = config['disabled']
|
85
|
+
@disabled = disabled if !disabled.nil?
|
86
|
+
rescue
|
87
|
+
# we can't load the file
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Cloud66
|
5
|
+
module Utils
|
6
|
+
|
7
|
+
class Server
|
8
|
+
include HTTParty
|
9
|
+
|
10
|
+
def self.send_configure(data)
|
11
|
+
process(do_post('/agent.json', build_content(data)))
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.send_pulse
|
15
|
+
process(do_get("/agent/#{$config.agent_uid}/pulse.json", build_content))
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.send_address(data)
|
19
|
+
process(do_post("/agent/#{$config.agent_uid}/address.json", build_content(data)))
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.send_vitals(data)
|
23
|
+
process(do_post("/agent/#{$config.agent_uid}/vitals.json", build_content(data)))
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def self.process(response)
|
29
|
+
$logger.debug "Received response!"
|
30
|
+
if response.code != 200
|
31
|
+
$logger.debug "Response code: #{response.code}"
|
32
|
+
$logger.debug "Response body: #{response.body}"
|
33
|
+
raise response.body
|
34
|
+
else
|
35
|
+
parsed_response = response.parsed_response
|
36
|
+
unless parsed_response['ok']
|
37
|
+
if parsed_response['shut_down']
|
38
|
+
$config.disabled = true
|
39
|
+
$config.save
|
40
|
+
exit -1
|
41
|
+
elsif parsed_response.has_key?('error')
|
42
|
+
raise parsed_response['error']
|
43
|
+
else
|
44
|
+
raise "unidentified error"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
$logger.debug "Parsed response: #{parsed_response}"
|
48
|
+
parsed_response['data']
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.do_post(url, content)
|
53
|
+
$logger.debug "Sending (post) request..."
|
54
|
+
$logger.debug "Request url: #{url}"
|
55
|
+
$logger.debug "Request content: #{content}"
|
56
|
+
base_uri $config.url
|
57
|
+
post(url, content)
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.do_delete(url, content)
|
61
|
+
$logger.debug "Sending (delete) request..."
|
62
|
+
$logger.debug "Request url: #{url}"
|
63
|
+
$logger.debug "Request content: #{content}"
|
64
|
+
base_uri $config.url
|
65
|
+
delete(url, content)
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.do_get(url, content)
|
69
|
+
$logger.debug "Sending (get) request..."
|
70
|
+
$logger.debug "Request url: #{url}"
|
71
|
+
$logger.debug "Request content: #{content}"
|
72
|
+
base_uri $config.url
|
73
|
+
get(url, content)
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.do_put(url, content)
|
77
|
+
$logger.debug "Sending (put) request..."
|
78
|
+
$logger.debug "Request url: #{url}"
|
79
|
+
$logger.debug "Request content: #{content}"
|
80
|
+
base_uri $config.url
|
81
|
+
put(url, content)
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.build_content(data = nil)
|
85
|
+
time = Time.now.utc.to_i
|
86
|
+
hash = Digest::SHA1.hexdigest("#{$config.api_key}#{$config.secret_key}#{time}").downcase
|
87
|
+
if data.nil?
|
88
|
+
content = { :headers => { 'api_key' => $config.api_key, 'hash' => hash, 'time' => time.to_s } }
|
89
|
+
else
|
90
|
+
content = { :headers => { 'api_key' => $config.api_key, 'hash' => hash, 'time' => time.to_s, 'Content-Type' => 'application/json' }, :body => { data: data }.to_json }
|
91
|
+
end
|
92
|
+
content
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
=begin
|
101
|
+
def get_job
|
102
|
+
process(self.class.get("/queue/#{@agent_id}.json", { :headers => ClientAuth.build_headers(@api_key, @secret_key) } ))
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
|
107
|
+
def unregister(agent)
|
108
|
+
process(self.class.delete("/agent/#{@agent_id}.json", :headers => ClientAuth.build_headers(@api_key, @secret_key)))
|
109
|
+
end
|
110
|
+
|
111
|
+
def post_results(job_id, data)
|
112
|
+
process(self.class.post("/job/#{job_id}/complete.json", { :headers => ClientAuth.build_headers(@api_key, @secret_key).merge({'Content-Type' => 'application/json'}), :body => data.to_json } ))
|
113
|
+
end
|
114
|
+
|
115
|
+
def pulse_without_ip_address
|
116
|
+
process(self.class.get("/agent/#{@agent_id}/pulse.json", { :headers => ClientAuth.build_headers(@api_key, @secret_key) } ))
|
117
|
+
end
|
118
|
+
|
119
|
+
def pulse_with_ip_address(data)
|
120
|
+
process(self.class.post("/agent/#{@agent_id}/pulse.json", { :headers => ClientAuth.build_headers(@api_key, @secret_key).merge({'Content-Type' => 'application/json'}), :body => data.to_json } ))
|
121
|
+
end
|
122
|
+
|
123
|
+
def status(stat)
|
124
|
+
data = { :status => stat }
|
125
|
+
process(self.class.post("/agent/#{@agent_id}/status.json", { :headers => ClientAuth.build_headers(@api_key, @secret_key).merge({'Content-Type' => 'application/json'}), :body => data.to_json }))
|
126
|
+
end
|
127
|
+
|
128
|
+
def init(data)
|
129
|
+
process(self.class.post("/agent/#{@agent_id}/initialize.json", { :headers => ClientAuth.build_headers(@api_key, @secret_key).merge({'Content-Type' => 'application/json'}), :body => data.to_json }))
|
130
|
+
end
|
131
|
+
|
132
|
+
def send_vital_signs(data)
|
133
|
+
process(self.class.post("/agent/#{@agent_id}/vitalsigns.json", { :headers => ClientAuth.build_headers(@api_key, @secret_key).merge({'Content-Type' => 'application/json'}), :body => data.to_json } ))
|
134
|
+
end
|
135
|
+
|
136
|
+
private
|
137
|
+
|
138
|
+
def process(response)
|
139
|
+
if response.code != 200
|
140
|
+
raise response.body
|
141
|
+
else
|
142
|
+
response.parsed_response
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
=end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Cloud66
|
3
|
+
module Utils
|
4
|
+
class Version
|
5
|
+
|
6
|
+
##
|
7
|
+
# Change the MAJOR, MINOR and PATCH constants below
|
8
|
+
# to adjust the version of the Cloud66 Agent gem
|
9
|
+
#
|
10
|
+
# MAJOR:
|
11
|
+
# Defines the major version
|
12
|
+
# MINOR:
|
13
|
+
# Defines the minor version
|
14
|
+
# PATCH:
|
15
|
+
# Defines the patch version
|
16
|
+
MAJOR, MINOR, PATCH = 0, 0, 1
|
17
|
+
|
18
|
+
#ie. PRERELEASE_MODIFIER = 'beta1'
|
19
|
+
PRERELEASE_MODIFIER = "pre2"
|
20
|
+
|
21
|
+
##
|
22
|
+
# Returns the major version ( big release based off of multiple minor releases )
|
23
|
+
def self.major
|
24
|
+
MAJOR
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# Returns the minor version ( small release based off of multiple patches )
|
29
|
+
def self.minor
|
30
|
+
MINOR
|
31
|
+
end
|
32
|
+
|
33
|
+
##
|
34
|
+
# Returns the patch version ( updates, features and (crucial) bug fixes )
|
35
|
+
def self.patch
|
36
|
+
PATCH
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# Returns the prerelease modifier ( not quite ready for public consumption )
|
41
|
+
def self.prerelease_modifier
|
42
|
+
PRERELEASE_MODIFIER
|
43
|
+
end
|
44
|
+
|
45
|
+
##
|
46
|
+
# Returns the current version of the Backup gem ( qualified for the gemspec )
|
47
|
+
def self.current
|
48
|
+
prerelease_modifier.nil? ? "#{major}.#{minor}.#{patch}" : "#{major}.#{minor}.#{patch}.#{prerelease_modifier}"
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
require 'sys/filesystem'
|
2
|
+
require 'socket'
|
3
|
+
|
4
|
+
module Cloud66
|
5
|
+
module Utils
|
6
|
+
class VitalSigns
|
7
|
+
|
8
|
+
def self.system_info
|
9
|
+
return { ext_ipv4: "123.123.123.123",
|
10
|
+
int_ipv4: "123.123.123.123",
|
11
|
+
ext_ipv6: "123.123.123.123",
|
12
|
+
int_ipv6: "123.123.123.123" } if RUBY_PLATFORM.include?('darwin')
|
13
|
+
|
14
|
+
# system info
|
15
|
+
return parse_data(`sudo facter`)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.address_info
|
19
|
+
return { ext_ipv4: "123.123.123.123",
|
20
|
+
int_ipv4: "123.123.123.123",
|
21
|
+
ext_ipv6: "123.123.123.123",
|
22
|
+
int_ipv6: "123.123.123.123" } if RUBY_PLATFORM.include?('darwin')
|
23
|
+
|
24
|
+
# address information
|
25
|
+
hash = parse_data(`facter ipaddress ec2_public_ipv4 ipaddress_eth0 ipaddress6 ipaddress6_eth0`)
|
26
|
+
|
27
|
+
result = {}
|
28
|
+
|
29
|
+
if hash.has_key?('ec2_public_ipv4')
|
30
|
+
# return ec2 info first (most specific)
|
31
|
+
result[:ext_ipv4] = hash['ec2_public_ipv4']
|
32
|
+
elsif hash.has_key?('ipaddress')
|
33
|
+
# return ip address next (general)
|
34
|
+
result[:ext_ipv4] = hash['ipaddress']
|
35
|
+
end
|
36
|
+
result[:int_ipv4] = hash['ipaddress_eth0'] if hash.has_key?('ipaddress_eth0')
|
37
|
+
result[:ext_ipv6] = hash['ipaddress6'] if hash.has_key?('ipaddress6')
|
38
|
+
result[:int_ipv6] = hash['ipaddress6_eth0'] if hash.has_key?('ipaddress6_eth0')
|
39
|
+
|
40
|
+
# don't have any ip address info
|
41
|
+
raise "no address information" if result.empty? || !result.has_key?(:ext_ipv4)
|
42
|
+
|
43
|
+
# return ip address info
|
44
|
+
return result
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.vitals_info
|
48
|
+
return {
|
49
|
+
disk: Utils::VitalSigns.disk_usage_info,
|
50
|
+
cpu: Utils::VitalSigns.cpu_usage_info,
|
51
|
+
memory: Utils::VitalSigns.memory_usage_info
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def self.disk_usage_info
|
58
|
+
space_info = {}
|
59
|
+
Sys::Filesystem.mounts do |mount|
|
60
|
+
stat = Sys::Filesystem.stat(mount.mount_point)
|
61
|
+
|
62
|
+
#skip if this mount is not active
|
63
|
+
#next if stat.blocks_available == 0 && stat.blocks == 0
|
64
|
+
|
65
|
+
mb_free = Float(stat.block_size) * Float(stat.blocks_available) / 1000 / 1000
|
66
|
+
mb_total = Float(stat.block_size) * Float(stat.blocks) / 1000 / 1000
|
67
|
+
mb_used = mb_total - mb_free
|
68
|
+
percent_used = mb_total > 0.0 ? mb_used / mb_total * 100 : 0.0
|
69
|
+
|
70
|
+
space_info[mount.mount_point] = { mb_free: mb_free, mb_used: mb_used, mb_total: mb_total, percent_used: percent_used }
|
71
|
+
mount.mount_point
|
72
|
+
end
|
73
|
+
return space_info
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.cpu_usage_info
|
77
|
+
|
78
|
+
#NOTE: we can get core-level info with mpstat -P ALL 1 1
|
79
|
+
#parse mpstat result
|
80
|
+
mpstat_result = `mpstat 1 5`
|
81
|
+
|
82
|
+
# mpstat_result = <<-SAMPLE
|
83
|
+
#Linux 3.2.0-23-generic (precise64) 12/07/2012 _x86_64_ (2 CPU)
|
84
|
+
#
|
85
|
+
#10:42:50 AM CPU %usr %nice %sys %ddle %irq %soft %steal %guest %idle
|
86
|
+
#10:42:51 AM all 0.00 0.00 0.50 5.00 0.00 0.00 0.00 0.00 99.50
|
87
|
+
#Average: all 0.00 0.00 0.50 50.00 0.00 0.00 0.00 0.00 99.50
|
88
|
+
#SAMPLE
|
89
|
+
|
90
|
+
#split output into lines
|
91
|
+
lines = mpstat_result.split(/\r?\n/)
|
92
|
+
|
93
|
+
#get rid of time (first 13 chars)
|
94
|
+
lines = lines.map { |line| line[13..-1] }
|
95
|
+
|
96
|
+
#get the header line and split into columns
|
97
|
+
header_line = lines.detect { |line| line =~ /%idle/ }
|
98
|
+
columns = header_line.split(/\s+/)
|
99
|
+
|
100
|
+
#detect position of %idle column
|
101
|
+
idle_index = columns.index('%idle')
|
102
|
+
|
103
|
+
#get average line
|
104
|
+
average_line = lines[-1]
|
105
|
+
columns = average_line.split(/\s+/)
|
106
|
+
|
107
|
+
#get idle value
|
108
|
+
idle_string = columns[idle_index]
|
109
|
+
idle_value = idle_string.to_f
|
110
|
+
|
111
|
+
percent_used = 100.0 - idle_value
|
112
|
+
|
113
|
+
#get average utilization value
|
114
|
+
return { percent_used: percent_used, percent_free: idle_value }
|
115
|
+
end
|
116
|
+
|
117
|
+
def self.memory_usage_info
|
118
|
+
free_m_result = `free -m`
|
119
|
+
# free_m_result = <<-SAMPLE
|
120
|
+
# total used free shared buffers cached
|
121
|
+
#Mem: 590 480 109 0 37 227
|
122
|
+
#-/+ buffers/cache: 216 373
|
123
|
+
#Swap: 0 0 0
|
124
|
+
#SAMPLE
|
125
|
+
|
126
|
+
free_m_result.each_line do |line|
|
127
|
+
if line =~ /^Mem:/
|
128
|
+
parts = line.scan(/.*?(\d+)/)
|
129
|
+
parts.flatten!
|
130
|
+
|
131
|
+
mb_total = parts[0].to_f
|
132
|
+
# mb_used is not a true representation due to OS gobbling up mem
|
133
|
+
# mb_used = parts[1].to_i
|
134
|
+
mb_free = parts[2].to_f
|
135
|
+
mb_shared = parts[3].to_f
|
136
|
+
mb_buffers = parts[4].to_f
|
137
|
+
mb_cached = parts[5].to_f
|
138
|
+
|
139
|
+
#The total free memory available to proceses is calculated by adding up Mem:cached + Mem:buffers + Mem:free (99 + 63 + 296)
|
140
|
+
#This then needs to be divided by Mem:total to get the total available free memory (1692)
|
141
|
+
mb_available = mb_cached + mb_buffers + mb_free
|
142
|
+
mb_used = mb_total - mb_available
|
143
|
+
mb_free = mb_total - mb_used
|
144
|
+
percent_used = mb_used / mb_total * 100
|
145
|
+
return { mb_free: mb_free, mb_used: mb_used, mb_total: mb_total, percent_used: percent_used }
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# parse the data, not using YAML due to YAML parsing issues and JSON output not always working
|
151
|
+
def self.parse_data(data)
|
152
|
+
hash = {}
|
153
|
+
data.lines.each do |line|
|
154
|
+
split = line.split('=>')
|
155
|
+
key = split[0]
|
156
|
+
if split.size == 2
|
157
|
+
value = split[1]
|
158
|
+
unless value.nil?
|
159
|
+
value = value.strip
|
160
|
+
# exclude empty or long results (like ssh keys)
|
161
|
+
if !value.empty? && value.size < 100
|
162
|
+
key = key.strip
|
163
|
+
hash[key] = value
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
return hash
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# require all commands
|
2
|
+
Dir.glob(File.dirname(File.absolute_path(__FILE__)) + '/cloud66_agent/commands/*', &method(:require))
|
3
|
+
|
4
|
+
class Cloud66Agent
|
5
|
+
def self.configure(server_uid)
|
6
|
+
Cloud66::Commands::Configure.perform server_uid
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.pulse
|
10
|
+
Cloud66::Commands::Pulse.perform
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.vitals
|
14
|
+
Cloud66::Commands::Vitals.perform
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.address
|
18
|
+
Cloud66::Commands::Address.perform
|
19
|
+
end
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cloud66_agent
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1.pre2
|
5
|
+
prerelease: 6
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Cloud 66
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-04-02 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: httparty
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.8.1
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.8.1
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: sys-filesystem
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 1.0.0
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 1.0.0
|
46
|
+
description: See https://cloud66.com for more info
|
47
|
+
email:
|
48
|
+
- hello@cloud66.com
|
49
|
+
executables:
|
50
|
+
- cloud66_agent
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files: []
|
53
|
+
files:
|
54
|
+
- .gitignore
|
55
|
+
- Gemfile
|
56
|
+
- LICENSE.txt
|
57
|
+
- README.md
|
58
|
+
- Rakefile
|
59
|
+
- bin/cloud66_agent
|
60
|
+
- cloud66_agent.gemspec
|
61
|
+
- lib/cloud66_agent.rb
|
62
|
+
- lib/cloud66_agent/commands/address.rb
|
63
|
+
- lib/cloud66_agent/commands/configure.rb
|
64
|
+
- lib/cloud66_agent/commands/pulse.rb
|
65
|
+
- lib/cloud66_agent/commands/vitals.rb
|
66
|
+
- lib/cloud66_agent/plugins/backup.rb
|
67
|
+
- lib/cloud66_agent/plugins/plugin.rb
|
68
|
+
- lib/cloud66_agent/utils/config.rb
|
69
|
+
- lib/cloud66_agent/utils/server.rb
|
70
|
+
- lib/cloud66_agent/utils/version.rb
|
71
|
+
- lib/cloud66_agent/utils/vital_signs.rb
|
72
|
+
homepage: http://cloud66.com
|
73
|
+
licenses: []
|
74
|
+
post_install_message:
|
75
|
+
rdoc_options: []
|
76
|
+
require_paths:
|
77
|
+
- lib
|
78
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
79
|
+
none: false
|
80
|
+
requirements:
|
81
|
+
- - ! '>='
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ! '>'
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 1.3.1
|
90
|
+
requirements: []
|
91
|
+
rubyforge_project:
|
92
|
+
rubygems_version: 1.8.25
|
93
|
+
signing_key:
|
94
|
+
specification_version: 3
|
95
|
+
summary: Cloud 66 server component
|
96
|
+
test_files: []
|