cheftacular 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/cft +4 -0
- data/bin/cftclr +4 -0
- data/bin/cheftacular +4 -0
- data/bin/client-list +4 -0
- data/lib/cheftacular/README.md +416 -0
- data/lib/cheftacular/actions/check.rb +32 -0
- data/lib/cheftacular/actions/console.rb +62 -0
- data/lib/cheftacular/actions/database.rb +13 -0
- data/lib/cheftacular/actions/db_console.rb +67 -0
- data/lib/cheftacular/actions/deploy.rb +40 -0
- data/lib/cheftacular/actions/log.rb +47 -0
- data/lib/cheftacular/actions/migrate.rb +57 -0
- data/lib/cheftacular/actions/run.rb +64 -0
- data/lib/cheftacular/actions/scale.rb +94 -0
- data/lib/cheftacular/actions/tail.rb +55 -0
- data/lib/cheftacular/actions.rb +14 -0
- data/lib/cheftacular/auditor.rb +46 -0
- data/lib/cheftacular/chef/data_bag.rb +104 -0
- data/lib/cheftacular/cheftacular.rb +55 -0
- data/lib/cheftacular/decryptors.rb +45 -0
- data/lib/cheftacular/encryptors.rb +48 -0
- data/lib/cheftacular/getters.rb +153 -0
- data/lib/cheftacular/helpers.rb +296 -0
- data/lib/cheftacular/initializers.rb +451 -0
- data/lib/cheftacular/parsers.rb +199 -0
- data/lib/cheftacular/remote_helpers.rb +30 -0
- data/lib/cheftacular/stateless_action.rb +16 -0
- data/lib/cheftacular/stateless_actions/add_ssh_key_to_bag.rb +44 -0
- data/lib/cheftacular/stateless_actions/arguments.rb +68 -0
- data/lib/cheftacular/stateless_actions/backups.rb +116 -0
- data/lib/cheftacular/stateless_actions/bootstrappers/centos_bootstrap.rb +7 -0
- data/lib/cheftacular/stateless_actions/bootstrappers/coreos_bootstrap.rb +7 -0
- data/lib/cheftacular/stateless_actions/bootstrappers/fedora_bootstrap.rb +7 -0
- data/lib/cheftacular/stateless_actions/bootstrappers/redhat_bootstrap.rb +7 -0
- data/lib/cheftacular/stateless_actions/bootstrappers/ubuntu_bootstrap.rb +102 -0
- data/lib/cheftacular/stateless_actions/bootstrappers/vyatta_bootstrap.rb +7 -0
- data/lib/cheftacular/stateless_actions/chef_bootstrap.rb +40 -0
- data/lib/cheftacular/stateless_actions/chef_environment.rb +21 -0
- data/lib/cheftacular/stateless_actions/clean_cookbooks.rb +104 -0
- data/lib/cheftacular/stateless_actions/clean_sensu_plugins.rb +19 -0
- data/lib/cheftacular/stateless_actions/clean_server_passwords.rb +14 -0
- data/lib/cheftacular/stateless_actions/cleanup_log_files.rb +14 -0
- data/lib/cheftacular/stateless_actions/client_list.rb +89 -0
- data/lib/cheftacular/stateless_actions/cloud.rb +107 -0
- data/lib/cheftacular/stateless_actions/cloud_bootstrap.rb +109 -0
- data/lib/cheftacular/stateless_actions/compile_audit_log.rb +60 -0
- data/lib/cheftacular/stateless_actions/compile_readme.rb +41 -0
- data/lib/cheftacular/stateless_actions/create_git_key.rb +67 -0
- data/lib/cheftacular/stateless_actions/disk_report.rb +75 -0
- data/lib/cheftacular/stateless_actions/environment.rb +100 -0
- data/lib/cheftacular/stateless_actions/fetch_file.rb +24 -0
- data/lib/cheftacular/stateless_actions/fix_known_hosts.rb +70 -0
- data/lib/cheftacular/stateless_actions/full_bootstrap.rb +30 -0
- data/lib/cheftacular/stateless_actions/get_active_ssh_connections.rb +18 -0
- data/lib/cheftacular/stateless_actions/get_haproxy_log.rb +55 -0
- data/lib/cheftacular/stateless_actions/get_log_from_bag.rb +38 -0
- data/lib/cheftacular/stateless_actions/get_pg_pass.rb +61 -0
- data/lib/cheftacular/stateless_actions/help.rb +71 -0
- data/lib/cheftacular/stateless_actions/initialize_data_bag_contents.rb +220 -0
- data/lib/cheftacular/stateless_actions/knife_upload.rb +23 -0
- data/lib/cheftacular/stateless_actions/pass.rb +49 -0
- data/lib/cheftacular/stateless_actions/reinitialize.rb +46 -0
- data/lib/cheftacular/stateless_actions/remove_client.rb +81 -0
- data/lib/cheftacular/stateless_actions/replication_status.rb +103 -0
- data/lib/cheftacular/stateless_actions/restart_swap.rb +55 -0
- data/lib/cheftacular/stateless_actions/rvm.rb +14 -0
- data/lib/cheftacular/stateless_actions/server_update.rb +99 -0
- data/lib/cheftacular/stateless_actions/service.rb +14 -0
- data/lib/cheftacular/stateless_actions/test_env.rb +82 -0
- data/lib/cheftacular/stateless_actions/update_split_branches.rb +64 -0
- data/lib/cheftacular/stateless_actions/update_tld.rb +62 -0
- data/lib/cheftacular/stateless_actions/upload_nodes.rb +120 -0
- data/lib/cheftacular/stateless_actions/upload_roles.rb +24 -0
- data/lib/cheftacular/version.rb +5 -0
- data/lib/cheftacular.rb +4 -0
- data/lib/cloud_interactor/authentication.rb +56 -0
- data/lib/cloud_interactor/cloud_interactor.rb +23 -0
- data/lib/cloud_interactor/domain/create.rb +17 -0
- data/lib/cloud_interactor/domain/create_record.rb +27 -0
- data/lib/cloud_interactor/domain/destroy.rb +17 -0
- data/lib/cloud_interactor/domain/destroy_record.rb +23 -0
- data/lib/cloud_interactor/domain/list.rb +9 -0
- data/lib/cloud_interactor/domain/list_records.rb +22 -0
- data/lib/cloud_interactor/domain/read.rb +23 -0
- data/lib/cloud_interactor/domain/read_record.rb +27 -0
- data/lib/cloud_interactor/domain/update.rb +18 -0
- data/lib/cloud_interactor/domain/update_record.rb +42 -0
- data/lib/cloud_interactor/domain.rb +18 -0
- data/lib/cloud_interactor/flavor.rb +27 -0
- data/lib/cloud_interactor/helpers.rb +70 -0
- data/lib/cloud_interactor/image.rb +27 -0
- data/lib/cloud_interactor/parser.rb +37 -0
- data/lib/cloud_interactor/server/attach_volume.rb +33 -0
- data/lib/cloud_interactor/server/create.rb +39 -0
- data/lib/cloud_interactor/server/destroy.rb +11 -0
- data/lib/cloud_interactor/server/detach_volume.rb +21 -0
- data/lib/cloud_interactor/server/list.rb +7 -0
- data/lib/cloud_interactor/server/list_volumes.rb +25 -0
- data/lib/cloud_interactor/server/poll.rb +22 -0
- data/lib/cloud_interactor/server/read.rb +9 -0
- data/lib/cloud_interactor/server/read_volume.rb +24 -0
- data/lib/cloud_interactor/server.rb +17 -0
- data/lib/cloud_interactor/version.rb +4 -0
- data/lib/cloud_interactor/volume/create.rb +13 -0
- data/lib/cloud_interactor/volume/destroy.rb +11 -0
- data/lib/cloud_interactor/volume/list.rb +7 -0
- data/lib/cloud_interactor/volume/read.rb +9 -0
- data/lib/cloud_interactor/volume.rb +20 -0
- data/lib/ridley/monkeypatches.rb +11 -0
- data/lib/sshkit/actions/start_commit_check.rb +19 -0
- data/lib/sshkit/actions/start_deploy.rb +25 -0
- data/lib/sshkit/actions/start_log_fetch.rb +91 -0
- data/lib/sshkit/actions/start_task.rb +29 -0
- data/lib/sshkit/getters.rb +67 -0
- data/lib/sshkit/helpers.rb +13 -0
- data/lib/sshkit/monkeypatches.rb +19 -0
- metadata +375 -0
@@ -0,0 +1,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,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
|