bcome 1.3.0 → 1.3.2

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
2
  SHA1:
3
- metadata.gz: 5175174eb843f0f210c02ca12ded004f75d77368
4
- data.tar.gz: ffd09a8ba7edc0df044a17b55db02d3176e0feee
3
+ metadata.gz: 8e5a43822b034da12c4c66c06a83ecba12ddc7f7
4
+ data.tar.gz: 903781f913e606b995658e265f6b9eca35d7c995
5
5
  SHA512:
6
- metadata.gz: 855151285002aa2270d025f97ff85c8beead1f3b7aabcad797bcd8eb03885c38f296a2ad1f1e01c2b1be6631a3d9ba724f57fae6046e6132f8ec8e95ae3e9ee9
7
- data.tar.gz: 09500dc2f2ac59b5a1c70252c07ca0bbc665f32eac78a52fc1ded53d3e9d79ba09d0367ac56921eab5d56d9ddd300665924640733d3e0d436150badbec7f55db
6
+ metadata.gz: 8dce77759769673ecfb59617ce4cc884fb52a21c46c9b6576bf7a82deda540d274882351c1818db3eed2a62f64f0675b48a9f06a5c620e25d60af4bc08c2eba2
7
+ data.tar.gz: 2641aed669f870e423c6845c03fe08af7069264c674799313f763132541811c4f6a63de7f99fe898869d69f89890518b9a11120fe67d20bd734b3af1526ef8f3
@@ -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.3.0'.freeze
2
+ VERSION = '1.3.2'.freeze
3
3
  end
@@ -23,8 +23,8 @@ module Bcome
23
23
  end
24
24
 
25
25
  def prompt_for_key
26
- message = "Please enter an encryption key (and if your data is already encrypted, you must provide the same key): ".informational
27
- @key = ::Readline.readline("\n#{message}", true).squeeze('').to_s
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
28
  puts "\n"
29
29
  end
30
30
 
@@ -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
@@ -57,6 +57,10 @@ module Bcome::Node
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
@@ -108,8 +112,14 @@ module Bcome::Node
108
112
 
109
113
  def validate_identifier
110
114
  @identifier = DEFAULT_IDENTIFIER if is_top_level_node? && !@identifier && !is_a?(::Bcome::Node::Server::Base)
111
- raise ::Bcome::Exception::MissingIdentifierOnView.new(@views.inspect) unless @identifier
112
- raise ::Bcome::Exception::InvalidIdentifier.new("'#{@identifier}' contains whitespace") if @identifier =~ /\s/
115
+
116
+ @identifier = "NO-ID_#{Time.now.to_i}" unless @identifier
117
+
118
+ #raise ::Bcome::Exception::MissingIdentifierOnView.new(@views.inspect) unless @identifier
119
+ @identifier.gsub!(/\s/, "_") # Remove whitespace
120
+ @identifier.gsub!("-", "_") # change hyphens to undescores, hyphens don't play well in var names in irb
121
+
122
+ #raise ::Bcome::Exception::InvalidIdentifier.new("'#{@identifier}' contains whitespace") if @identifier =~ /\s/
113
123
  end
114
124
 
115
125
  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
@@ -17,12 +17,25 @@ module Bcome::Node
17
17
  end
18
18
 
19
19
  def data_for_namespace(namespace)
20
- 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
+ parser = Bcome::Terraform::Parser.new(namespace)
26
+ attributes = parser.attributes
27
+ if attributes.keys.any?
28
+ {
29
+ "terraform_attributes" => attributes
30
+ }
31
+ else
32
+ {}
33
+ end
21
34
  end
22
35
 
23
36
  def prompt_for_decryption_key
24
- message = "Please enter your metadata encryption key: ".informational
25
- @decryption_key = ::Readline.readline("\n#{message}", true).squeeze('').to_s
37
+ print "\nEnter your decryption key: ".informational
38
+ @decryption_key = STDIN.noecho(&:gets).chomp
26
39
  end
27
40
 
28
41
  def load_file_data_for(filepath)
@@ -48,7 +61,7 @@ module Bcome::Node
48
61
  raise Bcome::Exception::InvalidMetaDataConfig, "Error: #{e.message}"
49
62
  end
50
63
  end
51
- all_meta_data
64
+ return all_meta_data
52
65
  end
53
66
  end
54
67
  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
@@ -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
@@ -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,34 @@
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
+ raise "No terraform tstate for this environment" unless config_exists?
26
+ JSON.parse(File.read(config_path))
27
+ end
28
+
29
+ def resources
30
+ return {} unless config_exists?
31
+ return config["modules"][0]["resources"]
32
+ end
33
+ end
34
+ end
@@ -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
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.3.0
4
+ version: 1.3.2
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-06-05 00:00:00.000000000 Z
11
+ date: 2018-10-29 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: An orchestration framework for AWS EC2
149
146
  email:
150
147
  - guillaume@webzakimbo.com
151
148
  executables:
@@ -246,6 +243,7 @@ files:
246
243
  - lib/objects/node/server/dynamic.rb
247
244
  - lib/objects/node/server/static.rb
248
245
  - lib/objects/orchestration/base.rb
246
+ - lib/objects/orchestration/interactive_terraform.rb
249
247
  - lib/objects/orchestrator.rb
250
248
  - lib/objects/parser/bread_crumb.rb
251
249
  - lib/objects/progress_bar.rb
@@ -268,12 +266,14 @@ files:
268
266
  - lib/objects/ssh/script_exec.rb
269
267
  - lib/objects/ssh/tunnel/local_port_forward.rb
270
268
  - lib/objects/system/local.rb
269
+ - lib/objects/terraform/parser.rb
270
+ - lib/objects/terraform/state.rb
271
271
  - lib/objects/workspace.rb
272
272
  - patches/irb.rb
273
273
  - patches/string-encrypt.rb
274
274
  - patches/string.rb
275
275
  - patches/string_stylesheet.rb
276
- homepage: https://github.com/webzakimbo/bcome-kontrol
276
+ homepage: https://bcome-kontrol.readthedocs.io/en/latest/
277
277
  licenses:
278
278
  - GPL-3.0
279
279
  metadata: {}
@@ -296,5 +296,6 @@ rubyforge_project:
296
296
  rubygems_version: 2.6.8
297
297
  signing_key:
298
298
  specification_version: 4
299
- summary: Automation and real-time orchestration toolkit
299
+ summary: Organise your world, then integrate absolutely whatever you want in pure
300
+ Ruby.
300
301
  test_files: []