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,40 @@
1
+ class Cheftacular
2
+ class ActionDocumentation
3
+ def deploy
4
+ @config['documentation']['action'] << [
5
+ "`cft deploy` will do a simple chef-client run on the servers for a role. " +
6
+ "Logs of the run itself will be sent to the local log directory in the application (or chef-repo) where the run was conducted.",
7
+
8
+ [
9
+ " 1. This command also restarts services on the server and updates the code. Changes behavior slightly with the `-z|-Z` args."
10
+ ]
11
+ ]
12
+ end
13
+ end
14
+
15
+ class Action
16
+ def deploy
17
+ nodes = @config['getter'].get_true_node_objects false, true #when this is run in scaling we'll need to make sure we deploy to new nodes
18
+
19
+ nodes = @config['parser'].exclude_nodes( nodes, [{ if: "role[#{ @options['negative_role'] }]" }]) if @options['negative_role']
20
+
21
+ #this must always precede on () calls so they have the instance variables they need
22
+ options, locs, ridley, logs_bag_hash, pass_bag_hash, bundle_command, cheftacular, passwords = @config['helper'].set_local_instance_vars
23
+
24
+ #on is namespaced to SSHKit::Backend::Netssh.on
25
+ on ( nodes.map { |n| "deploy@" + n.public_ipaddress } ), in: :groups, limit: 10, wait: 5 do |host|
26
+ n = get_node_from_address(nodes, host.hostname)
27
+
28
+ puts("Beginning chef client run for #{ n.name } (#{ n.public_ipaddress }) on role #{ options['role'] }") unless options['quiet']
29
+
30
+ log_data, timestamp = start_deploy( n.name, n.public_ipaddress, options, locs, passwords)
31
+
32
+ logs_bag_hash["#{ n.name }-deploy"] = { text: log_data.scrub_pretty_text, timestamp: timestamp }
33
+ end
34
+
35
+ @config['ChefDataBag'].save_logs_bag unless @options['debug'] #the debug chef-client runs are literally too large to POST
36
+
37
+ migrate(nodes) if @config['getter'].get_current_repo_config['database'] != 'none' && !@options['run_migration_already']
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,47 @@
1
+ class Cheftacular
2
+ class ActionDocumentation
3
+ def log
4
+ @config['documentation']['action'] << [
5
+ "`cft log` this command will output the last 500 lines of logs " +
6
+ "from every server set for CODEBASE (can be given additional args to specify) to the log directory",
7
+
8
+ [
9
+ " 1. `--nginx` will fetch the nginx logs as well as the application logs",
10
+
11
+ " 2. `--full` will fetch the entirety of the logs (will fetch the entire nginx log too if `--nginx` is specified)",
12
+
13
+ " 3. `--num INTEGER` will fetch the last INTEGER lines of logs",
14
+
15
+ " 1. `-l|--lines INTEGER` does the exact same thing as `--num INTEGER`.",
16
+
17
+ " 4. `--fetch-backup` If doing a pg_data log, this will fetch the latest logs from the pg_data log directory for each database."
18
+ ]
19
+ ]
20
+ end
21
+ end
22
+
23
+ class Action
24
+ def log
25
+ nodes = @config['getter'].get_true_node_objects
26
+
27
+ nodes = @config['parser'].exclude_nodes( nodes, [{ unless: "role[#{ @options['role'] }]" }] )
28
+
29
+ #this must always precede on () calls so they have the instance variables they need
30
+ options, locs, ridley, logs_bag_hash, pass_bag_hash, bundle_command, cheftacular, passwords = @config['helper'].set_local_instance_vars
31
+
32
+ getter = @config['getter']
33
+
34
+ on ( nodes.map { |n| "deploy@" + n.public_ipaddress } ), in: :parallel do |host|
35
+ n = get_node_from_address(nodes, host.hostname)
36
+
37
+ puts("Beginning log fetch run for #{ n.name } (#{ n.public_ipaddress }) on role #{ options['role'] }") unless options['quiet']
38
+
39
+ if getter.get_current_stack.nil?
40
+ start_log_role_map( name, n.public_ipaddress, getter.get_current_role_map['log_location'], options, locs, cheftacular, passwords)
41
+ else
42
+ self.send("start_log_fetch_#{ getter.get_current_stack }", n.name, n.public_ipaddress, n.run_list, options, locs, cheftacular, passwords)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,57 @@
1
+ class Cheftacular
2
+ class ActionDocumentation
3
+ def migrate
4
+ @config['documentation']['action'] << [
5
+ "`cft migrate` this command will grab the first alphabetical node for a repository " +
6
+ "and run a migration that will hit the database primary server."
7
+ ]
8
+ end
9
+ end
10
+
11
+ class Action
12
+ def migrate nodes=[]
13
+ self.send("migrate_#{ @config['getter'].get_current_stack }", nodes)
14
+ end
15
+
16
+ def migrate_ruby_on_rails nodes=[]
17
+ nodes = @config['getter'].get_true_node_objects if nodes.empty?
18
+
19
+ #must have rails stack to run migrations, only want ONE node
20
+ nodes = @config['parser'].exclude_nodes(nodes, [{ unless: "role[#{ @options['role'] }]" }, { unless: 'role[rails]' }], true)
21
+
22
+ log_data = ""
23
+
24
+ #this must always precede on () calls so they have the instance variables they need
25
+ options, locs, ridley, logs_bag_hash, pass_bag_hash, bundle_command, cheftacular, passwords = @config['helper'].set_local_instance_vars
26
+
27
+ on ( nodes.map { |n| "deploy@" + n.public_ipaddress } ) do |host|
28
+ n = get_node_from_address(nodes, host.hostname)
29
+
30
+ puts("Beginning migration run for #{ n.name } (#{ n.public_ipaddress }) on role #{ options['role'] }") unless options['quiet']
31
+
32
+ log_data, timestamp = start_task( n.name, n.public_ipaddress, n.run_list, "#{ bundle_command } exec rake db:migrate", options, locs, cheftacular)
33
+
34
+ logs_bag_hash["#{ n.name }-migrate"] = { text: log_data.scrub_pretty_text, timestamp: timestamp }
35
+ end
36
+
37
+ @config['ChefDataBag'].save_logs_bag
38
+
39
+ @options['run_migration_already'] = true
40
+
41
+ #restart the servers again after a deploy with a migration just in case
42
+ deploy if !log_data.empty? && log_data != @config['cheftacular']['repositories'][@options['role']]['not_a_migration_message']
43
+ end
44
+
45
+ def migrate_wordpress nodes=[]
46
+ raise "Not yet implemented"
47
+ end
48
+
49
+ def migrate_nodejs nodes=[]
50
+ raise "Not yet implemented"
51
+ end
52
+
53
+ def migrate_all nodes=[]
54
+ raise "You attempted to migrate the all role, this is not possible."
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,64 @@
1
+ class Cheftacular
2
+ class ActionDocumentation
3
+ def run
4
+ @config['documentation']['action'] << [
5
+ "`cft run COMMAND [--all]` will trigger the command on the first server in the role. " +
6
+ "Can be used to run rake commands or anything else.",
7
+
8
+ [
9
+ " 1. `--all` will make the command run against all servers in a role rather than the first server it comes across. " +
10
+ "Don't do this if you're modifying the database with the command.",
11
+
12
+ " 2. EX: `cft run rake routes`",
13
+
14
+ " 3. EX: `cft run ruby lib/one_time_fix.rb staging 20140214` This command can be used to run anything, not just rake tasks. " +
15
+ "It prepends bundle exec to your command for rails stack repositories",
16
+
17
+ " 4. IMPORTANT NOTE: You cannot run `cft run rake -T` as is, you have to enclose any command that uses command line dash " +
18
+ 'arguments in quotes like `cft run "rake -T"`'
19
+ ]
20
+ ]
21
+ end
22
+ end
23
+
24
+ class Action
25
+ def run
26
+ self.send("run_#{ @config['getter'].get_current_stack }")
27
+ end
28
+
29
+ def run_ruby_on_rails
30
+ nodes = @config['getter'].get_true_node_objects
31
+ command = @config['parser'].parse_runtime_arguments 0, 'range'
32
+
33
+ #must have rails stack to run migrations and not be a db, only want ONE node
34
+ nodes = @config['parser'].exclude_nodes( nodes, [{ unless: "role[#{ @options['role'] }]" }], !@options['run_on_all'] )
35
+
36
+ #this must always precede on () calls so they have the instance variables they need
37
+ options, locs, ridley, logs_bag_hash, pass_bag_hash, bundle_command, cheftacular, passwords = @config['helper'].set_local_instance_vars
38
+
39
+ on ( nodes.map { |n| "deploy@" + n.public_ipaddress } ) do |host|
40
+ n = get_node_from_address(nodes, host.hostname)
41
+
42
+ puts("Beginning task run for #{ n.name } (#{ n.public_ipaddress }) on role #{ options['role'] }") unless options['quiet']
43
+
44
+ log_data, timestamp = start_task( n.name, n.public_ipaddress, n.run_list, "#{ bundle_command } exec #{ command }", options, locs, cheftacular)
45
+
46
+ logs_bag_hash["#{ n.name }-run"] = { text: log_data.scrub_pretty_text, timestamp: timestamp }
47
+ end
48
+
49
+ @config['ChefDataBag'].save_logs_bag
50
+ end
51
+
52
+ def run_nodejs
53
+ raise "Not yet implemented"
54
+ end
55
+
56
+ def run_wordpress
57
+ raise "Not yet implemented"
58
+ end
59
+
60
+ def run_all
61
+ raise "You attempted to run a command for the all role, this is not possible."
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,94 @@
1
+
2
+ # all in one method that will interact with cloud interactor to bring up the server and obtain its initial root pass then run ubuntu bootstrap and chef_bootstrap
3
+ class Cheftacular
4
+ class ActionDocumentation
5
+ def scale
6
+ @config['documentation']['action'] << [
7
+ "`cft scale up|down [NUM_TO_SCALE]` will add (or remove) NUM_TO_SCALE servers from the server array. " +
8
+ "This command will not let you scale down below 1 server.",
9
+
10
+ [
11
+ " 1. In the case of server creation, this command takes a great deal of time to execute. " +
12
+ "It will output what stage it is currently on to the terminal but <b>you must not kill this command while it is executing</b>." +
13
+ "A failed build may require the server to be destroyed / examined by a DevOps engineer."
14
+ ]
15
+ ]
16
+ end
17
+ end
18
+
19
+ class Action
20
+ def scale type="up", num_to_scale=1
21
+ type = ARGV[1] if ARGV[1]
22
+ num_to_scale = ARGV[2] if ARGV[2]
23
+
24
+ raise "Unknown type for scaling: #{ type }" unless (type =~ /up|down/) == 0
25
+ raise "Unknown scaling: #{ num_to_scale }" unless num_to_scale.is_a?(Fixnum) && num_to_scale >= 1
26
+
27
+ nodes = @config['getter'].get_true_node_objects
28
+
29
+ base_node_names = {}
30
+
31
+ nodes = @config['parser'].exclude_nodes( nodes, [{ unless: 'role[scalable]' }] )
32
+
33
+ nodes.each do |n|
34
+ #names are stored alphabetically so this will always put the result hash in the form of {base_name => highest N}
35
+ base_node_names[n.name.gsub(/\d/,'')] ||= {}
36
+ base_node_names[n.name.gsub(/\d/,'')][n.name] = n.name.gsub(/[^\d]/,'')
37
+
38
+ if base_node_names[n.name.gsub(/\d/,'')][n.name] > base_node_names[n.name.gsub(/\d/,'')]['highest_val'] || base_node_names[n.name.gsub(/\d/,'')]['highest_val'].nil?
39
+ base_node_names[n.name.gsub(/\d/,'')]['highest_val'] = n.name.gsub(/[^\d]/,'').to_i
40
+ end
41
+ end
42
+
43
+ base_node_names.each_pair do |base_name, nodes_under_name_hash|
44
+ raise "Cannot scale lower than 1" if (nodes_under_name_hash.keys-1).count <= num_to_scale && type == 'down'
45
+ end
46
+
47
+ if base_node_names.empty?
48
+ puts("There are no nodes for #{ @options['role'] } in env #{ @options['env'] } that have scaling enabled.") unless options['quiet']
49
+ end
50
+
51
+ @options['force_yes'] = true
52
+ @options['in_scaling'] = true
53
+
54
+ scaling_node_defaults = @config['cheftacular']['scaling_nodes']
55
+
56
+ (1..num_to_scale).each do |i|
57
+ case type
58
+ when 'up'
59
+ base_node_names.each_pair do |base_name, nodes_under_name_hash|
60
+ @options['node_name'] = base_name + ( node_under_name_hash['highest_val'] + i ).to_s.rjust(2, '0')
61
+
62
+ if scaling_node_defaults.has_key?(base_name)
63
+ @options['flavor_name'] = scaling_node_defaults[base_name].has_key?('flavor') ? scaling_node_defaults[base_name]['flavor'] : @config['cheftacular']['default_flavor_name']
64
+ @options['descriptor'] = scaling_node_defaults[base_name].has_key?('descriptor') ? scaling_node_defaults[base_name]['descriptor'] : @options['node_name']
65
+ else
66
+ @options['flavor_name'] = @config['cheftacular']['default_flavor_name']
67
+ @options['descriptor'] = @options['node_name']
68
+ end
69
+
70
+ puts("Preparing to scale #{ type } server #{ @options['node_name'] } on role #{ @options['role'] }!") unless @options['quiet']
71
+
72
+ @config['stateless_action'].cloud_bootstrap
73
+ end
74
+ when 'down'
75
+ base_node_names.each_pair do |base_name, nodes_under_name_hash|
76
+ @options['node_name'] = base_name + ( node_under_name_hash['highest_val'] + i ).to_s.rjust(2, '0')
77
+
78
+ puts("Preparing to scale #{ type } server #{ @options['node_name'] } on role #{ @options['role'] }!") unless @options['quiet']
79
+
80
+ remove_client true
81
+ end
82
+ end
83
+
84
+ sleep 15 if num_to_scale > 1
85
+ end
86
+
87
+ @config['ChefDataBag'].save_server_passwords_bag #we must save the auth bag here and not in the individual rax_bootstrap runs so they don't corrupt the bags
88
+
89
+ @options['node_name'] = nil #if this is not nil deploy_role will try to deploy to a single server instead of the group
90
+
91
+ @config['action'].deploy
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,55 @@
1
+ class Cheftacular
2
+ class ActionDocumentation
3
+ def tail
4
+ @config['documentation']['action'] << [
5
+ "`cft tail` will tail the logs (return continuous output) of the first node if finds " +
6
+ "that has an application matching the repository running on it. Currently only supports rails stacks",
7
+
8
+ [
9
+ " 1. pass `-n NODE_NAME` to grab the output of a node other than the first.",
10
+
11
+ " 2. Workers and job servers change the output of this command heavily. " +
12
+ "Worker and job servers should tail their log to the master log (/var/log/syslog) where <b>all</b> of the major processes on the server output to. " +
13
+ "While the vast majority of this syslog will be relevant to application developers, some will not (usually firewall blocks and the like)."
14
+ ]
15
+ ]
16
+ end
17
+ end
18
+
19
+ class Action
20
+ #TODO ARG FOR TAILING ANY LOG FILE
21
+ def tail
22
+ nodes = @config['getter'].get_true_node_objects
23
+
24
+ nodes = @config['parser'].exclude_nodes( nodes, [{ unless: "role[#{ @options['role'] }]" }], true )
25
+
26
+ nodes.each do |n|
27
+ puts("Beginning tail run for #{ n.name } (#{ n.public_ipaddress }) on role #{ @options['role'] }") unless @options['quiet']
28
+
29
+
30
+ if @config['getter'].get_current_stack.nil?
31
+ start_tail_role_map( n.public_ipaddress )
32
+ else
33
+ self.send("start_tail_#{ @config['getter'].get_current_stack }", n.public_ipaddress, n.run_list )
34
+ end
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def start_tail_role_map ip_address
41
+ log_loc = @config['getter'].get_current_role_map['log_location'].split(',').first
42
+
43
+ `ssh -oStrictHostKeyChecking=no -tt deploy@#{ ip_address } "#{ @config['helper'].sudo(ip_address) } tail -f #{ log_loc }" > /dev/tty`
44
+ end
45
+
46
+ def start_tail_ruby_on_rails ip_address, run_list
47
+ true_env = @config['dummy_sshkit'].get_true_environment run_list, @config['cheftacular']['run_list_environments'], @options['env']
48
+
49
+ #special servers should be listed first as most of them will have web role
50
+ log_loc = "#{ @config['cheftacular']['base_file_path'] }/#{ @options['repository'] }/current/log/#{ true_env }.log"
51
+
52
+ `ssh -oStrictHostKeyChecking=no -tt deploy@#{ ip_address } "#{ @config['helper'].sudo(ip_address) } tail -f #{ log_loc }" > /dev/tty`
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,14 @@
1
+
2
+ class Cheftacular
3
+ class ActionDocumentation
4
+ def initialize options, config
5
+ @options, @config = options, config
6
+ end
7
+ end
8
+
9
+ class Action
10
+ def initialize options, config
11
+ @options, @config = options, config
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,46 @@
1
+
2
+ class Cheftacular
3
+ class Auditor
4
+ def initialize options, config
5
+ @options, @config = options, config
6
+ end
7
+
8
+ def audit_run
9
+ current_day = Time.now.strftime('%Y%m%d')
10
+ current_time = Time.now.strftime('%H:%M')
11
+
12
+ @config[@options['env']]['audit_bag_hash']['audit_log'][current_day] ||= {}
13
+ @config[@options['env']]['audit_bag_hash']['audit_log'][current_day][current_time] ||= []
14
+ @config[@options['env']]['audit_bag_hash']['audit_log'][current_day][current_time] << read_audit_cache_file_to_hash
15
+
16
+ @config['ChefDataBag'].save_audit_bag
17
+ end
18
+
19
+ def write_audit_cache_file
20
+ File.open( @config['helper'].current_audit_file_path, "w") { |f| f.write( fetch_audit_data_hash ) }
21
+ end
22
+
23
+ def read_audit_cache_file_to_hash ret_hash={}
24
+ ret_hash = Hash.class_eval( File.read( @config['helper'].current_audit_file_path ))
25
+ ret_hash['command'] = @options['command']
26
+ ret_hash['options'] = @options.except(:preferred_cloud, :preferred_cloud_region, :preferred_cloud_image) #TODO load preferred_X options if they are not the default?
27
+ ret_hash['arguments'] = ARGV[1..ARGV.length]
28
+
29
+ ret_hash
30
+ end
31
+
32
+ def fetch_audit_data_hash ret_hash={}, ip=""
33
+ Timeout::timeout(10) do
34
+ response = Net::HTTP.get URI.parse('http://checkip.dyndns.org')
35
+ ip = response.match( /(?:Address: )([\d\.]+)/ )[1]
36
+ end
37
+
38
+ ret_hash['hostname'] = Socket.gethostname
39
+ ret_hash['true_ip'] = ip
40
+
41
+ ret_hash
42
+ rescue StandardError => exception
43
+ @config['helper'].exception_output "Unable to finish parsing auditing hash", exception
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,104 @@
1
+
2
+ class Cheftacular
3
+ class ChefDataBag
4
+ def initialize options={}, config={}
5
+ @options, @config = options, config
6
+ end
7
+
8
+ #this will only intialize bags (and their hashes) if they don't exist. Use ridley data bag methods to reload the data etc
9
+ def init_bag bag_env, bag_name, encrypted=true
10
+ self.instance_eval("@config['ridley'].data_bag.create(name: '#{ bag_env }')") if self.instance_eval("@config['ridley'].data_bag.find('#{ bag_env }').nil?")
11
+
12
+ if self.instance_eval("@config['ridley'].data_bag.find('#{ bag_env }').item.find('#{ bag_name }').nil?")
13
+ self.instance_eval("@config['ridley'].data_bag.find('#{ bag_env }').item.create(id: '#{ bag_name }')")
14
+ end
15
+
16
+ @config[bag_env] ||= {}
17
+
18
+ if !@config[bag_env].has_key?("#{ bag_name }_bag") || !@config[bag_env].has_key?("#{ bag_name }_bag_hash")
19
+ self.instance_eval "@config['#{ bag_env }']['#{ bag_name }_bag'] ||= @config['ridley'].data_bag.find('#{ bag_env }').item.find('#{ bag_name }')"
20
+
21
+ self.instance_eval "@config['#{ bag_env }']['#{ bag_name }_bag_hash'] ||= @config['#{ bag_env }']['#{ bag_name }_bag']#{ encrypted ? '.decrypt' : '' }.to_hash"
22
+ end
23
+ end
24
+
25
+ def save_logs_bag bag_env="options"
26
+ env = bag_env == 'options' ? @options['env'] : bag_env
27
+
28
+ item = @config[env]['logs_bag'].reload
29
+
30
+ #TODO use zlib gem to store and display logs https://stackoverflow.com/questions/17882463/compressing-large-string-in-ruby
31
+ item.attributes = item.attributes.deep_merge(@config[env]['logs_bag_hash'].dup)
32
+
33
+ begin
34
+ item.save
35
+ rescue Ridley::Errors::HTTPRequestEntityTooLarge => e
36
+ puts "WARNING! #{ e }! The logs from this run will not be saved on the chef server. Wiping the bag so future runs can be saved."
37
+
38
+ item.update(id: 'logs')
39
+
40
+ @config[env]['logs_bag_hash'] = @config[env]['logs_bag'].to_hash
41
+ end
42
+ end
43
+
44
+ #TODO special save for bag that will compile the data into a different bag for storage (the data will be stored as an audit log and zlib'd)
45
+ def save_audit_bag bag_env="options"
46
+ env = bag_env == 'options' ? @options['env'] : bag_env
47
+
48
+ save_bag 'audit', bag_env, @config[env]['audit_bag'], @config[env]['audit_bag_hash']
49
+ end
50
+
51
+ def save_authentication_bag bag_env="default"
52
+ save_bag 'authentication', bag_env, @config['default']['authentication_bag'], @config['default']['authentication_bag_hash']
53
+ end
54
+
55
+ def save_chef_passwords_bag bag_env="options"
56
+ env = bag_env == 'options' ? @options['env'] : bag_env
57
+
58
+ save_bag 'chef_passwords', bag_env, @config[env]['chef_passwords_bag'], @config[env]['chef_passwords_bag_hash'], true
59
+ end
60
+
61
+ def save_server_passwords_bag bag_env="options"
62
+ env = bag_env == 'options' ? @options['env'] : bag_env
63
+
64
+ save_bag 'server_passwords', bag_env, @config[env]['server_passwords_bag'], @config[env]['server_passwords_bag_hash'], true
65
+ end
66
+
67
+ def save_addresses_bag bag_env="options"
68
+ env = bag_env == 'options' ? @options['env'] : bag_env
69
+
70
+ save_bag 'addresses', bag_env, @config[env]['addresses_bag'], @config[env]['addresses_bag_hash']
71
+ end
72
+
73
+ def save_config_bag bag_env="options"
74
+ env = bag_env == 'options' ? @options['env'] : bag_env
75
+
76
+ save_bag 'config', bag_env, @config[env]['config_bag'], @config[env]['config_bag_hash']
77
+ end
78
+
79
+ def save_node_roles_bag bag_env="options"
80
+ env = bag_env == 'options' ? @options['env'] : bag_env
81
+
82
+ save_bag 'node_roles', bag_env, @config[env]['node_roles_bag'], @config[env]['node_roles_bag_hash']
83
+ end
84
+
85
+ private
86
+ def save_bag bag_name, bag_env, bag, bag_hash, encrypted=false
87
+ return true if @config['helper'].running_on_chef_node?
88
+
89
+ new_bag_hash = bag_hash.deep_dup
90
+
91
+ item = bag.reload
92
+
93
+ load_hash = encrypted ? item.decrypt.to_hash.deep_merge(new_bag_hash) : item.attributes.deep_merge(new_bag_hash)
94
+
95
+ item.attributes = encrypted ? @config['encryptor'].return_encrypted_hash(load_hash) : load_hash
96
+
97
+ item.save
98
+ rescue Ridley::Errors::HTTPRequestEntityTooLarge => e
99
+ msg = "FATAL! Bag #{ bag_name } in environment bag #{ bag_env } was not able to be saved because it has grown too large! This bag must cleaned up ASAP!"
100
+
101
+ @config['helper'].exception_output msg, e
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,55 @@
1
+ require 'ridley' #chef tools outside of chef!
2
+ require 'highline/import'
3
+ require 'optparse'
4
+ require 'base64'
5
+ require 'openssl'
6
+ require 'ffi_yajl'
7
+ require 'sshkit'
8
+ require 'sshkit/dsl' #yes you have to do it this way
9
+ require 'awesome_print'
10
+ require 'rest-client'
11
+ require 'active_support'
12
+ require 'active_support/inflector'
13
+ require 'active_support/core_ext'
14
+ require 'public_suffix'
15
+ require 'yaml'
16
+ require 'json'
17
+ require 'rbconfig'
18
+ require 'fog'
19
+ require 'socket'
20
+ require 'net/http'
21
+ require 'timeout'
22
+
23
+ Dir["#{File.dirname(__FILE__)}/../**/*.rb"].each { |f| require f }
24
+
25
+ class Cheftacular
26
+ def initialize options={'env'=>'staging'}, config={}
27
+ @options, @config = options, config
28
+
29
+ @config['start_time'] = Time.now
30
+
31
+ @config['helper'] = Helper.new(@options, @config)
32
+
33
+ @config['initializer'] = Initializer.new(@options, @config)
34
+
35
+ @config['stateless_action'].initialize_data_bag_contents @options['env'] #ensure basic structure are always maintained before each run
36
+
37
+ @config['parser'].parse_application_context if @config['cheftacular']['mode'] == 'application'
38
+
39
+ @config['parser'].parse_context
40
+
41
+ puts("Preparing to run command \"#{ @options['command'] }\"...") if @options['verbose']
42
+
43
+ @config['auditor'].audit_run if @config['cheftacular']['auditing'] == 'true'
44
+
45
+ @config['action'].send(@options['command']) if @config['helper'].is_command?(@options['command'])
46
+
47
+ @config['stateless_action'].send(@options['command']) if @config['helper'].is_stateless_command?(@options['command'])
48
+
49
+ @config['stateless_action'].send('help') if @config['helper'].is_not_command_or_stateless_command?(@options['command'])
50
+
51
+ @config['helper'].output_run_stats
52
+
53
+ exit #explicitly call this in case some celluoid workers are still hanging around
54
+ end
55
+ end
@@ -0,0 +1,45 @@
1
+
2
+ class Cheftacular
3
+ class Decryptor
4
+ ALGORITHM = 'aes-256-cbc'
5
+
6
+ def initialize data_bag_secret
7
+ @data_bag_secret = data_bag_secret
8
+ end
9
+
10
+ def return_decrypted_hash input_hash, return_hash={}
11
+ input_hash.each_pair do |key, value_hash|
12
+ next if key =~ /id/
13
+ next if value_hash['iv'].nil? || value_hash['iv'].empty?
14
+
15
+ return_hash[key] = JSON.parse(decrypt_hash(value_hash)).to_hash["json_wrapper"]
16
+ end
17
+
18
+ return_hash['id'] = input_hash['id']
19
+
20
+ return_hash
21
+ end
22
+
23
+ def openssl_decryptor
24
+ openssl_decryptor = begin
25
+ decryptor = OpenSSL::Cipher::Cipher.new(ALGORITHM)
26
+ decryptor.decrypt
27
+ decryptor.key = Digest::SHA256.digest(@data_bag_secret)
28
+ decryptor.iv = @iv
29
+ decryptor
30
+ end
31
+ end
32
+
33
+ def decrypt_hash hash
34
+ @iv = Base64.decode64(hash["iv"])
35
+
36
+ decryptor = openssl_decryptor
37
+
38
+ decrypted_data = decryptor.update(Base64.decode64(hash["encrypted_data"]))
39
+
40
+ decrypted_data << decryptor.final
41
+
42
+ decrypted_data
43
+ end
44
+ end
45
+ end