cheftacular 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/cft +4 -0
- data/bin/cftclr +4 -0
- data/bin/cheftacular +4 -0
- data/bin/client-list +4 -0
- data/lib/cheftacular/README.md +416 -0
- data/lib/cheftacular/actions/check.rb +32 -0
- data/lib/cheftacular/actions/console.rb +62 -0
- data/lib/cheftacular/actions/database.rb +13 -0
- data/lib/cheftacular/actions/db_console.rb +67 -0
- data/lib/cheftacular/actions/deploy.rb +40 -0
- data/lib/cheftacular/actions/log.rb +47 -0
- data/lib/cheftacular/actions/migrate.rb +57 -0
- data/lib/cheftacular/actions/run.rb +64 -0
- data/lib/cheftacular/actions/scale.rb +94 -0
- data/lib/cheftacular/actions/tail.rb +55 -0
- data/lib/cheftacular/actions.rb +14 -0
- data/lib/cheftacular/auditor.rb +46 -0
- data/lib/cheftacular/chef/data_bag.rb +104 -0
- data/lib/cheftacular/cheftacular.rb +55 -0
- data/lib/cheftacular/decryptors.rb +45 -0
- data/lib/cheftacular/encryptors.rb +48 -0
- data/lib/cheftacular/getters.rb +153 -0
- data/lib/cheftacular/helpers.rb +296 -0
- data/lib/cheftacular/initializers.rb +451 -0
- data/lib/cheftacular/parsers.rb +199 -0
- data/lib/cheftacular/remote_helpers.rb +30 -0
- data/lib/cheftacular/stateless_action.rb +16 -0
- data/lib/cheftacular/stateless_actions/add_ssh_key_to_bag.rb +44 -0
- data/lib/cheftacular/stateless_actions/arguments.rb +68 -0
- data/lib/cheftacular/stateless_actions/backups.rb +116 -0
- data/lib/cheftacular/stateless_actions/bootstrappers/centos_bootstrap.rb +7 -0
- data/lib/cheftacular/stateless_actions/bootstrappers/coreos_bootstrap.rb +7 -0
- data/lib/cheftacular/stateless_actions/bootstrappers/fedora_bootstrap.rb +7 -0
- data/lib/cheftacular/stateless_actions/bootstrappers/redhat_bootstrap.rb +7 -0
- data/lib/cheftacular/stateless_actions/bootstrappers/ubuntu_bootstrap.rb +102 -0
- data/lib/cheftacular/stateless_actions/bootstrappers/vyatta_bootstrap.rb +7 -0
- data/lib/cheftacular/stateless_actions/chef_bootstrap.rb +40 -0
- data/lib/cheftacular/stateless_actions/chef_environment.rb +21 -0
- data/lib/cheftacular/stateless_actions/clean_cookbooks.rb +104 -0
- data/lib/cheftacular/stateless_actions/clean_sensu_plugins.rb +19 -0
- data/lib/cheftacular/stateless_actions/clean_server_passwords.rb +14 -0
- data/lib/cheftacular/stateless_actions/cleanup_log_files.rb +14 -0
- data/lib/cheftacular/stateless_actions/client_list.rb +89 -0
- data/lib/cheftacular/stateless_actions/cloud.rb +107 -0
- data/lib/cheftacular/stateless_actions/cloud_bootstrap.rb +109 -0
- data/lib/cheftacular/stateless_actions/compile_audit_log.rb +60 -0
- data/lib/cheftacular/stateless_actions/compile_readme.rb +41 -0
- data/lib/cheftacular/stateless_actions/create_git_key.rb +67 -0
- data/lib/cheftacular/stateless_actions/disk_report.rb +75 -0
- data/lib/cheftacular/stateless_actions/environment.rb +100 -0
- data/lib/cheftacular/stateless_actions/fetch_file.rb +24 -0
- data/lib/cheftacular/stateless_actions/fix_known_hosts.rb +70 -0
- data/lib/cheftacular/stateless_actions/full_bootstrap.rb +30 -0
- data/lib/cheftacular/stateless_actions/get_active_ssh_connections.rb +18 -0
- data/lib/cheftacular/stateless_actions/get_haproxy_log.rb +55 -0
- data/lib/cheftacular/stateless_actions/get_log_from_bag.rb +38 -0
- data/lib/cheftacular/stateless_actions/get_pg_pass.rb +61 -0
- data/lib/cheftacular/stateless_actions/help.rb +71 -0
- data/lib/cheftacular/stateless_actions/initialize_data_bag_contents.rb +220 -0
- data/lib/cheftacular/stateless_actions/knife_upload.rb +23 -0
- data/lib/cheftacular/stateless_actions/pass.rb +49 -0
- data/lib/cheftacular/stateless_actions/reinitialize.rb +46 -0
- data/lib/cheftacular/stateless_actions/remove_client.rb +81 -0
- data/lib/cheftacular/stateless_actions/replication_status.rb +103 -0
- data/lib/cheftacular/stateless_actions/restart_swap.rb +55 -0
- data/lib/cheftacular/stateless_actions/rvm.rb +14 -0
- data/lib/cheftacular/stateless_actions/server_update.rb +99 -0
- data/lib/cheftacular/stateless_actions/service.rb +14 -0
- data/lib/cheftacular/stateless_actions/test_env.rb +82 -0
- data/lib/cheftacular/stateless_actions/update_split_branches.rb +64 -0
- data/lib/cheftacular/stateless_actions/update_tld.rb +62 -0
- data/lib/cheftacular/stateless_actions/upload_nodes.rb +120 -0
- data/lib/cheftacular/stateless_actions/upload_roles.rb +24 -0
- data/lib/cheftacular/version.rb +5 -0
- data/lib/cheftacular.rb +4 -0
- data/lib/cloud_interactor/authentication.rb +56 -0
- data/lib/cloud_interactor/cloud_interactor.rb +23 -0
- data/lib/cloud_interactor/domain/create.rb +17 -0
- data/lib/cloud_interactor/domain/create_record.rb +27 -0
- data/lib/cloud_interactor/domain/destroy.rb +17 -0
- data/lib/cloud_interactor/domain/destroy_record.rb +23 -0
- data/lib/cloud_interactor/domain/list.rb +9 -0
- data/lib/cloud_interactor/domain/list_records.rb +22 -0
- data/lib/cloud_interactor/domain/read.rb +23 -0
- data/lib/cloud_interactor/domain/read_record.rb +27 -0
- data/lib/cloud_interactor/domain/update.rb +18 -0
- data/lib/cloud_interactor/domain/update_record.rb +42 -0
- data/lib/cloud_interactor/domain.rb +18 -0
- data/lib/cloud_interactor/flavor.rb +27 -0
- data/lib/cloud_interactor/helpers.rb +70 -0
- data/lib/cloud_interactor/image.rb +27 -0
- data/lib/cloud_interactor/parser.rb +37 -0
- data/lib/cloud_interactor/server/attach_volume.rb +33 -0
- data/lib/cloud_interactor/server/create.rb +39 -0
- data/lib/cloud_interactor/server/destroy.rb +11 -0
- data/lib/cloud_interactor/server/detach_volume.rb +21 -0
- data/lib/cloud_interactor/server/list.rb +7 -0
- data/lib/cloud_interactor/server/list_volumes.rb +25 -0
- data/lib/cloud_interactor/server/poll.rb +22 -0
- data/lib/cloud_interactor/server/read.rb +9 -0
- data/lib/cloud_interactor/server/read_volume.rb +24 -0
- data/lib/cloud_interactor/server.rb +17 -0
- data/lib/cloud_interactor/version.rb +4 -0
- data/lib/cloud_interactor/volume/create.rb +13 -0
- data/lib/cloud_interactor/volume/destroy.rb +11 -0
- data/lib/cloud_interactor/volume/list.rb +7 -0
- data/lib/cloud_interactor/volume/read.rb +9 -0
- data/lib/cloud_interactor/volume.rb +20 -0
- data/lib/ridley/monkeypatches.rb +11 -0
- data/lib/sshkit/actions/start_commit_check.rb +19 -0
- data/lib/sshkit/actions/start_deploy.rb +25 -0
- data/lib/sshkit/actions/start_log_fetch.rb +91 -0
- data/lib/sshkit/actions/start_task.rb +29 -0
- data/lib/sshkit/getters.rb +67 -0
- data/lib/sshkit/helpers.rb +13 -0
- data/lib/sshkit/monkeypatches.rb +19 -0
- metadata +375 -0
@@ -0,0 +1,120 @@
|
|
1
|
+
|
2
|
+
class Cheftacular
|
3
|
+
class StatelessActionDocumentation
|
4
|
+
def upload_nodes
|
5
|
+
@config['documentation']['stateless_action'] << [
|
6
|
+
"`cft upload_nodes` This command will resync the chef server's nodes with the data in our chef-repo/node_roles. ",
|
7
|
+
|
8
|
+
[
|
9
|
+
" 1. This command changes behavior depending on several factors about both your mode and the state of your environment",
|
10
|
+
|
11
|
+
" 2. In Devops mode, being run directly, this command will prompt you to update a data bag of node_role data that will help " +
|
12
|
+
"non-devops runs perform actions that involve setting roles on servers.",
|
13
|
+
|
14
|
+
" 1. In this setting, any time the chef server's data bag hash differs from the hash stored on disk for a role, you will be " +
|
15
|
+
"prompted to see if you really want to overwrite.",
|
16
|
+
|
17
|
+
" 3. When building new servers *in any mode*, this command will check the node_roles stored in the data bag only and update the " +
|
18
|
+
"run lists of the nodes from that data, NOT from the node_roles data stored on disk in the nodes_dir.",
|
19
|
+
|
20
|
+
" 1. Due to this, only users running this against their chef-repo need to worry about having a nodes_dir, the way it should be."
|
21
|
+
]
|
22
|
+
]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class StatelessAction
|
27
|
+
def upload_nodes invalidate_file_node_cache=false
|
28
|
+
raise "This action can only be performed if the mode is set to devops" if !@config['helper'].running_in_mode?('devops') && !@options['in_scaling']
|
29
|
+
|
30
|
+
@config['chef_environments'].each do |env|
|
31
|
+
@config['initializer'].initialize_data_bags_for_environment env, false, ['node_roles']
|
32
|
+
|
33
|
+
@config['initializer'].initialize_node_roles_bag_contents env
|
34
|
+
end
|
35
|
+
|
36
|
+
nodes = @options['multi-step'] ? @config['getter'].get_true_node_objects(true,true) : @config['getter'].get_true_node_objects(true)
|
37
|
+
|
38
|
+
node_roles_hash, bag_hash, allowed_changes_hash = {},{},{}
|
39
|
+
|
40
|
+
Dir.foreach(@config['locs']['nodes']) do |fr|
|
41
|
+
next if @config['helper'].is_junk_filename?(fr)
|
42
|
+
|
43
|
+
Dir.foreach("#{ @config['locs']['nodes'] }/#{ fr }") do |f|
|
44
|
+
next if @config['helper'].is_junk_filename?(f)
|
45
|
+
|
46
|
+
node_roles_hash[f.split('.json').first] = JSON.parse(File.read("#{ @config['locs']['nodes'] }/#{ fr }/#{ f }"))
|
47
|
+
end
|
48
|
+
end if @config['helper'].running_in_mode?('devops') #only devops modes should have a nodes_dir
|
49
|
+
|
50
|
+
@config['chef_environments'].each do |env|
|
51
|
+
@config[env]['node_roles_bag_hash']['node_roles'].each_pair do |role_name, role_hash|
|
52
|
+
bag_hash[role_hash['name']] = role_hash.to_hash #hashes from chef server are stored as hashie objects until forced into hashes
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
if !@options['force_yes'] && @config['helper'].running_in_mode?('devops')
|
57
|
+
node_roles_hash.each_pair do |role_name, role_hash|
|
58
|
+
overwrite = false
|
59
|
+
if bag_hash[role_name] != role_hash
|
60
|
+
puts "Detected difference between saved roles hash and updated node_roles json hash."
|
61
|
+
|
62
|
+
puts "Saved roles hash:"
|
63
|
+
ap(bag_hash[role_name])
|
64
|
+
|
65
|
+
puts "New roles hash:"
|
66
|
+
ap(role_hash)
|
67
|
+
|
68
|
+
puts "Preparing to overwrite the saved roles hash with the node_roles hash, enter Y/y to confirm."
|
69
|
+
|
70
|
+
input = STDIN.gets.chomp
|
71
|
+
|
72
|
+
overwrite = true if ( input =~ /y|Y|yes|Yes/ ) == 0
|
73
|
+
|
74
|
+
allowed_changes_hash[role_name] = role_hash if overwrite
|
75
|
+
else #bag_hash does not have a key for that role, populate it.
|
76
|
+
allowed_changes_hash[role_name] = role_hash
|
77
|
+
end
|
78
|
+
|
79
|
+
@config[role_hash['chef_environment']]['node_roles_bag_hash']['node_roles'][role_name] = role_hash
|
80
|
+
end
|
81
|
+
else
|
82
|
+
allowed_changes_hash = bag_hash
|
83
|
+
end
|
84
|
+
|
85
|
+
nodes.each do |node|
|
86
|
+
# if there is a node_roles file that completely matches the name of the file, use it
|
87
|
+
changes_for_current_node = false
|
88
|
+
|
89
|
+
if allowed_changes_hash[node.name]
|
90
|
+
allowed_changes_hash[node.name].each_pair do |node_key, node_val|
|
91
|
+
if (node_key =~ /name/) != 0 && node.send(node_key) != node_val
|
92
|
+
puts("Updating #{ node.name } with attribute #{ node_key } = #{ node_val } from #{ node.name }.json") unless @options['quiet']
|
93
|
+
node.send("#{ node_key }=", node_val)
|
94
|
+
|
95
|
+
changes_for_current_node, invalidate_file_node_cache = true, true
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
elsif allowed_changes_hash.keys.include?(node.name.gsub(/\d/,'')) #if there is a template file that matches the stripped down name, use it
|
100
|
+
allowed_changes_hash[node.name.gsub(/\d/,'')].each_pair do |node_key, node_val|
|
101
|
+
if (node_key =~ /name/) != 0 && node.send(node_key) != node_val
|
102
|
+
puts("Updating #{ node.name } with attribute #{ node_key } = #{ node_val } from template json file") unless @options['quiet']
|
103
|
+
node.send("#{ node_key }=", node_val)
|
104
|
+
|
105
|
+
changes_for_current_node, invalidate_file_node_cache = true, true
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
node.save if changes_for_current_node
|
111
|
+
end
|
112
|
+
|
113
|
+
@config['chef_environments'].each do |env|
|
114
|
+
@config['ChefDataBag'].save_node_roles_bag env
|
115
|
+
end if !@options['force_yes'] && @config['helper'].running_in_mode?('devops')
|
116
|
+
|
117
|
+
@config['helper'].cleanup_file_caches('current') if invalidate_file_node_cache
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
|
2
|
+
class Cheftacular
|
3
|
+
class StatelessActionDocumentation
|
4
|
+
def upload_roles
|
5
|
+
@config['documentation']['stateless_action'] << [
|
6
|
+
"`cft upload_roles` This command will resync the chef server's roles with the data in the chef-repo/roles."
|
7
|
+
]
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class StatelessAction
|
12
|
+
def upload_roles
|
13
|
+
raise "This action can only be performed if the mode is set to devops" if !@config['helper'].running_in_mode?('devops') && !@options['in_scaling']
|
14
|
+
|
15
|
+
Dir.foreach(@config['locs']['roles']) do |rd|
|
16
|
+
next if @config['helper'].is_junk_filename?(rd)
|
17
|
+
|
18
|
+
puts("Loading in role from file #{ rd }") if @options['verbose']
|
19
|
+
|
20
|
+
puts `knife role from file "#{ @config['locs']['roles'] }/#{ rd }"`
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/cheftacular.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
|
2
|
+
class CloudInteractor
|
3
|
+
class Authentication
|
4
|
+
def initialize auth_hash, options={}
|
5
|
+
@auth_hash = auth_hash
|
6
|
+
@options = options
|
7
|
+
end
|
8
|
+
|
9
|
+
def auth_service interaction_class
|
10
|
+
except_keys = []
|
11
|
+
|
12
|
+
interaction_class = case interaction_class.downcase
|
13
|
+
when 'volume'
|
14
|
+
case @options['preferred_cloud']
|
15
|
+
when 'rackspace'
|
16
|
+
except_keys = [:provider, :version]
|
17
|
+
|
18
|
+
'Rackspace::BlockStorage'
|
19
|
+
else
|
20
|
+
raise "CloudInteractor Does not currently support this #{ options['preferred_cloud'] }'s' volume creation at this time"
|
21
|
+
end
|
22
|
+
when 'dns'
|
23
|
+
case @options['preferred_cloud']
|
24
|
+
when 'rackspace'
|
25
|
+
except_keys = [:version, :rackspace_region]
|
26
|
+
|
27
|
+
interaction_class
|
28
|
+
else
|
29
|
+
except_keys = [:version]
|
30
|
+
|
31
|
+
interaction_class
|
32
|
+
end
|
33
|
+
else
|
34
|
+
interaction_class
|
35
|
+
end
|
36
|
+
|
37
|
+
classes_to_inject = interaction_class.split('::')
|
38
|
+
classes_to_inject = classes_to_inject.map { |str| str.camelcase }
|
39
|
+
|
40
|
+
cloud_hash = case @options['preferred_cloud']
|
41
|
+
when 'rackspace'
|
42
|
+
{
|
43
|
+
provider: @options['preferred_cloud'].capitalize,
|
44
|
+
rackspace_username: @auth_hash['cloud_auth'][@options['preferred_cloud']]['username'],
|
45
|
+
rackspace_api_key: @auth_hash['cloud_auth'][@options['preferred_cloud']]['api_key'],
|
46
|
+
version: :v2,
|
47
|
+
rackspace_region: @options['preferred_cloud_region'].to_sym,
|
48
|
+
connection_options: {}
|
49
|
+
}.delete_if { |key, value| except_keys.flatten.include?(key) }
|
50
|
+
else raise "CloudInteractor Does not currently support #{ @options['preferred_cloud'] } at this time"
|
51
|
+
end
|
52
|
+
|
53
|
+
Fog.class_eval(classes_to_inject.join('::')).new(cloud_hash)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class CloudInteractor
|
2
|
+
ALLOWED_HLMS = ['domain', 'flavor', 'image', 'server', 'volume']
|
3
|
+
|
4
|
+
def initialize auth_hash, options
|
5
|
+
@main_obj, @classes = {},{}
|
6
|
+
@options = options
|
7
|
+
@main_obj['output'] = {}
|
8
|
+
@auth_hash = auth_hash
|
9
|
+
@classes['helper'] = Helper.new(@main_obj, @classes, @options)
|
10
|
+
@classes['domain'] = Domain.new(@main_obj, @auth_hash, @classes, @options)
|
11
|
+
@classes['flavor'] = Flavor.new(@main_obj, @classes, @options)
|
12
|
+
@classes['image'] = Image.new(@main_obj, @classes, @options)
|
13
|
+
@classes['server'] = Server.new(@main_obj, @classes, @options)
|
14
|
+
@classes['volume'] = Volume.new(@main_obj, @classes, @options)
|
15
|
+
@classes['auth'] = Authentication.new(@auth_hash, @options)
|
16
|
+
end
|
17
|
+
|
18
|
+
def run args
|
19
|
+
parse_args(args) unless args.empty?
|
20
|
+
|
21
|
+
@main_obj['output']
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class CloudInteractor
|
2
|
+
class Domain
|
3
|
+
def create args, already_created=false
|
4
|
+
read args, false
|
5
|
+
|
6
|
+
unless @main_obj["specific_#{ IDENTITY }"].empty?
|
7
|
+
puts "#{ IDENTITY } #{ args[IDENTITY.singularize] } already exists... returning."
|
8
|
+
|
9
|
+
return false
|
10
|
+
end
|
11
|
+
|
12
|
+
@classes['auth'].auth_service(RESOURCE).instance_eval('zones').create(domain: args[IDENTITY.singularize], email: @auth_hash['cloud_auth'][@options['preferred_cloud']]['email'])
|
13
|
+
|
14
|
+
puts "Created #{ IDENTITY } #{ args[IDENTITY.singularize] }..."
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class CloudInteractor
|
2
|
+
class Domain
|
3
|
+
def create_record args, already_created=false
|
4
|
+
args['type'] ||= 'A'
|
5
|
+
args['ttl'] ||= 300
|
6
|
+
|
7
|
+
read args, false
|
8
|
+
|
9
|
+
@main_obj['specific_records'][args[IDENTITY.singularize]].each do |record_hash|
|
10
|
+
already_created = true if record_hash['name'] == "#{ args['subdomain'] }.#{ args[IDENTITY.singularize] }"
|
11
|
+
|
12
|
+
break if already_created
|
13
|
+
end
|
14
|
+
|
15
|
+
if already_created
|
16
|
+
update_record args
|
17
|
+
|
18
|
+
else
|
19
|
+
specific_fog_object = @classes['auth'].auth_service(RESOURCE).instance_eval('zones').get @main_obj["specific_#{ IDENTITY }"].last['id']
|
20
|
+
|
21
|
+
specific_fog_object.records.create(name: "#{ args['subdomain'] }.#{ args[IDENTITY.singularize] }", value: args['target_ip'], type: args['type'], ttl: args['ttl'])
|
22
|
+
|
23
|
+
puts "Attached #{ args['subdomain'] } (#{ args['target_ip'] }) to #{ args[IDENTITY.singularize] }..."
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class CloudInteractor
|
2
|
+
class Domain
|
3
|
+
def destroy args
|
4
|
+
read args, false
|
5
|
+
|
6
|
+
if @main_obj["specific_#{ IDENTITY }"].empty? && @main_obj["specific_#{ IDENTITY }"].last[IDENTITY.singularize] != args[IDENTITY.singularize]
|
7
|
+
puts "#{ IDENTITY } #{ args[IDENTITY.singularize] } doesn't exist... returning."
|
8
|
+
|
9
|
+
return false
|
10
|
+
end
|
11
|
+
|
12
|
+
@classes['auth'].auth_service(RESOURCE).instance_eval('zones').get(@main_obj["specific_#{ IDENTITY }"].last['id']).destroy
|
13
|
+
|
14
|
+
puts "Destroyed #{ IDENTITY.singularize } #{ args[IDENTITY.singularize] }..."
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class CloudInteractor
|
2
|
+
class Domain
|
3
|
+
def destroy_record args, already_exists=false
|
4
|
+
read args, false
|
5
|
+
|
6
|
+
@main_obj['specific_records'][args[IDENTITY.singularize]].each do |record_hash|
|
7
|
+
already_exists = true if record_hash['name'] == "#{ args['subdomain'] }.#{ args[IDENTITY.singularize] }"
|
8
|
+
|
9
|
+
args['id'] = record_hash['id']
|
10
|
+
|
11
|
+
break if already_exists
|
12
|
+
end
|
13
|
+
|
14
|
+
raise "Subdomain not found for #{ args[IDENTITY.singularize] }" unless already_exists
|
15
|
+
|
16
|
+
puts "Destroying #{ args['subdomain'] } from #{ args[IDENTITY.singularize] }..."
|
17
|
+
|
18
|
+
specific_fog_object = @classes['auth'].auth_service(RESOURCE).instance_eval('zones').get @main_obj["specific_#{ IDENTITY }"].last['id']
|
19
|
+
|
20
|
+
specific_fog_object.records.get(args['id']).destroy
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class CloudInteractor
|
2
|
+
class Domain
|
3
|
+
def list_records args, output=false
|
4
|
+
puts "Querying #{ args["domain"] } for rackspace..."
|
5
|
+
|
6
|
+
specific_fog_object = @classes['auth'].auth_service(RESOURCE).instance_eval('zones').get @main_obj["specific_#{ IDENTITY }"].last['id']
|
7
|
+
|
8
|
+
@main_obj['specific_records'] ||= {}
|
9
|
+
@main_obj['specific_records'][args[IDENTITY.singularize]] ||= []
|
10
|
+
@main_obj["specific_#{ IDENTITY }"].last['records'] ||= []
|
11
|
+
|
12
|
+
specific_fog_object.records.each do |record|
|
13
|
+
record_obj = JSON.parse(record.to_json)
|
14
|
+
|
15
|
+
@main_obj["specific_#{ IDENTITY }"].last['records'] << record_obj
|
16
|
+
@main_obj['specific_records'][args[IDENTITY.singularize]] << record_obj
|
17
|
+
end
|
18
|
+
|
19
|
+
ap(@main_obj["specific_#{ IDENTITY }"].last['records']) if output
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class CloudInteractor
|
2
|
+
class Domain
|
3
|
+
def read args={}, output=true
|
4
|
+
list [], false
|
5
|
+
|
6
|
+
@classes['helper'].generic_read_parse args, IDENTITY, output, IDENTITY.singularize
|
7
|
+
|
8
|
+
@main_obj["specific_#{ IDENTITY }"] ||= {}
|
9
|
+
|
10
|
+
specific_identity = @classes['helper'].set_specific_identity args, IDENTITY.singularize
|
11
|
+
|
12
|
+
@main_obj[IDENTITY].each do |identity_hash|
|
13
|
+
next if !specific_identity.nil? && !identity_hash[IDENTITY.singularize].include?(specific_identity)
|
14
|
+
|
15
|
+
self.list_records identity_hash
|
16
|
+
end
|
17
|
+
|
18
|
+
ap(@main_obj["specific_#{ IDENTITY }"]) if output
|
19
|
+
|
20
|
+
puts("#{ specific_identity } not found in #{ IDENTITY }!") if @main_obj["specific_#{ IDENTITY }"].empty?
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class CloudInteractor
|
2
|
+
class Domain
|
3
|
+
def read_record args, output=true, strict_match=false
|
4
|
+
|
5
|
+
specific_record = args['subdomain']
|
6
|
+
|
7
|
+
read args, false
|
8
|
+
|
9
|
+
@main_obj['specific_records'][args[IDENTITY.singularize]].each do |record_hash|
|
10
|
+
if strict_match
|
11
|
+
next unless record_hash['name'] == (specific_record)
|
12
|
+
else
|
13
|
+
next unless record_hash['name'].include?(specific_record)
|
14
|
+
end
|
15
|
+
|
16
|
+
@main_obj['specific_queried_domains'] ||= {}
|
17
|
+
|
18
|
+
@main_obj['specific_queried_domains'][args[IDENTITY.singularize]] ||= []
|
19
|
+
@main_obj['specific_queried_domains'][args[IDENTITY.singularize]] << record_hash
|
20
|
+
|
21
|
+
ap(record_hash) if output
|
22
|
+
end
|
23
|
+
|
24
|
+
puts("#{ args[IDENTITY.singularize] } does not have the subdomain #{ args['subdomain'] }!") if @main_obj["specific_queried_domains"].nil?
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
#TODO Grant more options to update?
|
2
|
+
class CloudInteractor
|
3
|
+
class Domain
|
4
|
+
def update args
|
5
|
+
read args, false
|
6
|
+
|
7
|
+
if (@main_obj["specific_#{ IDENTITY }"].nil? || @main_obj["specific_#{ IDENTITY }"].empty?) && @main_obj["specific_#{ IDENTITY }"].last[IDENTITY.singularize] != args[IDENTITY.singularize]
|
8
|
+
puts "#{ IDENTITY } #{ args[IDENTITY.singularize] } doesn't exist... returning."
|
9
|
+
|
10
|
+
return false
|
11
|
+
end
|
12
|
+
|
13
|
+
@classes['auth'].auth_service(resource).instance_eval('zones').get(@main_obj["specific_#{ IDENTITY }"].last['id']).update(ttl: 5, email: @auth_hash['cloud_auth'][@options['preferred_cloud']]['email'])
|
14
|
+
|
15
|
+
puts "Updated #{ IDENTITY } #{ args[IDENTITY.singularize] }..."
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class CloudInteractor
|
2
|
+
class Domain
|
3
|
+
def update_record args, already_created=false
|
4
|
+
args['type'] ||= 'A'
|
5
|
+
args['ttl'] ||= 300
|
6
|
+
|
7
|
+
read args, false
|
8
|
+
|
9
|
+
puts "Updating #{ args['subdomain'] } for #{ args[IDENTITY.singularize] }..."
|
10
|
+
|
11
|
+
@main_obj['specific_records'][args[IDENTITY.singularize]].each do |record_hash|
|
12
|
+
already_created = true if record_hash['name'] == "#{ args['subdomain'] }.#{ args[IDENTITY.singularize] }"
|
13
|
+
|
14
|
+
args['id'] = record_hash['id']
|
15
|
+
|
16
|
+
break if already_created
|
17
|
+
end
|
18
|
+
|
19
|
+
if already_created
|
20
|
+
specific_fog_object = @classes['auth'].auth_service(RESOURCE).instance_eval('zones').get @main_obj["specific_#{ IDENTITY }"].last['id']
|
21
|
+
|
22
|
+
#the fact that there is no public update method is silly
|
23
|
+
specific_record = specific_fog_object.records.get(args['id'])
|
24
|
+
|
25
|
+
case @options['preferred_cloud']
|
26
|
+
when 'rackspace'
|
27
|
+
specific_record.type = args['type']
|
28
|
+
specific_record.value = args['target_ip']
|
29
|
+
specific_record.ttl = args['ttl']
|
30
|
+
|
31
|
+
specific_record.save
|
32
|
+
else
|
33
|
+
raise "Unsupported action #{ __method__ } for #{ @options['preferred_cloud'] }. Please create an issue on github or submit a PR to fix this issue."
|
34
|
+
end
|
35
|
+
|
36
|
+
puts "Updated #{ args['subdomain'] } (#{ args['target_ip'] }) to #{ args[IDENTITY.singularize] }..."
|
37
|
+
else
|
38
|
+
create_record [ args ]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
|
2
|
+
class CloudInteractor
|
3
|
+
class Domain #http://docs.rackspace.com/cdns/api/v1.0/cdns-devguide/content/API_Operations_Wadl-d1e2648.html
|
4
|
+
IDENTITY = 'domains'
|
5
|
+
RESOURCE = 'DNS'
|
6
|
+
|
7
|
+
def initialize main_obj, auth_hash, classes, options={}
|
8
|
+
@main_obj = main_obj
|
9
|
+
@auth_hash = auth_hash
|
10
|
+
@options = options
|
11
|
+
@classes = classes
|
12
|
+
end
|
13
|
+
|
14
|
+
def run method, args
|
15
|
+
self.send(method, args)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
|
2
|
+
class CloudInteractor
|
3
|
+
class Flavor #http://docs.rackspace.com/servers/api/v2/cs-devguide/content/List_Flavors-d1e4188.html
|
4
|
+
IDENTITY = 'flavors'
|
5
|
+
RESOURCE = 'compute'
|
6
|
+
|
7
|
+
def initialize main_obj, classes, options={}
|
8
|
+
@main_obj = main_obj
|
9
|
+
@options = options
|
10
|
+
@classes = classes
|
11
|
+
end
|
12
|
+
|
13
|
+
def run method, args
|
14
|
+
self.send(method, args)
|
15
|
+
end
|
16
|
+
|
17
|
+
def list args={}, output=true
|
18
|
+
@classes['helper'].generic_list_call IDENTITY, RESOURCE, output
|
19
|
+
end
|
20
|
+
|
21
|
+
def read args, output=true
|
22
|
+
list [], false
|
23
|
+
|
24
|
+
@classes['helper'].generic_read_parse args, IDENTITY, output
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
class CloudInteractor
|
2
|
+
class Helper
|
3
|
+
def initialize main_obj, classes, options={}
|
4
|
+
@main_obj = main_obj
|
5
|
+
@options = options
|
6
|
+
@classes = classes
|
7
|
+
end
|
8
|
+
|
9
|
+
def set_specific_identity args, key_to_extract
|
10
|
+
case args.class.to_s
|
11
|
+
when "Hash" then args[key_to_extract]
|
12
|
+
when "String" then args
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def generic_list_call identity, resource, output=true
|
17
|
+
puts "Returning list of #{ identity } for #{ @options['preferred_cloud'] }..."
|
18
|
+
|
19
|
+
@main_obj[identity] = JSON.parse(@classes['auth'].auth_service(resource).instance_eval(identity).to_json)
|
20
|
+
|
21
|
+
ap(@main_obj[identity]) if output && !@options['in_scaling']
|
22
|
+
end
|
23
|
+
|
24
|
+
def generic_read_parse args, identity, output=true, mode='name', search_key='name'
|
25
|
+
search_key = mode if mode != 'name' && search_key == 'name'
|
26
|
+
|
27
|
+
specific_identity = set_specific_identity args, search_key
|
28
|
+
|
29
|
+
@main_obj["specific_#{ identity }"] ||= []
|
30
|
+
|
31
|
+
@main_obj[identity].each do |identity_hash|
|
32
|
+
if specific_identity.nil?
|
33
|
+
puts("Query arguments \"#{ args }\" are not being mapped correctly for #{ identity.singularize } reads from method #{ caller[3][/`.*'/][1..-2] }! This read will return no objects.")
|
34
|
+
|
35
|
+
break
|
36
|
+
end
|
37
|
+
|
38
|
+
next if identity_hash[mode] && !identity_hash[mode].include?(specific_identity)
|
39
|
+
next if identity == 'servers' && identity_hash['state'] == 'DELETED' #FOR SOME REASON you will get status 'DELETED' items on reads sometimes for rackspace servers
|
40
|
+
|
41
|
+
case identity
|
42
|
+
when 'image' then @main_obj["specific_#{ identity }"] << identity_hash unless identity_hash[mode].include?(@options['virtualization_mode'])
|
43
|
+
else @main_obj["specific_#{ identity }"] << identity_hash
|
44
|
+
end
|
45
|
+
|
46
|
+
ap(identity_hash) if output
|
47
|
+
end
|
48
|
+
|
49
|
+
puts("#{ specific_identity } not found in #{ identity }!") if @main_obj["specific_#{ identity }"].empty?
|
50
|
+
end
|
51
|
+
|
52
|
+
def generic_destroy_parse destroy_hash, identity, resource, mode='name'
|
53
|
+
puts("Queried #{ identity } #{ ap @main_obj["specific_#{ identity }"] }") if @options['verbose']
|
54
|
+
|
55
|
+
raise "#{ identity.singularize } not found for #{ destroy_hash[mode] }" unless @main_obj["specific_#{ identity }"]
|
56
|
+
|
57
|
+
if destroy_hash[mode].empty? || @main_obj["specific_#{ identity }"].last[mode] != destroy_hash[mode] #without this it will delete the first object in the list, this is obviously bad
|
58
|
+
raise "Name mismatch on destroy! Expected #{ destroy_hash[mode] } and was going to destroy #{ @main_obj["specific_#{ identity }"].last[mode] }"
|
59
|
+
end
|
60
|
+
|
61
|
+
puts "Destroying #{ destroy_hash[mode] }..."
|
62
|
+
|
63
|
+
specific_fog_object = @classes['auth'].auth_service(resource).instance_eval(identity).get @main_obj["specific_#{ identity }"].last['id']
|
64
|
+
|
65
|
+
@main_obj["#{ identity }_destroy_request"] = specific_fog_object.destroy
|
66
|
+
|
67
|
+
puts "REMINDER! This destroy is not instant! It can take up to a few minutes for a #{ identity.singularize } to actually be fully destroyed!"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
|
2
|
+
class CloudInteractor
|
3
|
+
class Image #http://docs.rackspace.com/servers/api/v2/cs-devguide/content/List_Images-d1e4435.html
|
4
|
+
IDENTITY = 'images'
|
5
|
+
RESOURCE = 'compute'
|
6
|
+
|
7
|
+
def initialize main_obj, classes, options={}
|
8
|
+
@main_obj = main_obj
|
9
|
+
@options = options
|
10
|
+
@classes = classes
|
11
|
+
end
|
12
|
+
|
13
|
+
def run method, args
|
14
|
+
self.send(method, args)
|
15
|
+
end
|
16
|
+
|
17
|
+
def list args, output=true
|
18
|
+
@classes['helper'].generic_list_call IDENTITY, RESOURCE, output
|
19
|
+
end
|
20
|
+
|
21
|
+
def read args, output=true
|
22
|
+
list [], false
|
23
|
+
|
24
|
+
@classes['helper'].generic_read_parse args, IDENTITY, output
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class CloudInteractor
|
2
|
+
def parse_args args, final_args={}
|
3
|
+
#example args: domain "create:mydomain.us:23.253.44.192:my"
|
4
|
+
|
5
|
+
raise "This class does not support #{ args[0] } at this time" unless ALLOWED_HLMS.include?(args[0])
|
6
|
+
|
7
|
+
@classes[args[0]].send(:run, args[1].split(':').first, parse_args_hash(args))
|
8
|
+
end
|
9
|
+
|
10
|
+
def parse_args_hash args, hash_args={}
|
11
|
+
prep_args = args[1].split(':')
|
12
|
+
|
13
|
+
hash_args[prep_args[0]] = {}
|
14
|
+
|
15
|
+
if prep_args.count > 1
|
16
|
+
prep_args[1..(prep_args.count-1)].each do |arg|
|
17
|
+
hash_args[prep_args[0]][prep_args.index(arg)] = arg
|
18
|
+
end
|
19
|
+
else
|
20
|
+
hash_args[prep_args[0]]['type'] = prep_args[0]
|
21
|
+
end
|
22
|
+
|
23
|
+
mappings = case
|
24
|
+
#this case should always be first or else "server" case overrides
|
25
|
+
when prep_args[0] =~ /attach_volume|detach_volume|list_volumes|read_volume/ then { 1 => "server_name", 2 => "volume_name", 3 => "size", 4 => "device_location", 5 => 'volume_type'}
|
26
|
+
#remap the args from generic args to ones specific for a case
|
27
|
+
when args[0] =~ /domain/ then { 1 => "domain", 2 => "subdomain", 3 => "target_ip", 4 => 'type' }
|
28
|
+
when args[0] =~ /server/ then { 1 => "name", 2 => "flavor" }
|
29
|
+
when args[0] =~ /volume/ then { 1 => "display_name", 2 => "size", 3 => 'volume_type' }
|
30
|
+
when args[0] =~ /flavor/ then { 1 => "name" }
|
31
|
+
when args[0] =~ /image/ then { 1 => "name" }
|
32
|
+
else raise "FATAL! Unsupported High Level Class #{ args[0] } for CloudInteractor! Please raise an issue on github with this stacktrace! args:#{ args }"
|
33
|
+
end
|
34
|
+
|
35
|
+
Hash[hash_args[hash_args.keys.first].map {|k,v| [mappings[k], v]}]
|
36
|
+
end
|
37
|
+
end
|