cheftacular 2.0.0

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.
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