bcome 0.0.3
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/bcome +99 -0
- data/bin/bcome-setup +43 -0
- data/documentation/configuration.md +71 -0
- data/documentation/examples/network.yml-example +57 -0
- data/documentation/examples/platform.yml-example +18 -0
- data/documentation/installation.md +35 -0
- data/documentation/usage.md +181 -0
- data/filters/ec2_filter.rb +12 -0
- data/lib/bcome.rb +9 -0
- data/lib/bcome/version.rb +3 -0
- data/lib/become_object.rb +69 -0
- data/lib/boot.rb +25 -0
- data/lib/command.rb +45 -0
- data/lib/filters/base.rb +10 -0
- data/lib/filters/ec2_filter.rb +4 -0
- data/lib/helpers/command_helper.rb +9 -0
- data/lib/helpers/environment_ssh.rb +44 -0
- data/lib/helpers/fog_helper.rb +54 -0
- data/lib/helpers/instance_command.rb +27 -0
- data/lib/helpers/instance_ssh.rb +66 -0
- data/lib/helpers/selections.rb +120 -0
- data/lib/object.rb +72 -0
- data/lib/patches/string.rb +43 -0
- data/lib/render_irb.rb +34 -0
- data/lib/scp.rb +29 -0
- data/lib/ssh.rb +49 -0
- data/lib/stack/environment.rb +117 -0
- data/lib/stack/instance.rb +92 -0
- data/lib/stack/platform.rb +62 -0
- data/lib/workspace_context.rb +36 -0
- data/patches/irb.rb +17 -0
- metadata +157 -0
data/lib/bcome.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
module ::Bcome::BecomeObject
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
CONFIGS_PATH = "bcome/config"
|
6
|
+
|
7
|
+
def identifier
|
8
|
+
raise "Missing method 'identifier' - this should uniquely identify your workspace context object."
|
9
|
+
end
|
10
|
+
|
11
|
+
def become_identifier
|
12
|
+
"#{previous_workspace_object.send(:become_identifier)}> #{identifier}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def previous_workspace_object=(object)
|
16
|
+
@previous_workspace_object = object
|
17
|
+
end
|
18
|
+
|
19
|
+
def previous_workspace_object
|
20
|
+
@previous_workspace_object
|
21
|
+
end
|
22
|
+
|
23
|
+
def main_context=(main_context)
|
24
|
+
@main_context = main_context
|
25
|
+
end
|
26
|
+
|
27
|
+
def main_context
|
28
|
+
@main_context
|
29
|
+
end
|
30
|
+
|
31
|
+
def has_main_context?
|
32
|
+
!@main_context.nil?
|
33
|
+
end
|
34
|
+
|
35
|
+
def resources
|
36
|
+
@resources ||= do_load_resources
|
37
|
+
end
|
38
|
+
|
39
|
+
def do_load_resources
|
40
|
+
config = YAML.load_file(config_path)
|
41
|
+
return collection_klass.collection_from_config(self, config)
|
42
|
+
end
|
43
|
+
|
44
|
+
def resource_for_identifier(identifier)
|
45
|
+
return nil unless resources
|
46
|
+
matches = resources.select{|resource| resource.identifier.to_sym == identifier.to_sym }
|
47
|
+
raise "Retrieved more than one match for #{collection_key} '#{identifier}'. Selection is ambiguous" if matches.size > 1
|
48
|
+
return matches.first
|
49
|
+
end
|
50
|
+
|
51
|
+
def all_items
|
52
|
+
return [] unless resources
|
53
|
+
resources.collect(&:identifier)
|
54
|
+
end
|
55
|
+
|
56
|
+
def responds_to_list?
|
57
|
+
true
|
58
|
+
end
|
59
|
+
|
60
|
+
def list
|
61
|
+
if responds_to_list?
|
62
|
+
::RENDER.list_items(collection_key, resources)
|
63
|
+
else
|
64
|
+
puts "\nNo list function at this level".headsup
|
65
|
+
end
|
66
|
+
end
|
67
|
+
alias :ls :list
|
68
|
+
|
69
|
+
end
|
data/lib/boot.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
class ::Bcome::Boot
|
2
|
+
|
3
|
+
include ::Bcome::BecomeObject
|
4
|
+
|
5
|
+
def config_path
|
6
|
+
"#{CONFIGS_PATH}/platform.yml"
|
7
|
+
end
|
8
|
+
|
9
|
+
def collection_klass
|
10
|
+
::PLATFORM_STACK_KLASS
|
11
|
+
end
|
12
|
+
|
13
|
+
def collection_key
|
14
|
+
:platforms
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
"Bootup"
|
19
|
+
end
|
20
|
+
|
21
|
+
def become_identifier
|
22
|
+
::START_PROMPT
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
data/lib/command.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
class ::Bcome::Command
|
2
|
+
|
3
|
+
# Wraps a command. Convenient flex-point for logging etc
|
4
|
+
|
5
|
+
attr_reader :raw_command, :host_node, :stdout, :stderr, :exit_code, :exit_signal
|
6
|
+
|
7
|
+
def initialize(raw_command, host_node)
|
8
|
+
@raw_command = raw_command
|
9
|
+
@host_node = host_node
|
10
|
+
@stdout = ""
|
11
|
+
@stderr = ""
|
12
|
+
@exit_code = nil
|
13
|
+
@exit_signal = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def pretty_result
|
17
|
+
return is_success? ? "Success".success : "Failed".failure
|
18
|
+
end
|
19
|
+
|
20
|
+
def output
|
21
|
+
command_output = is_success? ? @stdout : "Exit code: #{@exit_code}\n\nSTDERR: #{@stderr}"
|
22
|
+
return "\n#{command_output}\n"
|
23
|
+
end
|
24
|
+
|
25
|
+
def is_success?
|
26
|
+
exit_code == 0
|
27
|
+
end
|
28
|
+
|
29
|
+
def stdout=(data)
|
30
|
+
@stdout = data #data.output
|
31
|
+
end
|
32
|
+
|
33
|
+
def stderr=(data)
|
34
|
+
@stderr = data #data.output
|
35
|
+
end
|
36
|
+
|
37
|
+
def exit_code=(data)
|
38
|
+
@exit_code = data
|
39
|
+
end
|
40
|
+
|
41
|
+
def exit_signal(data)
|
42
|
+
@exit_signal = data
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
data/lib/filters/base.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
module ::Bcome::EnvironmentSSH
|
2
|
+
|
3
|
+
def proxy
|
4
|
+
return nil unless ssh_mode_type == ::SSH_JUMP_MODE_IDENTIFIER
|
5
|
+
return Net::SSH::Proxy::Command.new(proxy_command)
|
6
|
+
end
|
7
|
+
|
8
|
+
def proxy_command
|
9
|
+
return "ssh -W %h:%p #{bastion_ip_address}"
|
10
|
+
end
|
11
|
+
|
12
|
+
def execute_command(commands, node)
|
13
|
+
begin
|
14
|
+
return execute_cmd(commands, node, proxy)
|
15
|
+
rescue Net::SSH::AuthenticationFailed
|
16
|
+
raise "Could not authenticate connection to #{node.identifier}".failure
|
17
|
+
rescue Net::SSH::Disconnect
|
18
|
+
raise "SSH connection to #{node.identifier} was disconnected".failure
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def execute_cmd(raw_commands, node, proxy)
|
23
|
+
commands = raw_commands.collect{|raw_command|
|
24
|
+
raw_command.is_a?(::Bcome::Command) ? raw_command : ::Bcome::Command.new(raw_command, node)
|
25
|
+
}
|
26
|
+
|
27
|
+
ssh = ::Bcome::Ssh.new(commands, proxy, node)
|
28
|
+
ssh.execute!
|
29
|
+
return
|
30
|
+
end
|
31
|
+
|
32
|
+
def execute_scp_upload(files, remote_path, node)
|
33
|
+
begin
|
34
|
+
scp = ::Bcome::Scp.new(files, remote_path, proxy, node)
|
35
|
+
scp.upload!
|
36
|
+
rescue Net::SSH::AuthenticationFailed
|
37
|
+
raise "Could not authenticate connection to #{node.identifier}".failure
|
38
|
+
rescue Net::SSH::Disconnect
|
39
|
+
raise "SSH connection to #{node.identifier} was disconnected".failure
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module ::Bcome::FogHelper
|
2
|
+
|
3
|
+
def credentials_key
|
4
|
+
network_lookup[:credentials_key]
|
5
|
+
end
|
6
|
+
|
7
|
+
def fog_client
|
8
|
+
@fog_client ||= get_fog_client
|
9
|
+
end
|
10
|
+
|
11
|
+
def unfiltered_servers
|
12
|
+
@unfiltered_servers ||= fog_client.servers.all(network_lookup[:ec2_server_lookup_filters])
|
13
|
+
end
|
14
|
+
|
15
|
+
def ec2_tags
|
16
|
+
network_lookup[:ec2_tag_filter]
|
17
|
+
end
|
18
|
+
|
19
|
+
def servers
|
20
|
+
all = unfiltered_servers
|
21
|
+
filtered_by_tags = ec2_tags ? filter_by_tags(all) : all
|
22
|
+
|
23
|
+
custom_filter_method = @meta_data[:network_lookup][:custom_post_process_filter_method]
|
24
|
+
|
25
|
+
if custom_filter_method
|
26
|
+
filter = ::Bcome::Filter::Ec2Filter.new(filtered_by_tags, self)
|
27
|
+
raise "Custome filter method #{custom_filter_method} is missing. Make sure you've added it".failure unless filter.respond_to?(custom_filter_method.to_sym)
|
28
|
+
return filter.send(custom_filter_method.to_sym)
|
29
|
+
else
|
30
|
+
return filtered_by_tags
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def filter_by_tags(instances)
|
35
|
+
return instances.select{|instance| instance_matches_tags?(instance)}
|
36
|
+
end
|
37
|
+
|
38
|
+
def instance_matches_tags?(instance)
|
39
|
+
tags = instance.tags
|
40
|
+
return ec2_tags.select{|key, value| tags[key.to_s] == value }.size == ec2_tags.keys.size
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def get_fog_client
|
46
|
+
::Fog.credential = credentials_key
|
47
|
+
client = ::Fog::Compute.new(
|
48
|
+
:provider => "AWS",
|
49
|
+
:region => network_lookup[:provisioning_region]
|
50
|
+
)
|
51
|
+
return client
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module ::Bcome::InstanceCommand
|
2
|
+
|
3
|
+
def run(raw_commands)
|
4
|
+
commands = raw_commands.is_a?(String) ? [raw_commands] : raw_commands
|
5
|
+
execute_command(commands)
|
6
|
+
end
|
7
|
+
|
8
|
+
def execute_command(commands)
|
9
|
+
@environment.execute_command(commands, self)
|
10
|
+
end
|
11
|
+
|
12
|
+
def rsync(local_path, remote_path)
|
13
|
+
puts "rsync> #{self.identifier}".informational
|
14
|
+
if @environment.ssh_mode_type == ::SSH_DIRECT_MODE_IDENTIFIER
|
15
|
+
rsync_command = "rsync -av #{local_path} #{self.ssh_user}@#{self.ip_address}:#{remote_path}"
|
16
|
+
else
|
17
|
+
rsync_command = "rsync -av -e \"ssh -A #{self.ssh_user}@#{@environment.bastion_ip_address} ssh -o StrictHostKeyChecking=no\" #{local_path} #{self.ssh_user}@#{self.ip_address}:#{remote_path}"
|
18
|
+
end
|
19
|
+
run_local(rsync_command)
|
20
|
+
end
|
21
|
+
|
22
|
+
def scp(files, remote_path)
|
23
|
+
@environment.execute_scp_upload(files, remote_path, self)
|
24
|
+
return
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module ::Bcome::InstanceSsh
|
2
|
+
|
3
|
+
def execute_task(task_name, *args)
|
4
|
+
begin
|
5
|
+
public_send(task_name, *args)
|
6
|
+
rescue ::Seahorse::Client::NetworkingError
|
7
|
+
puts "Could not execute '#{task_name}'. Cannot initiate connection - check your network connection and try again.".failure
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def ssh
|
12
|
+
execute_task(:do_ssh)
|
13
|
+
end
|
14
|
+
|
15
|
+
def do_ssh(command)
|
16
|
+
result = execute_task(:run_local, command)
|
17
|
+
raise "Unable to execute result" unless result # TODO node has not been bootstrapped most likely. Implement a fallback
|
18
|
+
return result
|
19
|
+
end
|
20
|
+
|
21
|
+
def ssh_jump_command_to_bastion
|
22
|
+
command = "ssh -o UserKnownHostsFile=/dev/null -i #{ssh_key_path} #{ssh_user}@#{bastion_ip_address}"
|
23
|
+
end
|
24
|
+
|
25
|
+
def ssh_jump_command_to_internal_node
|
26
|
+
command = "ssh -o UserKnownHostsFile=/dev/null -i #{ssh_key_path} -o \"ProxyCommand ssh -W %h:%p -i #{ssh_key_path} #{ssh_user}@#{bastion_ip_address}\" #{ssh_user}@#{ip_address}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def direct_ssh_command
|
30
|
+
command = "ssh #{environment.ssh_mode_user}@#{public_ip_address}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def ssh_user
|
34
|
+
# defined by the environment or we fall back to the local user
|
35
|
+
return @environment.ssh_mode_user ? @environment.ssh_mode_user : `whoami`.gsub("\n","")
|
36
|
+
end
|
37
|
+
|
38
|
+
def ssh_key_path
|
39
|
+
"~/.ssh/id_rsa"
|
40
|
+
end
|
41
|
+
|
42
|
+
def keys
|
43
|
+
[ssh_key_path]
|
44
|
+
end
|
45
|
+
|
46
|
+
def bastion_ip_address
|
47
|
+
return @environment.bastion_ip_address
|
48
|
+
end
|
49
|
+
|
50
|
+
def is_direct_ssh?
|
51
|
+
environment.ssh_mode_type == ::SSH_DIRECT_MODE_IDENTIFIER
|
52
|
+
end
|
53
|
+
|
54
|
+
def is_jump_ssh?
|
55
|
+
environment.ssh_mode_type == ::SSH_JUMP_MODE_IDENTIFIER
|
56
|
+
end
|
57
|
+
|
58
|
+
def ssh
|
59
|
+
is_jump_ssh? ? (is_ssh_jump_host? ? do_ssh(ssh_jump_command_to_bastion) : do_ssh(ssh_jump_command_to_internal_node)) : do_ssh(direct_ssh_command)
|
60
|
+
end
|
61
|
+
|
62
|
+
def is_ssh_jump_host?
|
63
|
+
@meta_data["role"] == ::SSH_JUMP_HOST_ROLE_IDENTIFIER
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module ::Bcome::Selections
|
2
|
+
|
3
|
+
def manage_object(method, resource_identifier = nil)
|
4
|
+
unless resource_identifier && resource_identifier.is_a?(String)
|
5
|
+
print "You must select a resource identified by its name. #{available_resources_options_string}".headsup
|
6
|
+
else
|
7
|
+
object = find_resource_by_identifier(resource_identifier)
|
8
|
+
unless object
|
9
|
+
print "Cannot find object with identifier #{resource_identifier} within the resources for this collection. #{available_resources_options_string}".failure unless object
|
10
|
+
else
|
11
|
+
send(method, object)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
## Runs commands over *every* object in the selection
|
17
|
+
def run(raw_commands)
|
18
|
+
return unless @objects
|
19
|
+
@objects.each do |object|
|
20
|
+
object.run(raw_commands)
|
21
|
+
end
|
22
|
+
return
|
23
|
+
end
|
24
|
+
|
25
|
+
def rsync(local_path, remote_path)
|
26
|
+
if !@objects || @objects.empty?
|
27
|
+
no_selections_error
|
28
|
+
return
|
29
|
+
end
|
30
|
+
@objects.each do |object|
|
31
|
+
object.rsync(local_path, remote_path)
|
32
|
+
end
|
33
|
+
return
|
34
|
+
end
|
35
|
+
|
36
|
+
def scp(files, remote_path)
|
37
|
+
if !@objects || @objects.empty?
|
38
|
+
no_selections_error
|
39
|
+
return
|
40
|
+
end
|
41
|
+
|
42
|
+
return unless @objects
|
43
|
+
@objects.each do |object|
|
44
|
+
object.scp(files, remote_path)
|
45
|
+
end
|
46
|
+
return
|
47
|
+
end
|
48
|
+
|
49
|
+
def add(resource_identifier = nil)
|
50
|
+
if resource_identifier.is_a?(Array)
|
51
|
+
resource_identifier.each do |ri|
|
52
|
+
manage_object(:add_object, ri)
|
53
|
+
end
|
54
|
+
else
|
55
|
+
manage_object(:add_object, resource_identifier)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def remove(resource_identifier = nil)
|
60
|
+
if resource_identifier.is_a?(Array)
|
61
|
+
resource_identifier.each do |ri|
|
62
|
+
manage_object(:remove_object, ri)
|
63
|
+
end
|
64
|
+
else
|
65
|
+
manage_object(:remove_object, resource_identifier)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def add!
|
70
|
+
@objects = resources
|
71
|
+
puts "All nodes added".informational
|
72
|
+
end
|
73
|
+
|
74
|
+
def clear!
|
75
|
+
@objects = []
|
76
|
+
selections
|
77
|
+
end
|
78
|
+
|
79
|
+
def find_resource_by_identifier(resource_identifier)
|
80
|
+
resources.select{|r| r.identifier == resource_identifier }.first
|
81
|
+
end
|
82
|
+
|
83
|
+
def in_resources?(object)
|
84
|
+
resources.include?(object)
|
85
|
+
end
|
86
|
+
|
87
|
+
def add_object(object)
|
88
|
+
return if object_in_selections?(object)
|
89
|
+
@objects = @objects ? (@objects << object) : [object]
|
90
|
+
return
|
91
|
+
end
|
92
|
+
|
93
|
+
def remove_object(object)
|
94
|
+
return if !@objects || @objects.empty?
|
95
|
+
unless object_in_selections?(object)
|
96
|
+
print "\n#{object.identifier} is not within selections\n".headsup
|
97
|
+
else
|
98
|
+
@objects = @objects - [object]
|
99
|
+
end
|
100
|
+
return
|
101
|
+
end
|
102
|
+
|
103
|
+
def object_in_selections?(object)
|
104
|
+
return false unless @objects
|
105
|
+
return @objects.include?(object)
|
106
|
+
end
|
107
|
+
|
108
|
+
def selections
|
109
|
+
if !@objects || @objects.empty?
|
110
|
+
print "\nNo added nodes.\n".headsup
|
111
|
+
else
|
112
|
+
print "\nAdded nodes: #{@objects.collect(&:identifier).join(", ")}\n".headsup
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def no_selections_error
|
117
|
+
puts "No nodes selected".informational
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|