bcome 1.2.0 → 1.3.5
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 +5 -5
- data/lib/bcome.rb +1 -0
- data/lib/objects/bcome/version.rb +1 -1
- data/lib/objects/encryptor.rb +99 -0
- data/lib/objects/exception/invalid_metadata_encryption_key.rb +7 -0
- data/lib/objects/modules/workspace_commands.rb +0 -1
- data/lib/objects/modules/workspace_menu.rb +12 -0
- data/lib/objects/node/base.rb +28 -3
- data/lib/objects/node/factory.rb +18 -0
- data/lib/objects/node/inventory/base.rb +6 -1
- data/lib/objects/node/inventory/defined.rb +5 -0
- data/lib/objects/node/inventory/subselect.rb +6 -0
- data/lib/objects/node/meta_data_loader.rb +46 -4
- data/lib/objects/node/resources/base.rb +5 -1
- data/lib/objects/node/resources/inventory.rb +22 -2
- data/lib/objects/node/server/base.rb +6 -1
- data/lib/objects/orchestration/interactive_terraform.rb +80 -0
- data/lib/objects/parser/bread_crumb.rb +1 -1
- data/lib/objects/ssh/connection_handler.rb +2 -2
- data/lib/objects/ssh/driver.rb +55 -4
- data/lib/objects/terraform/parser.rb +23 -0
- data/lib/objects/terraform/state.rb +40 -0
- data/patches/irb.rb +1 -1
- data/patches/string-encrypt.rb +40 -0
- metadata +18 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4ecdbb7b050b6fa0d1b6f6c7364e809cc997735ecb9970b8b6ecf3a59966fc6a
|
4
|
+
data.tar.gz: e002f9bfbe1cd8df7630b40173fb3218c3a19aeb9785bdd04e348abf091a47f7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5ed1ca4f5fd5affa049d7959e273196e72cf7a250fa1af191f82a6741c8765492e35d1eaca5e03e52a078d206fcefdb883212c082c7f955ec2ac68fb7e0c6fe5
|
7
|
+
data.tar.gz: 5774596787d2cd30c8117f1c0c69c8de252e41ce30c49c6a7d450cb73f6264ba5cb67928243f1132eb7e9bf3baf22bf4a85ab4c22aed5e5077ef6ce2a0340c9e
|
data/lib/bcome.rb
CHANGED
@@ -0,0 +1,99 @@
|
|
1
|
+
module Bcome
|
2
|
+
class Encryptor
|
3
|
+
|
4
|
+
UNENC_SIGNIFIER = "".freeze
|
5
|
+
ENC_SIGNIFIER = "enc".freeze
|
6
|
+
|
7
|
+
include Singleton
|
8
|
+
|
9
|
+
attr_reader :key
|
10
|
+
|
11
|
+
def pack
|
12
|
+
# Bcome currently works with a single encryption key - the same one - for all files
|
13
|
+
# When we attempt an encrypt we'll check first to see if any encrypted files already exists, and
|
14
|
+
# we'll try our key on it. If the fails to unpack the file, we abort the encryption attempt.
|
15
|
+
prompt_for_key
|
16
|
+
if has_files_to_encrypt?
|
17
|
+
verify_presented_key if has_encrypted_files?
|
18
|
+
toggle_packed_files(all_unencrypted_filenames, :encrypt)
|
19
|
+
else
|
20
|
+
puts "\nNo unencrypted files to encrypt.\n".warning
|
21
|
+
end
|
22
|
+
return
|
23
|
+
end
|
24
|
+
|
25
|
+
def prompt_for_key
|
26
|
+
print "Please enter an encryption key (and if your data is already encrypted, you must provide the same key): ".informational
|
27
|
+
@key = STDIN.noecho(&:gets).chomp
|
28
|
+
puts "\n"
|
29
|
+
end
|
30
|
+
|
31
|
+
def has_encrypted_files?
|
32
|
+
all_encrypted_filenames.any?
|
33
|
+
end
|
34
|
+
|
35
|
+
def has_files_to_encrypt?
|
36
|
+
all_unencrypted_filenames.any?
|
37
|
+
end
|
38
|
+
|
39
|
+
def verify_presented_key
|
40
|
+
# We attempt a decrypt of any encrypted file in order to verify that a newly presented key
|
41
|
+
# matches the key used to previously encrypt. Bcome operates on a one-key-per-implementation basis.
|
42
|
+
test_file = all_encrypted_filenames.first
|
43
|
+
file_contents = File.read(test_file)
|
44
|
+
file_contents.decrypt(@key)
|
45
|
+
end
|
46
|
+
|
47
|
+
def unpack
|
48
|
+
prompt_for_key
|
49
|
+
toggle_packed_files(all_encrypted_filenames,:decrypt)
|
50
|
+
return
|
51
|
+
end
|
52
|
+
|
53
|
+
def toggle_packed_files(filenames, packer_method)
|
54
|
+
raise "Missing encryption key. Please set an encryption key" unless @key
|
55
|
+
filenames.each do |filename|
|
56
|
+
# Get raw
|
57
|
+
raw_contents = File.read(filename)
|
58
|
+
|
59
|
+
if packer_method == :decrypt
|
60
|
+
filename =~ /#{path_to_metadata}\/(.+)\.enc/
|
61
|
+
opposing_filename = $1
|
62
|
+
action = "Unpacking"
|
63
|
+
else
|
64
|
+
filename =~ /#{path_to_metadata}\/(.*)/
|
65
|
+
opposing_filename = "#{$1}.enc"
|
66
|
+
action = "Packing"
|
67
|
+
end
|
68
|
+
|
69
|
+
# Write encrypted/decryption action
|
70
|
+
enc_decrypt_result = raw_contents.send(packer_method, @key)
|
71
|
+
puts "#{action}\s".informational + filename + "\sto\s".informational + "#{path_to_metadata}/" + opposing_filename
|
72
|
+
write_file(opposing_filename, enc_decrypt_result)
|
73
|
+
end
|
74
|
+
puts "\ndone".informational
|
75
|
+
end
|
76
|
+
|
77
|
+
def path_to_metadata
|
78
|
+
"bcome/metadata"
|
79
|
+
end
|
80
|
+
|
81
|
+
def write_file(filename, contents)
|
82
|
+
filepath = "#{path_to_metadata}/#{filename}"
|
83
|
+
File.open("#{filepath}", 'w') { |f| f.write(contents) }
|
84
|
+
end
|
85
|
+
|
86
|
+
def all_unencrypted_filenames
|
87
|
+
Dir["#{metadata_path}/*"].reject {|f| f =~ /\.enc/}
|
88
|
+
end
|
89
|
+
|
90
|
+
def all_encrypted_filenames
|
91
|
+
Dir["#{metadata_path}/*.enc"]
|
92
|
+
end
|
93
|
+
|
94
|
+
def metadata_path
|
95
|
+
"bcome/metadata"
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|
@@ -102,7 +102,6 @@ module Bcome::WorkspaceCommands
|
|
102
102
|
desc += "\t"
|
103
103
|
desc += is_active ? key.to_s.resource_key : key.to_s.resource_key_inactive
|
104
104
|
desc += "\s" * (12 - key.length)
|
105
|
-
attribute_value = value == :identifier ? attribute_value.underline : attribute_value
|
106
105
|
desc += is_active ? attribute_value.resource_value : attribute_value.resource_value_inactive
|
107
106
|
desc += "\n"
|
108
107
|
desc = desc unless is_active
|
@@ -89,6 +89,12 @@ module Bcome::WorkspaceMenu
|
|
89
89
|
console_only: false,
|
90
90
|
terminal_usage: "put 'local/path' 'remote/path'"
|
91
91
|
},
|
92
|
+
put_str: {
|
93
|
+
description: 'Write a file /to/remote/path from a string',
|
94
|
+
usage: 'put_str',
|
95
|
+
console_only: false,
|
96
|
+
terminal_usage: "put_str '<file contents>', 'remote/path'"
|
97
|
+
},
|
92
98
|
rsync: {
|
93
99
|
description: 'upload a file or directory using rsync (faster)',
|
94
100
|
usage: "rsync 'local/path','remote/path'",
|
@@ -113,6 +119,12 @@ module Bcome::WorkspaceMenu
|
|
113
119
|
meta: {
|
114
120
|
description: 'Print out all metadata related to this node'
|
115
121
|
},
|
122
|
+
pack_metadata: {
|
123
|
+
description: 'Encrypt your metadata files',
|
124
|
+
},
|
125
|
+
unpack_metadata: {
|
126
|
+
description: 'Decrypt and expose your encrypted metadata files',
|
127
|
+
},
|
116
128
|
registry: {
|
117
129
|
description: 'List all user defined commands present in your registry, and available to this namespace',
|
118
130
|
console_only: false
|
data/lib/objects/node/base.rb
CHANGED
@@ -50,13 +50,17 @@ module Bcome::Node
|
|
50
50
|
end
|
51
51
|
|
52
52
|
def enabled_menu_items
|
53
|
-
[:ls, :lsa, :workon, :enable, :disable, :enable!, :disable!, :run, :tree, :ping, :put, :rsync, :cd, :meta, :registry, :interactive, :execute_script]
|
53
|
+
[:ls, :lsa, :workon, :enable, :disable, :enable!, :disable!, :run, :tree, :ping, :put, :put_str, :rsync, :cd, :meta, :pack_metadata, :unpack_metadata, :registry, :interactive, :execute_script]
|
54
54
|
end
|
55
55
|
|
56
56
|
def has_proxy?
|
57
57
|
ssh_driver.has_proxy?
|
58
58
|
end
|
59
59
|
|
60
|
+
def identifier=(new_identifier)
|
61
|
+
@identifier = new_identifier
|
62
|
+
end
|
63
|
+
|
60
64
|
def proxy
|
61
65
|
ssh_driver.proxy
|
62
66
|
end
|
@@ -83,6 +87,13 @@ module Bcome::Node
|
|
83
87
|
return
|
84
88
|
end
|
85
89
|
|
90
|
+
def put_str(string, remote_path)
|
91
|
+
resources.active.each do |resource|
|
92
|
+
resource.put_str(string, remote_path)
|
93
|
+
end
|
94
|
+
return
|
95
|
+
end
|
96
|
+
|
86
97
|
def execute_script(script_name)
|
87
98
|
results = {}
|
88
99
|
machines.pmap do |machine|
|
@@ -91,6 +102,14 @@ module Bcome::Node
|
|
91
102
|
end
|
92
103
|
results
|
93
104
|
end
|
105
|
+
|
106
|
+
def pack_metadata
|
107
|
+
::Bcome::Encryptor.instance.pack
|
108
|
+
end
|
109
|
+
|
110
|
+
def unpack_metadata
|
111
|
+
::Bcome::Encryptor.instance.unpack
|
112
|
+
end
|
94
113
|
|
95
114
|
def validate_attributes
|
96
115
|
validate_identifier
|
@@ -100,8 +119,14 @@ module Bcome::Node
|
|
100
119
|
|
101
120
|
def validate_identifier
|
102
121
|
@identifier = DEFAULT_IDENTIFIER if is_top_level_node? && !@identifier && !is_a?(::Bcome::Node::Server::Base)
|
103
|
-
|
104
|
-
|
122
|
+
|
123
|
+
@identifier = "NO-ID_#{Time.now.to_i}" unless @identifier
|
124
|
+
|
125
|
+
#raise ::Bcome::Exception::MissingIdentifierOnView.new(@views.inspect) unless @identifier
|
126
|
+
@identifier.gsub!(/\s/, "_") # Remove whitespace
|
127
|
+
@identifier.gsub!("-", "_") # change hyphens to undescores, hyphens don't play well in var names in irb
|
128
|
+
|
129
|
+
#raise ::Bcome::Exception::InvalidIdentifier.new("'#{@identifier}' contains whitespace") if @identifier =~ /\s/
|
105
130
|
end
|
106
131
|
|
107
132
|
def requires_description?
|
data/lib/objects/node/factory.rb
CHANGED
@@ -7,6 +7,7 @@ module Bcome::Node
|
|
7
7
|
CONFIG_PATH = 'bcome'.freeze
|
8
8
|
DEFAULT_CONFIG_NAME = 'networks.yml'.freeze
|
9
9
|
SERVER_OVERRIDE_CONFIG_NAME = 'machines-data.yml'.freeze
|
10
|
+
LOCAL_OVERRIDE_CONFIG_NAME = 'me.yml'.freeze
|
10
11
|
|
11
12
|
INVENTORY_KEY = 'inventory'.freeze
|
12
13
|
COLLECTION_KEY = 'collection'.freeze
|
@@ -122,6 +123,23 @@ module Bcome::Node
|
|
122
123
|
raise Bcome::Exception::InvalidNetworkConfig, 'Invalid yaml in machines data config' + e.message
|
123
124
|
end
|
124
125
|
|
126
|
+
def local_data
|
127
|
+
@local_data ||= load_local_data
|
128
|
+
end
|
129
|
+
|
130
|
+
def local_data_path
|
131
|
+
"#{CONFIG_PATH}/#{LOCAL_OVERRIDE_CONFIG_NAME}"
|
132
|
+
end
|
133
|
+
|
134
|
+
def load_local_data
|
135
|
+
return {} unless File.exist?(local_data_path)
|
136
|
+
config = YAML.load_file(local_data_path)
|
137
|
+
return {} if config.nil?
|
138
|
+
return config
|
139
|
+
rescue ArgumentError, Psych::SyntaxError => e
|
140
|
+
raise Bcome::Exception::InvalidNetworkConfig, 'Invalid yaml in machines data config' + e.message
|
141
|
+
end
|
142
|
+
|
125
143
|
def is_running_deprecated_configs?
|
126
144
|
File.exist?("bcome/config/platform.yml")
|
127
145
|
end
|
@@ -60,7 +60,7 @@ module Bcome::Node::Inventory
|
|
60
60
|
end
|
61
61
|
|
62
62
|
def tags(identifier = nil)
|
63
|
-
direct_invoke_server(:tags, identifier)
|
63
|
+
identifier.nil? ? direct_invoke_all_servers(:tags) : direct_invoke_server(:tags, identifier)
|
64
64
|
end
|
65
65
|
|
66
66
|
def direct_invoke_server(method, identifier)
|
@@ -76,6 +76,11 @@ module Bcome::Node::Inventory
|
|
76
76
|
end
|
77
77
|
end
|
78
78
|
|
79
|
+
def direct_invoke_all_servers(method)
|
80
|
+
resources.active.each {|m| m.send(method) }
|
81
|
+
return
|
82
|
+
end
|
83
|
+
|
79
84
|
def cache_nodes_in_memory
|
80
85
|
@cache_handler.do_cache_nodes!
|
81
86
|
end
|
@@ -29,6 +29,7 @@ module Bcome::Node::Inventory
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def reload
|
32
|
+
resources.reset_duplicate_nodes!
|
32
33
|
do_reload
|
33
34
|
puts "\nDone. Hit 'ls' to see the refreshed inventory.\n".informational
|
34
35
|
end
|
@@ -99,9 +100,13 @@ module Bcome::Node::Inventory
|
|
99
100
|
|
100
101
|
def load_dynamic_nodes
|
101
102
|
raw_servers = fetch_server_list
|
103
|
+
|
102
104
|
raw_servers.each do |raw_server|
|
103
105
|
resources << ::Bcome::Node::Server::Dynamic.new_from_fog_instance(raw_server, self)
|
104
106
|
end
|
107
|
+
|
108
|
+
resources.rename_initial_duplicate if resources.should_rename_initial_duplicate?
|
109
|
+
|
105
110
|
end
|
106
111
|
|
107
112
|
def fetch_server_list
|
@@ -31,10 +31,16 @@ module Bcome::Node::Inventory
|
|
31
31
|
'sub-inventory'
|
32
32
|
end
|
33
33
|
|
34
|
+
def reload
|
35
|
+
do_reload
|
36
|
+
end
|
37
|
+
|
34
38
|
def do_reload
|
39
|
+
parent_inventory.resources.reset_duplicate_nodes!
|
35
40
|
parent_inventory.do_reload
|
36
41
|
resources.run_subselect
|
37
42
|
update_nodes
|
43
|
+
return
|
38
44
|
end
|
39
45
|
|
40
46
|
private
|
@@ -8,25 +8,67 @@ module Bcome::Node
|
|
8
8
|
@all_metadata_filenames = Dir["#{META_DATA_FILE_PATH_PREFIX}/*"]
|
9
9
|
end
|
10
10
|
|
11
|
+
def decryption_key
|
12
|
+
@decryption_key
|
13
|
+
end
|
14
|
+
|
11
15
|
def data
|
12
16
|
@data ||= do_load
|
13
17
|
end
|
14
18
|
|
15
19
|
def data_for_namespace(namespace)
|
16
|
-
data[namespace.to_sym] ? data[namespace.to_sym] : {}
|
20
|
+
static_data = data[namespace.to_sym] ? data[namespace.to_sym] : {}
|
21
|
+
static_data.merge(terraform_data_for_namespace(namespace))
|
22
|
+
end
|
23
|
+
|
24
|
+
def terraform_data_for_namespace(namespace)
|
25
|
+
## TODO Not sure what was being smoked, but this only adds in data for the first module
|
26
|
+
## Until I can fix, we will:
|
27
|
+
|
28
|
+
parser = Bcome::Terraform::Parser.new(namespace)
|
29
|
+
attributes = parser.attributes
|
30
|
+
|
31
|
+
terraform_data = {}
|
32
|
+
|
33
|
+
if attributes.keys.any?
|
34
|
+
## 1. Keep the old broken implementation
|
35
|
+
terraform_data["terraform_attributes"] = attributes if attributes.keys.any?
|
36
|
+
## 2. But make all the data accessible
|
37
|
+
terraform_data["tf_state"] = parser.state.config
|
38
|
+
end
|
39
|
+
|
40
|
+
terraform_data
|
41
|
+
end
|
42
|
+
|
43
|
+
def prompt_for_decryption_key
|
44
|
+
print "\nEnter your decryption key: ".informational
|
45
|
+
@decryption_key = STDIN.noecho(&:gets).chomp
|
46
|
+
end
|
47
|
+
|
48
|
+
def load_file_data_for(filepath)
|
49
|
+
if filepath =~ /.enc/ # encrypted file contents
|
50
|
+
prompt_for_decryption_key unless decryption_key
|
51
|
+
encrypted_contents = File.read(filepath)
|
52
|
+
decrypted_contents = encrypted_contents.decrypt(decryption_key)
|
53
|
+
return YAML.load(decrypted_contents)
|
54
|
+
else # unencrypted
|
55
|
+
return YAML.load_file(filepath)
|
56
|
+
end
|
17
57
|
end
|
18
58
|
|
19
59
|
def do_load
|
20
60
|
all_meta_data = {}
|
21
|
-
@all_metadata_filenames.each do |
|
61
|
+
@all_metadata_filenames.each do |filepath|
|
62
|
+
next if filepath =~ /-unenc/ # we only read from the encrypted, packed files.
|
63
|
+
|
22
64
|
begin
|
23
|
-
filedata =
|
65
|
+
filedata = load_file_data_for(filepath)
|
24
66
|
all_meta_data.deep_merge!(filedata)
|
25
67
|
rescue Psych::SyntaxError => e
|
26
68
|
raise Bcome::Exception::InvalidMetaDataConfig, "Error: #{e.message}"
|
27
69
|
end
|
28
70
|
end
|
29
|
-
all_meta_data
|
71
|
+
return all_meta_data
|
30
72
|
end
|
31
73
|
end
|
32
74
|
end
|
@@ -22,6 +22,10 @@ module Bcome::Node::Resources
|
|
22
22
|
@nodes << node
|
23
23
|
end
|
24
24
|
|
25
|
+
def should_rename_initial_duplicate?
|
26
|
+
return false
|
27
|
+
end
|
28
|
+
|
25
29
|
def clear!
|
26
30
|
@disabled_resources = []
|
27
31
|
end
|
@@ -75,7 +79,7 @@ module Bcome::Node::Resources
|
|
75
79
|
end
|
76
80
|
|
77
81
|
def for_identifier(identifier)
|
78
|
-
resource = @nodes.select { |node| node.identifier == identifier }.
|
82
|
+
resource = @nodes.select { |node| node.identifier == identifier }.last
|
79
83
|
resource
|
80
84
|
end
|
81
85
|
|
@@ -7,13 +7,33 @@ module Bcome::Node::Resources
|
|
7
7
|
# We remove the static server from our selection
|
8
8
|
@nodes.delete(existing_node)
|
9
9
|
else
|
10
|
-
|
11
|
-
|
10
|
+
duplicate_nodes[node.identifier] = duplicate_nodes[node.identifier] ? (duplicate_nodes[node.identifier] + 1) : 2
|
11
|
+
count = duplicate_nodes[node.identifier]
|
12
|
+
node.identifier = "#{node.identifier}_#{count}"
|
12
13
|
end
|
13
14
|
end
|
14
15
|
@nodes << node
|
15
16
|
end
|
16
17
|
|
18
|
+
def should_rename_initial_duplicate?
|
19
|
+
return true
|
20
|
+
end
|
21
|
+
|
22
|
+
def rename_initial_duplicate
|
23
|
+
duplicate_nodes.each do |node_identifier, count|
|
24
|
+
node = for_identifier(node_identifier)
|
25
|
+
node.identifier = "#{node.identifier}_1"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def duplicate_nodes
|
30
|
+
@duplicate_nodes ||= {}
|
31
|
+
end
|
32
|
+
|
33
|
+
def reset_duplicate_nodes!
|
34
|
+
@duplicate_nodes = {}
|
35
|
+
end
|
36
|
+
|
17
37
|
def dynamic_nodes
|
18
38
|
active.select(&:dynamic_server?)
|
19
39
|
end
|
@@ -134,7 +134,8 @@ module Bcome::Node::Server
|
|
134
134
|
end
|
135
135
|
|
136
136
|
def pseudo_tty(command)
|
137
|
-
|
137
|
+
as_pseudo_tty = true
|
138
|
+
ssh_cmd = ssh_driver.ssh_command(as_pseudo_tty)
|
138
139
|
tty_command = "#{ssh_cmd} '#{command}'"
|
139
140
|
execute_local(tty_command)
|
140
141
|
end
|
@@ -152,6 +153,10 @@ module Bcome::Node::Server
|
|
152
153
|
ssh_driver.put(local_path, remote_path)
|
153
154
|
end
|
154
155
|
|
156
|
+
def put_str(string, remote_path)
|
157
|
+
ssh_driver.put_str(string, remote_path)
|
158
|
+
end
|
159
|
+
|
155
160
|
def get(remote_path, local_path)
|
156
161
|
ssh_driver.get(remote_path, local_path)
|
157
162
|
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Bcome::Orchestration
|
2
|
+
class InteractiveTerraform < Bcome::Orchestration::Base
|
3
|
+
|
4
|
+
QUIT = "\\q"
|
5
|
+
COMMAND_PROMPT = "enter command or '#{QUIT}' to quit: " + "terraform".informational + "\s"
|
6
|
+
|
7
|
+
def execute
|
8
|
+
show_intro_text
|
9
|
+
wait_for_command_input
|
10
|
+
end
|
11
|
+
|
12
|
+
def show_intro_text
|
13
|
+
puts "\n"
|
14
|
+
puts "INTERACTIVE TERRAFORM\n".underline
|
15
|
+
puts "Namespace:\s" + "#{@node.namespace}".informational
|
16
|
+
puts "Configuration Path:\s" + "#{path_to_env_config}/*".informational
|
17
|
+
puts "\nConfigured metadata:\s" + terraform_metadata.inspect.informational
|
18
|
+
|
19
|
+
puts "\nAny commands you enter here will be passed directly to Terraform in your configuration path scope."
|
20
|
+
end
|
21
|
+
|
22
|
+
# PROCESSING INTERACTIVE COMMANDS
|
23
|
+
#
|
24
|
+
def process_command(raw_command)
|
25
|
+
full_command = command(raw_command)
|
26
|
+
@node.execute_local(full_command)
|
27
|
+
wait_for_command_input
|
28
|
+
end
|
29
|
+
|
30
|
+
# HANDLING USER INPUT
|
31
|
+
#
|
32
|
+
def wait_for_command_input
|
33
|
+
raw_command = wait_for_input
|
34
|
+
unless raw_command == QUIT
|
35
|
+
process_command(raw_command)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def wait_for_input(message = COMMAND_PROMPT)
|
40
|
+
::Readline.readline("\n#{message}", true).squeeze('').to_s
|
41
|
+
end
|
42
|
+
|
43
|
+
# COMMAND PROCESSING
|
44
|
+
def terraform_metadata
|
45
|
+
@terraform_metadata ||= @node.metadata.fetch("terraform", @node.metadata.fetch(:terraform, {}))
|
46
|
+
end
|
47
|
+
|
48
|
+
# Get the terraform variables for this stack, and merge in with our EC2 access keys
|
49
|
+
def form_var_string
|
50
|
+
terraform_vars = terraform_metadata
|
51
|
+
ec2_credentials = @node.network_driver.raw_fog_credentials
|
52
|
+
|
53
|
+
cleaned_data = terraform_vars.select{|k,v|
|
54
|
+
!v.is_a?(Hash) && !v.is_a?(Array)
|
55
|
+
} # we can't yet handle nested terraform metadata on the command line so no arrays or hashes
|
56
|
+
|
57
|
+
all_vars = cleaned_data.merge({
|
58
|
+
:access_key => ec2_credentials["aws_access_key_id"],
|
59
|
+
:secret_key => ec2_credentials["aws_secret_access_key"]
|
60
|
+
})
|
61
|
+
|
62
|
+
all_vars.collect{|key, value| "-var #{key}=\"#{value}\""}.join("\s")
|
63
|
+
end
|
64
|
+
|
65
|
+
def var_string
|
66
|
+
@var_string ||= form_var_string
|
67
|
+
end
|
68
|
+
|
69
|
+
# Retrieve the path to the terraform configurations for this stack
|
70
|
+
def path_to_env_config
|
71
|
+
@path_to_env_config ||= "terraform/environments/#{@node.namespace.gsub(":","_")}"
|
72
|
+
end
|
73
|
+
|
74
|
+
# Formulate a terraform command
|
75
|
+
def command(raw_command)
|
76
|
+
"cd #{path_to_env_config} ; terraform #{raw_command} #{var_string}"
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
@@ -25,7 +25,7 @@ module Bcome::Parser
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def validate!
|
28
|
-
raise Bcome::Exception::InvalidBcomeBreadcrumb.new "- letters, numbers & underscores only" unless @raw_crumbs =~ /^([a-z0-9A-Z_]+)(:\s*[a-z0-9A-Z_]+)*:?$/i
|
28
|
+
#raise Bcome::Exception::InvalidBcomeBreadcrumb.new "- letters, numbers & underscores only" unless @raw_crumbs =~ /^([a-z0-9A-Z_]+)(:\s*[a-z0-9A-Z_]+)*:?$/i
|
29
29
|
end
|
30
30
|
end
|
31
31
|
end
|
@@ -45,7 +45,7 @@ module Bcome::Ssh
|
|
45
45
|
# from bcome, so, we'll sweep up failures and re-try to connect up to MAX_CONNECTION_ATTEMPTS. Once connected, we're generally good - and any subsequent connection failures
|
46
46
|
# within a specific session will be handled ad-hoc and re-connection is automatic.
|
47
47
|
while @servers_to_connect.any? && connection_attempt <= MAX_CONNECTION_ATTEMPTS
|
48
|
-
puts
|
48
|
+
puts "Retrying failed connections\n".warning if connection_attempt > 1
|
49
49
|
do_connect
|
50
50
|
connection_attempt += 1
|
51
51
|
end
|
@@ -69,7 +69,7 @@ module Bcome::Ssh
|
|
69
69
|
if @servers_to_connect.any?
|
70
70
|
puts "Failed to connect to #{@servers_to_connect.size} nodes".error
|
71
71
|
else
|
72
|
-
puts 'All nodes reachable'.success
|
72
|
+
puts 'All nodes reachable '.success
|
73
73
|
end
|
74
74
|
end
|
75
75
|
|
data/lib/objects/ssh/driver.rb
CHANGED
@@ -50,9 +50,14 @@ module Bcome::Ssh
|
|
50
50
|
end
|
51
51
|
|
52
52
|
def has_proxy?
|
53
|
+
return false if proxy_config_value && proxy_config_value == -1
|
53
54
|
!@config[:proxy].nil?
|
54
55
|
end
|
55
56
|
|
57
|
+
def proxy_config_value
|
58
|
+
@config[:proxy]
|
59
|
+
end
|
60
|
+
|
56
61
|
def proxy_connection_string
|
57
62
|
"ssh #{PROXY_CONNECT_PREFIX} #{bastion_host_user}@#{@proxy_data.host}"
|
58
63
|
end
|
@@ -70,15 +75,28 @@ module Bcome::Ssh
|
|
70
75
|
bootstrap? && @bootstrap_settings.bastion_host_user ? @bootstrap_settings.bastion_host_user : @proxy_data.bastion_host_user ? @proxy_data.bastion_host_user : user
|
71
76
|
end
|
72
77
|
|
73
|
-
def ssh_command
|
78
|
+
def ssh_command(as_pseudo_tty = false)
|
74
79
|
return bootstrap_ssh_command if bootstrap? && @bootstrap_settings.ssh_key_path
|
80
|
+
|
75
81
|
if has_proxy?
|
76
|
-
"ssh #{PROXY_SSH_PREFIX} #{bastion_host_user}@#{@proxy_data.host}\" #{user}@#{@context_node.internal_ip_address}"
|
82
|
+
"#{as_pseudo_tty ? "ssh -t" : "ssh"} #{PROXY_SSH_PREFIX} #{bastion_host_user}@#{@proxy_data.host}\" #{node_level_ssh_key_connection_string}#{user}@#{@context_node.internal_ip_address}"
|
77
83
|
else
|
78
|
-
"ssh #{user}@#{@context_node.public_ip_address}"
|
84
|
+
"#{as_pseudo_tty ? "ssh -t" : "ssh"} #{node_level_ssh_key_connection_string}#{user}@#{@context_node.public_ip_address}"
|
79
85
|
end
|
80
86
|
end
|
81
87
|
|
88
|
+
def node_level_ssh_key_connection_string
|
89
|
+
key_specified_at_node_level? ? "-i #{node_level_ssh_key}\s" : ""
|
90
|
+
end
|
91
|
+
|
92
|
+
def key_specified_at_node_level?
|
93
|
+
!node_level_ssh_key.nil?
|
94
|
+
end
|
95
|
+
|
96
|
+
def node_level_ssh_key
|
97
|
+
return (@config[:ssh_keys]) ? @config[:ssh_keys].first : nil
|
98
|
+
end
|
99
|
+
|
82
100
|
def local_port_forward(start_port, end_port)
|
83
101
|
if has_proxy?
|
84
102
|
|
@@ -109,7 +127,25 @@ module Bcome::Ssh
|
|
109
127
|
end
|
110
128
|
|
111
129
|
def user
|
112
|
-
|
130
|
+
# If we're in bootstrapping mode and have a bootstrap user set, return it
|
131
|
+
return @bootstrap_settings.user if (bootstrap? && @bootstrap_settings.user)
|
132
|
+
|
133
|
+
# If we have a user explcitly set in the config, then return it
|
134
|
+
return @config[:user] if @config[:user]
|
135
|
+
|
136
|
+
# If the local user has explicitly overriden their user, return that
|
137
|
+
return overriden_local_user if overriden_local_user
|
138
|
+
|
139
|
+
# Else fall back to whichever local user is using bcome
|
140
|
+
fallback_local_user
|
141
|
+
end
|
142
|
+
|
143
|
+
def overriden_local_user
|
144
|
+
@overriden_local_user ||= get_overriden_local_user
|
145
|
+
end
|
146
|
+
|
147
|
+
def get_overriden_local_user
|
148
|
+
::Bcome::Node::Factory.instance.local_data[:ssh_user]
|
113
149
|
end
|
114
150
|
|
115
151
|
def fallback_local_user
|
@@ -195,6 +231,21 @@ module Bcome::Ssh
|
|
195
231
|
nil
|
196
232
|
end
|
197
233
|
|
234
|
+
def put_str(string, remote_path)
|
235
|
+
raise Bcome::Exception::MissingParamsForScp, "'put' requires a string and a remote_path" if string.to_s.empty? || remote_path.to_s.empty?
|
236
|
+
puts "\n(#{@context_node.namespace})\s".namespace + "Uploading from string to #{remote_path}\n".informational
|
237
|
+
|
238
|
+
begin
|
239
|
+
scp.upload!(StringIO.new(string), remote_path) do |_ch, name, sent, total|
|
240
|
+
puts "#{name}: #{sent}/#{total}".progress
|
241
|
+
end
|
242
|
+
rescue Exception => e # scp just throws generic exceptions :-/
|
243
|
+
puts e.message.error
|
244
|
+
end
|
245
|
+
nil
|
246
|
+
end
|
247
|
+
|
248
|
+
|
198
249
|
def get(remote_path, local_path)
|
199
250
|
raise Bcome::Exception::MissingParamsForScp, "'get' requires a local_path and a remote_path" if local_path.to_s.empty? || remote_path.to_s.empty?
|
200
251
|
puts "\n(#{@context_node.namespace})\s".namespace + "Downloading #{remote_path} to #{local_path}\n".informational
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Bcome::Terraform
|
2
|
+
class Parser
|
3
|
+
def initialize(namespace)
|
4
|
+
@namespace = namespace
|
5
|
+
end
|
6
|
+
|
7
|
+
def attributes
|
8
|
+
a = {}
|
9
|
+
resources.keys.each do |key|
|
10
|
+
a[key] = resources[key]["primary"]["attributes"]
|
11
|
+
end
|
12
|
+
a
|
13
|
+
end
|
14
|
+
|
15
|
+
def resources
|
16
|
+
state.resources
|
17
|
+
end
|
18
|
+
|
19
|
+
def state
|
20
|
+
@state ||= ::Bcome::Terraform::State.new(@namespace)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Bcome::Terraform
|
2
|
+
class State
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
TSTATE_FILENAME = "terraform.tfstate".freeze
|
6
|
+
|
7
|
+
def initialize(namespace)
|
8
|
+
@namespace = namespace
|
9
|
+
end
|
10
|
+
|
11
|
+
def terraform_installation_path
|
12
|
+
# Look for a terraform config installation in the path belonging to this node
|
13
|
+
@terraform_installation_path ||= "terraform/environments/#{@namespace.gsub(":","_")}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def config_path
|
17
|
+
"#{terraform_installation_path}/#{TSTATE_FILENAME}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def config_exists?
|
21
|
+
File.exist?(config_path)
|
22
|
+
end
|
23
|
+
|
24
|
+
def config
|
25
|
+
return {} unless config_exists?
|
26
|
+
JSON.parse(File.read(config_path))
|
27
|
+
end
|
28
|
+
|
29
|
+
def modules
|
30
|
+
return {} unless config_exists?
|
31
|
+
return config["modules"]
|
32
|
+
end
|
33
|
+
|
34
|
+
def resources
|
35
|
+
# TODO What was I thinking ... We need all the modules...
|
36
|
+
return {} unless config_exists?
|
37
|
+
return modules[0]["resources"]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/patches/irb.rb
CHANGED
@@ -3,7 +3,7 @@ require 'irb'
|
|
3
3
|
module IRB
|
4
4
|
class << self
|
5
5
|
# with thanks: http://stackoverflow.com/questions/4749476/how-can-i-pass-arguments-to-irb-if-i-dont-specify-programfile
|
6
|
-
def parse_opts_with_ignoring_script
|
6
|
+
def parse_opts_with_ignoring_script(*params)
|
7
7
|
arg = ARGV.first
|
8
8
|
script = $PROGRAM_NAME
|
9
9
|
parse_opts_without_ignoring_script
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
# Adapted from https://stackoverflow.com/questions/39033577/opensslcipherciphererror-wrong-final-block-length
|
4
|
+
|
5
|
+
class String
|
6
|
+
|
7
|
+
ALGORITHM = 'AES-256-ECB'
|
8
|
+
|
9
|
+
def encrypt(key)
|
10
|
+
begin
|
11
|
+
cipher = OpenSSL::Cipher.new(ALGORITHM)
|
12
|
+
cipher.encrypt()
|
13
|
+
cipher.key = key.as_256_bit_key
|
14
|
+
crypt = cipher.update(self) + cipher.final()
|
15
|
+
crypt_string = (Base64.encode64(crypt))
|
16
|
+
return crypt_string
|
17
|
+
rescue Exception => e
|
18
|
+
puts "Failed to encrypt: #{e.message}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def decrypt(key)
|
23
|
+
begin
|
24
|
+
cipher = OpenSSL::Cipher.new(ALGORITHM)
|
25
|
+
cipher.decrypt()
|
26
|
+
cipher.key = key.as_256_bit_key
|
27
|
+
tempkey = Base64.decode64(self)
|
28
|
+
crypt = cipher.update(tempkey)
|
29
|
+
crypt << cipher.final()
|
30
|
+
return crypt
|
31
|
+
rescue Exception => e
|
32
|
+
raise ::Bcome::Exception::InvalidMetaDataEncryptionKey.new
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def as_256_bit_key
|
37
|
+
::Digest::SHA256.digest self
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bcome
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Guillaume Roderick (Webzakimbo)
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-09-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -106,14 +106,14 @@ dependencies:
|
|
106
106
|
requirements:
|
107
107
|
- - "~>"
|
108
108
|
- !ruby/object:Gem::Version
|
109
|
-
version: 2.2
|
109
|
+
version: '2.2'
|
110
110
|
type: :runtime
|
111
111
|
prerelease: false
|
112
112
|
version_requirements: !ruby/object:Gem::Requirement
|
113
113
|
requirements:
|
114
114
|
- - "~>"
|
115
115
|
- !ruby/object:Gem::Version
|
116
|
-
version: 2.2
|
116
|
+
version: '2.2'
|
117
117
|
- !ruby/object:Gem::Dependency
|
118
118
|
name: require_all
|
119
119
|
requirement: !ruby/object:Gem::Requirement
|
@@ -142,10 +142,7 @@ dependencies:
|
|
142
142
|
- - "~>"
|
143
143
|
- !ruby/object:Gem::Version
|
144
144
|
version: '1.0'
|
145
|
-
description:
|
146
|
-
the wheel for common & basic dev ops tasks. Network discovery / access machines
|
147
|
-
by SSH / deploy applications / apply orchestration / enable automation. Amazon
|
148
|
-
EC2 integration'
|
145
|
+
description: Orchestration & Automation framework for the Cloud.
|
149
146
|
email:
|
150
147
|
- guillaume@webzakimbo.com
|
151
148
|
executables:
|
@@ -163,6 +160,7 @@ files:
|
|
163
160
|
- lib/objects/driver/bucket.rb
|
164
161
|
- lib/objects/driver/ec2.rb
|
165
162
|
- lib/objects/driver/static.rb
|
163
|
+
- lib/objects/encryptor.rb
|
166
164
|
- lib/objects/exception/argument_error_invoking_method_from_command_line.rb
|
167
165
|
- lib/objects/exception/base.rb
|
168
166
|
- lib/objects/exception/can_only_subselect_on_inventory.rb
|
@@ -186,6 +184,7 @@ files:
|
|
186
184
|
- lib/objects/exception/invalid_machines_cache_config.rb
|
187
185
|
- lib/objects/exception/invalid_matcher_query.rb
|
188
186
|
- lib/objects/exception/invalid_meta_data_config.rb
|
187
|
+
- lib/objects/exception/invalid_metadata_encryption_key.rb
|
189
188
|
- lib/objects/exception/invalid_network_config.rb
|
190
189
|
- lib/objects/exception/invalid_network_driver_type.rb
|
191
190
|
- lib/objects/exception/invalid_proxy_config.rb
|
@@ -244,6 +243,7 @@ files:
|
|
244
243
|
- lib/objects/node/server/dynamic.rb
|
245
244
|
- lib/objects/node/server/static.rb
|
246
245
|
- lib/objects/orchestration/base.rb
|
246
|
+
- lib/objects/orchestration/interactive_terraform.rb
|
247
247
|
- lib/objects/orchestrator.rb
|
248
248
|
- lib/objects/parser/bread_crumb.rb
|
249
249
|
- lib/objects/progress_bar.rb
|
@@ -266,15 +266,21 @@ files:
|
|
266
266
|
- lib/objects/ssh/script_exec.rb
|
267
267
|
- lib/objects/ssh/tunnel/local_port_forward.rb
|
268
268
|
- lib/objects/system/local.rb
|
269
|
+
- lib/objects/terraform/parser.rb
|
270
|
+
- lib/objects/terraform/state.rb
|
269
271
|
- lib/objects/workspace.rb
|
270
272
|
- patches/irb.rb
|
273
|
+
- patches/string-encrypt.rb
|
271
274
|
- patches/string.rb
|
272
275
|
- patches/string_stylesheet.rb
|
273
276
|
homepage: https://github.com/webzakimbo/bcome-kontrol
|
274
277
|
licenses:
|
275
278
|
- GPL-3.0
|
276
|
-
metadata:
|
277
|
-
|
279
|
+
metadata:
|
280
|
+
documentation_uri: https://bcome-kontrol.readthedocs.io/en/latest/
|
281
|
+
post_install_message: |2
|
282
|
+
|
283
|
+
We'd love your feedack about this Gem: How can we improve? Email guillaume@webzakimbo.com
|
278
284
|
rdoc_options: []
|
279
285
|
require_paths:
|
280
286
|
- lib
|
@@ -289,9 +295,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
289
295
|
- !ruby/object:Gem::Version
|
290
296
|
version: '0'
|
291
297
|
requirements: []
|
292
|
-
|
293
|
-
rubygems_version: 2.6.8
|
298
|
+
rubygems_version: 3.0.3
|
294
299
|
signing_key:
|
295
300
|
specification_version: 4
|
296
|
-
summary:
|
301
|
+
summary: A DevOps Application development framework
|
297
302
|
test_files: []
|