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