bcome 1.2.0 → 1.3.5
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|