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/object.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
class Object
|
2
|
+
|
3
|
+
include ::Bcome::CommandHelper
|
4
|
+
|
5
|
+
def list
|
6
|
+
BOOT.send(:list) ### Our starting point in the hierarchy... maybe this is all ultimately configurable?
|
7
|
+
end
|
8
|
+
alias :ls :list
|
9
|
+
|
10
|
+
def become_identifier
|
11
|
+
::START_PROMPT
|
12
|
+
end
|
13
|
+
|
14
|
+
def become(object)
|
15
|
+
BECOME.set(object, self)
|
16
|
+
end
|
17
|
+
|
18
|
+
def workon(identifier)
|
19
|
+
resource = resource_for_identifier(identifier)
|
20
|
+
|
21
|
+
unless resource
|
22
|
+
puts "No matching #{collection_key} for identifier '#{identifier}'. #{available_resources_options_string}".failure
|
23
|
+
else
|
24
|
+
puts "\\ \nFrom #{resource.reference_key}, working on #{identifier}\n".command
|
25
|
+
become(resource)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
alias :w :workon
|
29
|
+
|
30
|
+
def menu_items
|
31
|
+
[
|
32
|
+
{ :command => "list / ls", :description => "List all available resources at the current context." },
|
33
|
+
{ :command => "describe", :description => "Describe the resource object at the current context." },
|
34
|
+
{ :command => "workon' / w", :description => "Select a resource object, and switch to its context.", :usage => "workon 'identifier'" },
|
35
|
+
{ :command => "exit", :description => "Close the current context" },
|
36
|
+
{ :command => "exit!", :description => "Close all contexts, and exit Become."},
|
37
|
+
{ :command => "local", :description => "Execute a shell command on your local machine.", :usage => 'local "command"'}
|
38
|
+
]
|
39
|
+
end
|
40
|
+
|
41
|
+
def menu
|
42
|
+
::RENDER.menu(menu_items)
|
43
|
+
end
|
44
|
+
|
45
|
+
def highlight?
|
46
|
+
false ## override in stack objects that should be highlighted within a list, e.g. instance objects at the environment level that have been selected to workon on
|
47
|
+
end
|
48
|
+
|
49
|
+
def available_resources_options_string
|
50
|
+
"Please select from one of '#{all_items.join(', ')}'"
|
51
|
+
end
|
52
|
+
|
53
|
+
def describe
|
54
|
+
if self.respond_to?(:do_describe)
|
55
|
+
message = "\nCollection Key: #{reference_key}\n\nResource: #{self.do_describe}".colorize(:green)
|
56
|
+
else
|
57
|
+
message = "\nNothing to describe. Use 'list' to see namespace options".headsup unless self.respond_to?(:do_describe)
|
58
|
+
end
|
59
|
+
puts message
|
60
|
+
end
|
61
|
+
|
62
|
+
def set_irb_prompt(conf)
|
63
|
+
conf[:PROMPT][:CUSTOM] = {
|
64
|
+
:PROMPT_N => "\e[1m:\e[m ",
|
65
|
+
:PROMPT_I => "\e[1m#{BECOME.irb_prompt} >\e[m ",
|
66
|
+
:PROMPT_C => "\e[1m#{BECOME.irb_prompt} >\e[m ",
|
67
|
+
:RETURN => ::VERBOSE ? "%s \n" : "\n"
|
68
|
+
}
|
69
|
+
conf[:PROMPT_MODE] = :CUSTOM
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
class String
|
2
|
+
|
3
|
+
def informational
|
4
|
+
self.colorize(:white).on_green
|
5
|
+
end
|
6
|
+
|
7
|
+
def command
|
8
|
+
self.colorize(:black).on_white
|
9
|
+
end
|
10
|
+
|
11
|
+
def failure
|
12
|
+
self.colorize(:red).on_white
|
13
|
+
end
|
14
|
+
|
15
|
+
def success
|
16
|
+
self.friendly
|
17
|
+
end
|
18
|
+
|
19
|
+
def friendly
|
20
|
+
self.colorize(:black).on_green
|
21
|
+
end
|
22
|
+
|
23
|
+
def warning
|
24
|
+
self.colorize(:orange).on_black
|
25
|
+
end
|
26
|
+
|
27
|
+
def menu
|
28
|
+
self.colorize(:yellow).on_black
|
29
|
+
end
|
30
|
+
|
31
|
+
def headsup
|
32
|
+
self.colorize(:yellow).on_black
|
33
|
+
end
|
34
|
+
|
35
|
+
def output
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def bg_gray
|
40
|
+
"\033[47m#{self}\033[0m"
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
data/lib/render_irb.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
class ::Bcome::RenderIrb
|
2
|
+
|
3
|
+
def list_items(parent_collection, items)
|
4
|
+
if !items || !items.any?
|
5
|
+
print "\nNo resources collections found. Maybe you forgot to add any?".headsup
|
6
|
+
else
|
7
|
+
print "\n #{parent_collection}\n\n".colorize(:green) # are available:\n\n".colorize(:green)
|
8
|
+
items.each do |item|
|
9
|
+
colour = item.highlight? ? :yellow : :green
|
10
|
+
print "* #{item.do_describe}\n".colorize(colour)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
print "\n"
|
14
|
+
end
|
15
|
+
|
16
|
+
def menu(menu_items)
|
17
|
+
menu_str = "\n** Commands **\n\n"
|
18
|
+
menu_str = "\n\The following commands are available: \n\n"
|
19
|
+
menu_items.each do |item|
|
20
|
+
command = item[:command]
|
21
|
+
description = item[:description]
|
22
|
+
usage = item[:usage]
|
23
|
+
|
24
|
+
menu_str += "> #{command}\n"
|
25
|
+
menu_str += "- #{description}\n"
|
26
|
+
menu_str += "e.g. #{usage}\n" if usage
|
27
|
+
|
28
|
+
menu_str += "\n"
|
29
|
+
end
|
30
|
+
menu_str += "\n\n"
|
31
|
+
print menu_str.menu
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
data/lib/scp.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
class ::Bcome::Scp
|
2
|
+
|
3
|
+
def initialize(files, remote_path, proxy, node)
|
4
|
+
@files = files
|
5
|
+
@proxy = proxy
|
6
|
+
@node = node
|
7
|
+
@remote_path = remote_path
|
8
|
+
end
|
9
|
+
|
10
|
+
def upload!
|
11
|
+
puts "scp> #{@node.identifier}".informational
|
12
|
+
::Net::SCP.start(@node.ip_address, @node.ssh_user, { :proxy => @proxy, :keys => @node.keys, :paranoid => false }) do |scp|
|
13
|
+
@files.each do |local_path|
|
14
|
+
name_old = ""
|
15
|
+
scp.upload!(local_path, @remote_path, { :recursive => true }) do |ch, name, sent, total|
|
16
|
+
log_string = "#{name}: #{sent}/#{total}"
|
17
|
+
if name_old == name
|
18
|
+
STDOUT.write "\r#{log_string}"
|
19
|
+
else
|
20
|
+
STDOUT.write "\n\r#{log_string}"
|
21
|
+
end
|
22
|
+
name_old = name
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
puts # we don't control the output here, this moves us down a line:
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
data/lib/ssh.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
class ::Bcome::Ssh
|
2
|
+
|
3
|
+
attr_reader :commands
|
4
|
+
|
5
|
+
def initialize(commands, proxy, node)
|
6
|
+
@commands = commands
|
7
|
+
@proxy = proxy
|
8
|
+
@node = node
|
9
|
+
end
|
10
|
+
|
11
|
+
def execute!
|
12
|
+
::Net::SSH.start(@node.ip_address, @node.ssh_user, :proxy => @proxy, :keys => @node.keys, :paranoid => false) do |ssh|
|
13
|
+
@commands.each do |command|
|
14
|
+
puts "(#{@node.identifier}) #{@user}$ #{command.raw_command}".command
|
15
|
+
ssh_exec!(ssh, command)
|
16
|
+
|
17
|
+
puts command.output
|
18
|
+
|
19
|
+
print command.pretty_result
|
20
|
+
puts "\n"
|
21
|
+
end
|
22
|
+
ssh.close
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def ssh_exec!(ssh, command)
|
27
|
+
ssh.open_channel do |channel|
|
28
|
+
channel.exec(command.raw_command) do |ch, success|
|
29
|
+
unless success
|
30
|
+
abort "FAILED: couldn't execute command (ssh.channel.exec)"
|
31
|
+
end
|
32
|
+
channel.on_data do |ch,data|
|
33
|
+
command.stdout +=data
|
34
|
+
end
|
35
|
+
channel.on_extended_data do |ch,type,data|
|
36
|
+
command.stderr +=data
|
37
|
+
end
|
38
|
+
channel.on_request("exit-status") do |ch,data|
|
39
|
+
command.exit_code = data.read_long
|
40
|
+
end
|
41
|
+
channel.on_request("exit-signal") do |ch, data|
|
42
|
+
command.exit_signal = data.read_long
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
ssh.loop
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module ::Bcome::Stack
|
2
|
+
class Environment
|
3
|
+
|
4
|
+
include ::Bcome::BecomeObject
|
5
|
+
include ::Bcome::Selections
|
6
|
+
include ::Bcome::FogHelper
|
7
|
+
include ::Bcome::EnvironmentSSH
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def collection_from_config(parent, configuration)
|
11
|
+
collection = []
|
12
|
+
configuration.each do |meta_data|
|
13
|
+
collection << new(meta_data, parent) if meta_data[:network].to_s == parent.identifier.to_s
|
14
|
+
end
|
15
|
+
return collection
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :identifier, :platform, :meta_data
|
20
|
+
|
21
|
+
def initialize(meta_data, parent)
|
22
|
+
@meta_data = meta_data
|
23
|
+
@identifier = meta_data[:environment]
|
24
|
+
@platform = parent
|
25
|
+
end
|
26
|
+
|
27
|
+
def menu_items
|
28
|
+
super + [
|
29
|
+
{ :command => "add", :description => "Add a resource you wish to work on.", :usage => "add 'identifier', OR add ['array', 'of', 'identifiers']" },
|
30
|
+
{ :command => "add!", :description => "Add all resources from the current context." },
|
31
|
+
{ :command => "remove", :description => "Remove a resource you no longer with to work on.", :usage => "remove 'identifier', OR remove ['array', 'of','identifiers']" },
|
32
|
+
{ :command => "clear!", :description => "Remove all selected resources." },
|
33
|
+
{ :command => "selections", :description => "Show all added resources." },
|
34
|
+
{ :command => "rsync", :description => "Rsync files to all selected resources.", :usage => "rsync 'localpath', 'remotepath'" },
|
35
|
+
{ :command => "run", :description => "Execute a command on all selected resources", :usage => "run 'command'" },
|
36
|
+
{ :command => "scp", :description => "SCP files up to all selected resources", :usage => "scp ['array','of', 'file', 'paths'], 'remote_path'" },
|
37
|
+
]
|
38
|
+
end
|
39
|
+
|
40
|
+
def bastion_server
|
41
|
+
resources.select{|resource| resource.identifier == @meta_data[:ssh_mode][:jump_host_identifier] }.first
|
42
|
+
end
|
43
|
+
|
44
|
+
def ssh_mode_type
|
45
|
+
ssh_mode_type = @meta_data[:ssh_mode][:type]
|
46
|
+
raise "Invalid ssh mode type #{ssh_mode_type}. Should be one of #{valid_ssh_modes.join(", ")}".failure unless valid_ssh_modes.include?(ssh_mode_type)
|
47
|
+
return @meta_data[:ssh_mode][:type]
|
48
|
+
end
|
49
|
+
|
50
|
+
def ssh_mode_user
|
51
|
+
return @meta_data[:ssh_mode][:ssh_user]
|
52
|
+
end
|
53
|
+
|
54
|
+
def valid_ssh_modes
|
55
|
+
[::SSH_JUMP_MODE_IDENTIFIER, ::SSH_DIRECT_MODE_IDENTIFIER]
|
56
|
+
end
|
57
|
+
|
58
|
+
def bastion_ip_address
|
59
|
+
if dynamic_network_lookup?
|
60
|
+
return bastion_server.public_ip_address
|
61
|
+
else
|
62
|
+
bastion_ip_address = @meta_data[:ssh_mode][:jump_host_ip_address]
|
63
|
+
raise "No jump_host_ip_address specified in your configuration.".failure unless bastion_ip_address
|
64
|
+
return bastion_ip_address
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def network_lookup
|
69
|
+
raise "Missing network lookup in networks_environment configuration".failure unless @meta_data[:network_lookup]
|
70
|
+
return @meta_data[:network_lookup]
|
71
|
+
end
|
72
|
+
|
73
|
+
def network_lookup_type
|
74
|
+
type = network_lookup[:type]
|
75
|
+
raise "Unknown network lookup type '#{type}" unless ["dynamic", "static"].include?(type)
|
76
|
+
return type
|
77
|
+
end
|
78
|
+
|
79
|
+
def dynamic_network_lookup?
|
80
|
+
return network_lookup_type == "dynamic"
|
81
|
+
end
|
82
|
+
|
83
|
+
def do_load_resources
|
84
|
+
if dynamic_network_lookup?
|
85
|
+
return collection_klass.collection_from_fog_data(self, servers)
|
86
|
+
else
|
87
|
+
path = config_path
|
88
|
+
raise "Missing instances.yml configuration for your environment. You've selected a static network lookup, and yet I cannot find #{config_path}".failure unless File.exist?(config_path)
|
89
|
+
config = YAML.load_file(path)
|
90
|
+
return collection_klass.collection_from_config(self, config)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def config_path
|
95
|
+
"#{CONFIGS_PATH}/static_instances/#{platform.identifier}_#{identifier}-instances.yml"
|
96
|
+
end
|
97
|
+
|
98
|
+
def do_describe
|
99
|
+
desc = "#{identifier}"
|
100
|
+
desc += "\t[ Net lookup: #{network_lookup_type}"
|
101
|
+
desc += "\t * SSH Mode: #{ssh_mode_type} ]"
|
102
|
+
end
|
103
|
+
|
104
|
+
def collection_klass
|
105
|
+
::INSTANCE_STACK_KLASS
|
106
|
+
end
|
107
|
+
|
108
|
+
def reference_key
|
109
|
+
:environments
|
110
|
+
end
|
111
|
+
|
112
|
+
def collection_key
|
113
|
+
:instances
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module ::Bcome::Stack
|
2
|
+
class Instance
|
3
|
+
|
4
|
+
include ::Bcome::BecomeObject
|
5
|
+
include ::Bcome::InstanceSsh
|
6
|
+
include ::Bcome::InstanceCommand
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def collection_from_fog_data(parent, fog_instances)
|
10
|
+
collection = []
|
11
|
+
fog_instances.each do |f_instance|
|
12
|
+
|
13
|
+
meta_data = {
|
14
|
+
:identifier => f_instance.tags["Name"],
|
15
|
+
:external_network_interface_address => f_instance.private_ip_address,
|
16
|
+
:public_ip_address => f_instance.public_ip_address,
|
17
|
+
:role => f_instance.tags["function"]
|
18
|
+
}
|
19
|
+
collection << new(meta_data, parent)
|
20
|
+
end
|
21
|
+
return collection
|
22
|
+
end
|
23
|
+
|
24
|
+
def collection_from_config(parent, configuration)
|
25
|
+
conf_for_env = configuration[:instances]
|
26
|
+
collection = []
|
27
|
+
conf_for_env.each do |meta_data|
|
28
|
+
collection << new(meta_data, parent)
|
29
|
+
end
|
30
|
+
return collection
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
attr_reader :environment
|
35
|
+
|
36
|
+
def initialize(meta_data, parent)
|
37
|
+
@meta_data = meta_data
|
38
|
+
@environment = parent
|
39
|
+
end
|
40
|
+
|
41
|
+
def menu_items
|
42
|
+
super + [
|
43
|
+
{ :command => "rsync", :description => "Rsync files.", :usage => "rsync 'localpath', 'remotepath'" },
|
44
|
+
{ :command => "run", :description => "Execute a command.", :usage => "run 'command'" },
|
45
|
+
{ :command => "scp", :description => "SCP files.", :usage => "scp ['array','of', 'file', 'paths'], 'remote_path'" },
|
46
|
+
{ :command => "ssh", :description => "Initiate an SSH connection." }
|
47
|
+
]
|
48
|
+
end
|
49
|
+
|
50
|
+
def platform
|
51
|
+
@environment.platform
|
52
|
+
end
|
53
|
+
|
54
|
+
def responds_to_list?
|
55
|
+
false
|
56
|
+
end
|
57
|
+
|
58
|
+
def identifier
|
59
|
+
@meta_data[:identifier]
|
60
|
+
end
|
61
|
+
|
62
|
+
def ip_address
|
63
|
+
@environment.ssh_mode_type == ::SSH_DIRECT_MODE_IDENTIFIER ? public_ip_address : @meta_data[:external_network_interface_address]
|
64
|
+
end
|
65
|
+
|
66
|
+
def public_ip_address
|
67
|
+
@meta_data[:public_ip_address]
|
68
|
+
end
|
69
|
+
|
70
|
+
def role
|
71
|
+
@meta_data[:role]
|
72
|
+
end
|
73
|
+
|
74
|
+
def reference_key
|
75
|
+
:instances
|
76
|
+
end
|
77
|
+
|
78
|
+
def highlight?
|
79
|
+
@environment.object_in_selections?(self)
|
80
|
+
end
|
81
|
+
|
82
|
+
def do_describe
|
83
|
+
description = "#{identifier}"
|
84
|
+
description += "\n\t* Internal IP #{@meta_data[:external_network_interface_address]}"
|
85
|
+
description += "\n\t* External IP #{public_ip_address}" if public_ip_address
|
86
|
+
description += "\n\t* #{role}" if role
|
87
|
+
description += "\n"
|
88
|
+
return description
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module ::Bcome::Stack
|
2
|
+
class Platform
|
3
|
+
|
4
|
+
include ::Bcome::BecomeObject
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def collection_from_config(parent, configuration)
|
8
|
+
return unless configuration
|
9
|
+
collection = []
|
10
|
+
configuration.each do |conf_data|
|
11
|
+
collection << new(conf_data[:name], conf_data)
|
12
|
+
end
|
13
|
+
return collection
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :identifier
|
18
|
+
attr_reader :quick_contexts
|
19
|
+
|
20
|
+
def initialize(identifier, meta_data)
|
21
|
+
@identifier = identifier
|
22
|
+
@meta_data = meta_data
|
23
|
+
@quick_contexts = @meta_data[:quick_contexts]
|
24
|
+
end
|
25
|
+
|
26
|
+
def menu_items
|
27
|
+
super
|
28
|
+
end
|
29
|
+
|
30
|
+
def do_describe
|
31
|
+
"#{identifier}\n - #{@meta_data[:description]}\n"
|
32
|
+
end
|
33
|
+
|
34
|
+
def config_path
|
35
|
+
"#{CONFIGS_PATH}/network.yml"
|
36
|
+
end
|
37
|
+
|
38
|
+
def collection_klass
|
39
|
+
::ENV_STACK_KLASS
|
40
|
+
end
|
41
|
+
|
42
|
+
def reference_key
|
43
|
+
:platforms
|
44
|
+
end
|
45
|
+
|
46
|
+
def collection_key
|
47
|
+
:environments
|
48
|
+
end
|
49
|
+
|
50
|
+
def has_quick_contexts?
|
51
|
+
return !quick_contexts.nil?
|
52
|
+
end
|
53
|
+
|
54
|
+
def quick_context_for_reference(context_reference)
|
55
|
+
return nil unless has_quick_contexts?
|
56
|
+
matches = quick_contexts.select{|qc| qc[:ref] == context_reference }
|
57
|
+
raise "Multiple quick context matches found on platform #{@identifier} for context key #{context_reference}. Cannot load quick context - selection is ambiguous." if matches.size > 1
|
58
|
+
return matches.first
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|