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