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 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: []