cheftacular 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. checksums.yaml +7 -0
  2. data/bin/cft +4 -0
  3. data/bin/cftclr +4 -0
  4. data/bin/cheftacular +4 -0
  5. data/bin/client-list +4 -0
  6. data/lib/cheftacular/README.md +416 -0
  7. data/lib/cheftacular/actions/check.rb +32 -0
  8. data/lib/cheftacular/actions/console.rb +62 -0
  9. data/lib/cheftacular/actions/database.rb +13 -0
  10. data/lib/cheftacular/actions/db_console.rb +67 -0
  11. data/lib/cheftacular/actions/deploy.rb +40 -0
  12. data/lib/cheftacular/actions/log.rb +47 -0
  13. data/lib/cheftacular/actions/migrate.rb +57 -0
  14. data/lib/cheftacular/actions/run.rb +64 -0
  15. data/lib/cheftacular/actions/scale.rb +94 -0
  16. data/lib/cheftacular/actions/tail.rb +55 -0
  17. data/lib/cheftacular/actions.rb +14 -0
  18. data/lib/cheftacular/auditor.rb +46 -0
  19. data/lib/cheftacular/chef/data_bag.rb +104 -0
  20. data/lib/cheftacular/cheftacular.rb +55 -0
  21. data/lib/cheftacular/decryptors.rb +45 -0
  22. data/lib/cheftacular/encryptors.rb +48 -0
  23. data/lib/cheftacular/getters.rb +153 -0
  24. data/lib/cheftacular/helpers.rb +296 -0
  25. data/lib/cheftacular/initializers.rb +451 -0
  26. data/lib/cheftacular/parsers.rb +199 -0
  27. data/lib/cheftacular/remote_helpers.rb +30 -0
  28. data/lib/cheftacular/stateless_action.rb +16 -0
  29. data/lib/cheftacular/stateless_actions/add_ssh_key_to_bag.rb +44 -0
  30. data/lib/cheftacular/stateless_actions/arguments.rb +68 -0
  31. data/lib/cheftacular/stateless_actions/backups.rb +116 -0
  32. data/lib/cheftacular/stateless_actions/bootstrappers/centos_bootstrap.rb +7 -0
  33. data/lib/cheftacular/stateless_actions/bootstrappers/coreos_bootstrap.rb +7 -0
  34. data/lib/cheftacular/stateless_actions/bootstrappers/fedora_bootstrap.rb +7 -0
  35. data/lib/cheftacular/stateless_actions/bootstrappers/redhat_bootstrap.rb +7 -0
  36. data/lib/cheftacular/stateless_actions/bootstrappers/ubuntu_bootstrap.rb +102 -0
  37. data/lib/cheftacular/stateless_actions/bootstrappers/vyatta_bootstrap.rb +7 -0
  38. data/lib/cheftacular/stateless_actions/chef_bootstrap.rb +40 -0
  39. data/lib/cheftacular/stateless_actions/chef_environment.rb +21 -0
  40. data/lib/cheftacular/stateless_actions/clean_cookbooks.rb +104 -0
  41. data/lib/cheftacular/stateless_actions/clean_sensu_plugins.rb +19 -0
  42. data/lib/cheftacular/stateless_actions/clean_server_passwords.rb +14 -0
  43. data/lib/cheftacular/stateless_actions/cleanup_log_files.rb +14 -0
  44. data/lib/cheftacular/stateless_actions/client_list.rb +89 -0
  45. data/lib/cheftacular/stateless_actions/cloud.rb +107 -0
  46. data/lib/cheftacular/stateless_actions/cloud_bootstrap.rb +109 -0
  47. data/lib/cheftacular/stateless_actions/compile_audit_log.rb +60 -0
  48. data/lib/cheftacular/stateless_actions/compile_readme.rb +41 -0
  49. data/lib/cheftacular/stateless_actions/create_git_key.rb +67 -0
  50. data/lib/cheftacular/stateless_actions/disk_report.rb +75 -0
  51. data/lib/cheftacular/stateless_actions/environment.rb +100 -0
  52. data/lib/cheftacular/stateless_actions/fetch_file.rb +24 -0
  53. data/lib/cheftacular/stateless_actions/fix_known_hosts.rb +70 -0
  54. data/lib/cheftacular/stateless_actions/full_bootstrap.rb +30 -0
  55. data/lib/cheftacular/stateless_actions/get_active_ssh_connections.rb +18 -0
  56. data/lib/cheftacular/stateless_actions/get_haproxy_log.rb +55 -0
  57. data/lib/cheftacular/stateless_actions/get_log_from_bag.rb +38 -0
  58. data/lib/cheftacular/stateless_actions/get_pg_pass.rb +61 -0
  59. data/lib/cheftacular/stateless_actions/help.rb +71 -0
  60. data/lib/cheftacular/stateless_actions/initialize_data_bag_contents.rb +220 -0
  61. data/lib/cheftacular/stateless_actions/knife_upload.rb +23 -0
  62. data/lib/cheftacular/stateless_actions/pass.rb +49 -0
  63. data/lib/cheftacular/stateless_actions/reinitialize.rb +46 -0
  64. data/lib/cheftacular/stateless_actions/remove_client.rb +81 -0
  65. data/lib/cheftacular/stateless_actions/replication_status.rb +103 -0
  66. data/lib/cheftacular/stateless_actions/restart_swap.rb +55 -0
  67. data/lib/cheftacular/stateless_actions/rvm.rb +14 -0
  68. data/lib/cheftacular/stateless_actions/server_update.rb +99 -0
  69. data/lib/cheftacular/stateless_actions/service.rb +14 -0
  70. data/lib/cheftacular/stateless_actions/test_env.rb +82 -0
  71. data/lib/cheftacular/stateless_actions/update_split_branches.rb +64 -0
  72. data/lib/cheftacular/stateless_actions/update_tld.rb +62 -0
  73. data/lib/cheftacular/stateless_actions/upload_nodes.rb +120 -0
  74. data/lib/cheftacular/stateless_actions/upload_roles.rb +24 -0
  75. data/lib/cheftacular/version.rb +5 -0
  76. data/lib/cheftacular.rb +4 -0
  77. data/lib/cloud_interactor/authentication.rb +56 -0
  78. data/lib/cloud_interactor/cloud_interactor.rb +23 -0
  79. data/lib/cloud_interactor/domain/create.rb +17 -0
  80. data/lib/cloud_interactor/domain/create_record.rb +27 -0
  81. data/lib/cloud_interactor/domain/destroy.rb +17 -0
  82. data/lib/cloud_interactor/domain/destroy_record.rb +23 -0
  83. data/lib/cloud_interactor/domain/list.rb +9 -0
  84. data/lib/cloud_interactor/domain/list_records.rb +22 -0
  85. data/lib/cloud_interactor/domain/read.rb +23 -0
  86. data/lib/cloud_interactor/domain/read_record.rb +27 -0
  87. data/lib/cloud_interactor/domain/update.rb +18 -0
  88. data/lib/cloud_interactor/domain/update_record.rb +42 -0
  89. data/lib/cloud_interactor/domain.rb +18 -0
  90. data/lib/cloud_interactor/flavor.rb +27 -0
  91. data/lib/cloud_interactor/helpers.rb +70 -0
  92. data/lib/cloud_interactor/image.rb +27 -0
  93. data/lib/cloud_interactor/parser.rb +37 -0
  94. data/lib/cloud_interactor/server/attach_volume.rb +33 -0
  95. data/lib/cloud_interactor/server/create.rb +39 -0
  96. data/lib/cloud_interactor/server/destroy.rb +11 -0
  97. data/lib/cloud_interactor/server/detach_volume.rb +21 -0
  98. data/lib/cloud_interactor/server/list.rb +7 -0
  99. data/lib/cloud_interactor/server/list_volumes.rb +25 -0
  100. data/lib/cloud_interactor/server/poll.rb +22 -0
  101. data/lib/cloud_interactor/server/read.rb +9 -0
  102. data/lib/cloud_interactor/server/read_volume.rb +24 -0
  103. data/lib/cloud_interactor/server.rb +17 -0
  104. data/lib/cloud_interactor/version.rb +4 -0
  105. data/lib/cloud_interactor/volume/create.rb +13 -0
  106. data/lib/cloud_interactor/volume/destroy.rb +11 -0
  107. data/lib/cloud_interactor/volume/list.rb +7 -0
  108. data/lib/cloud_interactor/volume/read.rb +9 -0
  109. data/lib/cloud_interactor/volume.rb +20 -0
  110. data/lib/ridley/monkeypatches.rb +11 -0
  111. data/lib/sshkit/actions/start_commit_check.rb +19 -0
  112. data/lib/sshkit/actions/start_deploy.rb +25 -0
  113. data/lib/sshkit/actions/start_log_fetch.rb +91 -0
  114. data/lib/sshkit/actions/start_task.rb +29 -0
  115. data/lib/sshkit/getters.rb +67 -0
  116. data/lib/sshkit/helpers.rb +13 -0
  117. data/lib/sshkit/monkeypatches.rb +19 -0
  118. 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
@@ -0,0 +1,5 @@
1
+ class Cheftacular
2
+ #major_version.minor_version.bugfixes
3
+ VERSION = "2.0.0"
4
+ RUBY_VERSION = "2.2.2"
5
+ end
@@ -0,0 +1,4 @@
1
+ require 'cheftacular/version'
2
+
3
+ class Cheftacular
4
+ end
@@ -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,9 @@
1
+ class CloudInteractor
2
+ class Domain
3
+ def list args={}, output=true
4
+ @classes['helper'].generic_list_call 'zones', RESOURCE, output
5
+
6
+ @main_obj[IDENTITY] = @main_obj['zones']
7
+ end
8
+ end
9
+ 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