bcome 1.3.0 → 1.3.2

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