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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 815c945bf4581f86eb3b246bc8c3c2f50590722e
4
- data.tar.gz: fc57c0bd29952acb549553a5a421523b8e02ce83
2
+ SHA256:
3
+ metadata.gz: 4ecdbb7b050b6fa0d1b6f6c7364e809cc997735ecb9970b8b6ecf3a59966fc6a
4
+ data.tar.gz: e002f9bfbe1cd8df7630b40173fb3218c3a19aeb9785bdd04e348abf091a47f7
5
5
  SHA512:
6
- metadata.gz: 211fdcf4946327294f6562bc386de71ace4261754a92311221984d5180608299e9088320709906658d8dc2dac4d1599502536b8d9f4c4b84290ca07c42266030
7
- data.tar.gz: 778cede06f00dc1e154aab3c65f531d8a171c5d298f5b162ecf219df42efad388508513c69576a817abf06b63591be4ee8d708cf2193b5cb5616eb7c5bafd82e
6
+ metadata.gz: 5ed1ca4f5fd5affa049d7959e273196e72cf7a250fa1af191f82a6741c8765492e35d1eaca5e03e52a078d206fcefdb883212c082c7f955ec2ac68fb7e0c6fe5
7
+ data.tar.gz: 5774596787d2cd30c8117f1c0c69c8de252e41ce30c49c6a7d450cb73f6264ba5cb67928243f1132eb7e9bf3baf22bf4a85ab4c22aed5e5077ef6ce2a0340c9e
data/lib/bcome.rb CHANGED
@@ -11,6 +11,7 @@ require 'active_support'
11
11
  require 'active_support/core_ext'
12
12
  require 'pp'
13
13
  require 'awesome_print'
14
+ require 'io/console'
14
15
 
15
16
  require_all "#{File.dirname(__FILE__)}/../patches"
16
17
  require_all "#{File.dirname(__FILE__)}/../lib/objects"
@@ -1,3 +1,3 @@
1
1
  module Bcome
2
- VERSION = '1.2.0'.freeze
2
+ VERSION = '1.3.5'.freeze
3
3
  end
@@ -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
@@ -0,0 +1,7 @@
1
+ module Bcome::Exception
2
+ class InvalidMetaDataEncryptionKey < ::Bcome::Exception::Base
3
+ def message_prefix
4
+ 'Your metadata encryption key is invalid - your metadata files are encrypted with a different key.'
5
+ end
6
+ end
7
+ 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
@@ -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
- raise ::Bcome::Exception::MissingIdentifierOnView.new(@views.inspect) unless @identifier
104
- raise ::Bcome::Exception::InvalidIdentifier.new("'#{@identifier}' contains whitespace") if @identifier =~ /\s/
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?
@@ -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 |filename|
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 = YAML.load_file(filename)
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 }.first
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
- exception_message = "#{node.identifier} is not unique within namespace #{node.parent.namespace}"
11
- raise Bcome::Exception::NodeIdentifiersMustBeUnique, exception_message
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
- ssh_cmd = ssh_driver.ssh_command.gsub("ssh", "ssh -t")
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 connection_attempt == 0 ? 'Initiating connections'.informational : 'Retrying failed connections'.warning
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
 
@@ -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
- bootstrap? && @bootstrap_settings.user ? @bootstrap_settings.user : @config[:user] ? @config[:user] : fallback_local_user
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.2.0
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: 2018-03-13 00:00:00.000000000 Z
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.1
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.1
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: 'Automation and real-time orchestration toolkit: no more re-inventing
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
- post_install_message:
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
- rubyforge_project:
293
- rubygems_version: 2.6.8
298
+ rubygems_version: 3.0.3
294
299
  signing_key:
295
300
  specification_version: 4
296
- summary: Automation and real-time orchestration toolkit
301
+ summary: A DevOps Application development framework
297
302
  test_files: []