bcome 0.7.0 → 1.0.0

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.
Files changed (173) hide show
  1. checksums.yaml +4 -4
  2. data/bin/bcome +8 -39
  3. data/lib/bcome.rb +12 -3
  4. data/lib/objects/bcome/version.rb +3 -0
  5. data/lib/objects/bootup.rb +81 -0
  6. data/lib/objects/command/local.rb +40 -0
  7. data/lib/objects/config_factory.rb +36 -0
  8. data/lib/objects/driver/base.rb +30 -0
  9. data/lib/objects/driver/bucket.rb +20 -0
  10. data/lib/objects/driver/ec2.rb +44 -0
  11. data/lib/objects/driver/static.rb +4 -0
  12. data/lib/objects/exception/argument_error_invoking_method_from_command_line.rb +7 -0
  13. data/lib/objects/exception/base.rb +15 -0
  14. data/lib/objects/exception/can_only_subselect_on_inventory.rb +7 -0
  15. data/lib/objects/exception/cannot_find_internal_registry_klass.rb +7 -0
  16. data/lib/objects/exception/cannot_find_subselection_parent.rb +7 -0
  17. data/lib/objects/exception/cant_find_key_in_cloud_tags.rb +7 -0
  18. data/lib/objects/exception/cant_find_key_in_metadata.rb +7 -0
  19. data/lib/objects/exception/cant_find_proxy_host_by_identifier.rb +7 -0
  20. data/lib/objects/exception/cant_find_proxy_host_by_namespace.rb +7 -0
  21. data/lib/objects/exception/could_not_initiate_ssh_connection.rb +7 -0
  22. data/lib/objects/exception/could_not_initiate_ssh_connection_through_backend_proxy.rb +7 -0
  23. data/lib/objects/exception/deprecation_warning.rb +11 -0
  24. data/lib/objects/exception/duplicate_command_line_argument_key.rb +7 -0
  25. data/lib/objects/exception/ec2_driver_missing_provisioning_region.rb +7 -0
  26. data/lib/objects/exception/failed_to_run_local_command.rb +7 -0
  27. data/lib/objects/exception/interactive_session_halt.rb +4 -0
  28. data/lib/objects/exception/invalid_bcome_breadcrumb.rb +7 -0
  29. data/lib/objects/exception/invalid_breadcrumb.rb +7 -0
  30. data/lib/objects/exception/invalid_context_command.rb +7 -0
  31. data/lib/objects/exception/invalid_identifier.rb +7 -0
  32. data/lib/objects/exception/invalid_machines_cache_config.rb +7 -0
  33. data/lib/objects/exception/invalid_matcher_query.rb +7 -0
  34. data/lib/objects/exception/invalid_meta_data_config.rb +7 -0
  35. data/lib/objects/exception/invalid_network_config.rb +7 -0
  36. data/lib/objects/exception/invalid_network_driver_type.rb +7 -0
  37. data/lib/objects/exception/invalid_proxy_config.rb +7 -0
  38. data/lib/objects/exception/invalid_regexp_matcher_in_registry.rb +7 -0
  39. data/lib/objects/exception/invalid_registry_arguments_type.rb +7 -0
  40. data/lib/objects/exception/invalid_registry_data_config.rb +7 -0
  41. data/lib/objects/exception/invalid_restriction_key_in_registry.rb +7 -0
  42. data/lib/objects/exception/invalid_ssh_config.rb +7 -0
  43. data/lib/objects/exception/inventories_cannot_have_subviews.rb +7 -0
  44. data/lib/objects/exception/malformed_command_line_arguments.rb +7 -0
  45. data/lib/objects/exception/method_invocation_requires_parameter.rb +7 -0
  46. data/lib/objects/exception/method_name_conflict_in_registry.rb +7 -0
  47. data/lib/objects/exception/missing_argument_for_registry_command.rb +7 -0
  48. data/lib/objects/exception/missing_description_on_view.rb +7 -0
  49. data/lib/objects/exception/missing_execute_on_registry_object.rb +7 -0
  50. data/lib/objects/exception/missing_identifier_on_view.rb +7 -0
  51. data/lib/objects/exception/missing_ip_address_on_server.rb +7 -0
  52. data/lib/objects/exception/missing_network_config.rb +7 -0
  53. data/lib/objects/exception/missing_params_for_rsync.rb +7 -0
  54. data/lib/objects/exception/missing_params_for_scp.rb +7 -0
  55. data/lib/objects/exception/missing_subselection_key.rb +7 -0
  56. data/lib/objects/exception/missing_type_on_view.rb +7 -0
  57. data/lib/objects/exception/no_node_found_for_breadcrumb.rb +7 -0
  58. data/lib/objects/exception/no_node_named_by_identifier.rb +7 -0
  59. data/lib/objects/exception/node_identifiers_must_be_unique.rb +7 -0
  60. data/lib/objects/exception/orchestration_script_does_not_exist.rb +7 -0
  61. data/lib/objects/exception/proxy_host_node_does_not_have_public_ip_address.rb +7 -0
  62. data/lib/objects/exception/unknown_method_for_namespace.rb +7 -0
  63. data/lib/objects/interactive/session.rb +45 -0
  64. data/lib/objects/interactive/session_item/base.rb +36 -0
  65. data/lib/objects/interactive/session_item/capture_input.rb +20 -0
  66. data/lib/objects/interactive/session_item/transparent_ssh.rb +111 -0
  67. data/lib/objects/modules/context.rb +13 -0
  68. data/lib/objects/modules/registry_management.rb +14 -0
  69. data/lib/objects/modules/ui_output.rb +9 -0
  70. data/lib/objects/modules/workspace_commands.rb +198 -0
  71. data/lib/objects/modules/workspace_menu.rb +128 -0
  72. data/lib/objects/node/attributes.rb +47 -0
  73. data/lib/objects/node/base.rb +248 -0
  74. data/lib/objects/node/cache_handler.rb +24 -0
  75. data/lib/objects/node/collection.rb +51 -0
  76. data/lib/objects/node/factory.rb +108 -0
  77. data/lib/objects/node/inventory/base.rb +88 -0
  78. data/lib/objects/node/inventory/defined.rb +112 -0
  79. data/lib/objects/node/inventory/subselect.rb +54 -0
  80. data/lib/objects/node/meta/base.rb +36 -0
  81. data/lib/objects/node/meta/cloud.rb +4 -0
  82. data/lib/objects/node/meta/local.rb +4 -0
  83. data/lib/objects/node/meta_data_factory.rb +23 -0
  84. data/lib/objects/node/meta_data_loader.rb +32 -0
  85. data/lib/objects/node/resources/base.rb +98 -0
  86. data/lib/objects/node/resources/inventory.rb +25 -0
  87. data/lib/objects/node/resources/sub_inventory.rb +56 -0
  88. data/lib/objects/node/server/base.rb +218 -0
  89. data/lib/objects/node/server/dynamic.rb +39 -0
  90. data/lib/objects/node/server/static.rb +31 -0
  91. data/lib/objects/orchestration/base.rb +13 -0
  92. data/lib/objects/orchestrator.rb +24 -0
  93. data/lib/objects/parser/bread_crumb.rb +31 -0
  94. data/lib/objects/progress_bar.rb +30 -0
  95. data/lib/objects/registry/arguments/base.rb +36 -0
  96. data/lib/objects/registry/arguments/command_line.rb +35 -0
  97. data/lib/objects/registry/arguments/console.rb +15 -0
  98. data/lib/objects/registry/command/base.rb +66 -0
  99. data/lib/objects/registry/command/external.rb +55 -0
  100. data/lib/objects/registry/command/group.rb +76 -0
  101. data/lib/objects/registry/command/internal.rb +34 -0
  102. data/lib/objects/registry/command_list.rb +33 -0
  103. data/lib/objects/registry/loader.rb +70 -0
  104. data/lib/objects/ssh/bootstrap.rb +19 -0
  105. data/lib/objects/ssh/command.rb +46 -0
  106. data/lib/objects/ssh/command_exec.rb +65 -0
  107. data/lib/objects/ssh/connection_handler.rb +101 -0
  108. data/lib/objects/ssh/driver.rb +204 -0
  109. data/lib/objects/ssh/proxy_data.rb +56 -0
  110. data/lib/objects/ssh/script_exec.rb +43 -0
  111. data/lib/objects/system/local.rb +30 -0
  112. data/lib/objects/workspace.rb +75 -0
  113. data/patches/irb.rb +20 -7
  114. data/patches/string.rb +75 -0
  115. data/patches/string_stylesheet.rb +61 -0
  116. metadata +175 -93
  117. data/bin/bcome-bash-setup +0 -18
  118. data/bin/bcome-setup +0 -43
  119. data/bin/boot.rb +0 -148
  120. data/bin/boot_no_shell.rb +0 -3
  121. data/filters/ec2_filter.rb +0 -12
  122. data/lib/bcome/version.rb +0 -3
  123. data/lib/become_object.rb +0 -111
  124. data/lib/boot.rb +0 -13
  125. data/lib/command.rb +0 -56
  126. data/lib/context_functions.rb +0 -65
  127. data/lib/filters/base.rb +0 -10
  128. data/lib/filters/ec2_filter.rb +0 -4
  129. data/lib/functions.rb +0 -70
  130. data/lib/helpers/command_helper.rb +0 -13
  131. data/lib/helpers/environment_ssh.rb +0 -122
  132. data/lib/helpers/fog_helper.rb +0 -108
  133. data/lib/helpers/instance_command.rb +0 -71
  134. data/lib/helpers/instance_ssh.rb +0 -83
  135. data/lib/helpers/selections.rb +0 -117
  136. data/lib/interactive/interactive_session_halt.rb +0 -4
  137. data/lib/interactive/session.rb +0 -44
  138. data/lib/interactive/session_item/base.rb +0 -30
  139. data/lib/interactive/session_item/transparent_ssh.rb +0 -133
  140. data/lib/nodes/base.rb +0 -60
  141. data/lib/nodes/environment.rb +0 -30
  142. data/lib/nodes/estate.rb +0 -44
  143. data/lib/nodes/instance.rb +0 -94
  144. data/lib/nodes/platform.rb +0 -17
  145. data/lib/nodes/view.rb +0 -31
  146. data/lib/object.rb +0 -21
  147. data/lib/orchestrator/command_group/base.rb +0 -56
  148. data/lib/orchestrator/command_group/custom.rb +0 -13
  149. data/lib/orchestrator/command_group/direct.rb +0 -53
  150. data/lib/orchestrator/direct_command/group.rb +0 -21
  151. data/lib/orchestrator/direct_command/instance.rb +0 -19
  152. data/lib/orchestrator/factory.rb +0 -38
  153. data/lib/orchestrator/loader.rb +0 -47
  154. data/lib/orchestrator/node_target/all.rb +0 -14
  155. data/lib/orchestrator/node_target/all_excluding_roles.rb +0 -14
  156. data/lib/orchestrator/node_target/all_with_roles.rb +0 -14
  157. data/lib/orchestrator/node_target/base.rb +0 -43
  158. data/lib/orchestrator/node_target/single.rb +0 -12
  159. data/lib/orchestrator/recipe.rb +0 -60
  160. data/lib/orchestrator/registry.rb +0 -37
  161. data/lib/orchestrator/validate_and_set.rb +0 -10
  162. data/lib/patches/string.rb +0 -86
  163. data/lib/progress_bar.rb +0 -31
  164. data/lib/render_irb.rb +0 -53
  165. data/lib/scp.rb +0 -40
  166. data/lib/ssh.rb +0 -51
  167. data/lib/stack/base.rb +0 -148
  168. data/lib/stack/environment.rb +0 -222
  169. data/lib/stack/estate.rb +0 -50
  170. data/lib/stack/instance.rb +0 -130
  171. data/lib/stack/platform.rb +0 -74
  172. data/lib/stack/view.rb +0 -56
  173. data/lib/workspace_context.rb +0 -40
@@ -0,0 +1,25 @@
1
+ module Bcome::Node::Resources
2
+ class Inventory < Bcome::Node::Resources::Base
3
+ def <<(node)
4
+ if existing_node = for_identifier(node.identifier)
5
+ if existing_node.static_server? && node.dynamic_server?
6
+ # We've got a duplicate, but we'll treat the remote node as authoritative
7
+ # We remove the static server from our selection
8
+ @nodes.delete(existing_node)
9
+ else
10
+ exception_message = "#{node.identifier} is not unique within namespace #{node.parent.namespace}"
11
+ raise Bcome::Exception::NodeIdentifiersMustBeUnique, exception_message
12
+ end
13
+ end
14
+ @nodes << node
15
+ end
16
+
17
+ def dynamic_nodes
18
+ active.select(&:dynamic_server?)
19
+ end
20
+
21
+ def unset!
22
+ @nodes = []
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,56 @@
1
+ module Bcome::Node::Resources
2
+ class SubselectInventory < Bcome::Node::Resources::Inventory
3
+ def initialize(config)
4
+ @config = config
5
+ super
6
+ run_subselect
7
+ end
8
+
9
+ def run_subselect
10
+ parent_inventory.load_nodes unless parent_inventory.nodes_loaded?
11
+ new_set = parent_inventory.resources.nodes
12
+
13
+ # Filter by ec2_filters
14
+ new_set = filter_by_tags(new_set)
15
+
16
+ @nodes = new_set
17
+ end
18
+
19
+ def update_nodes(inventory)
20
+ new_set = []
21
+
22
+ @nodes.collect do |node|
23
+ new_node = node.dup_with_new_parent(inventory)
24
+ if inventory.override_server_identifier?
25
+ new_node.identifier =~ /#{inventory.override_identifier}/
26
+ new_node.update_identifier(Regexp.last_match(1)) if Regexp.last_match(1)
27
+ end
28
+ new_set << new_node
29
+ end
30
+ @nodes = new_set
31
+ end
32
+
33
+ def filter_by_tags(nodes)
34
+ tag_filters.each do |key, values|
35
+ nodes = nodes.select { |node| node.has_tagged_value?(key, values) }
36
+ end
37
+ nodes
38
+ end
39
+
40
+ def parent_crumb
41
+ @config[:parent_crumb]
42
+ end
43
+
44
+ def filters
45
+ @config[:filters]
46
+ end
47
+
48
+ def tag_filters
49
+ filters[:by_tag] ? filters[:by_tag] : {}
50
+ end
51
+
52
+ def parent_inventory
53
+ @config[:parent_inventory]
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,218 @@
1
+ module Bcome::Node::Server
2
+ class Base < Bcome::Node::Base
3
+
4
+ attr_reader :origin_object_id
5
+
6
+ def initialize(*params)
7
+ super
8
+ # Set the object_id - sub inventories dup servers into new collections. This allows us to spot duplicates when interacting with collections
9
+ @origin_object_id = object_id
10
+ @bootstrap = false
11
+ end
12
+
13
+ def bootstrap?
14
+ @bootstrap ? true : false
15
+ end
16
+
17
+ def toggle_bootstrap(set_to = (@bootstrap ? false : true))
18
+ @bootstrap = set_to
19
+ puts "Bootstrap is #{bootstrap? ? "on" : "off" }".informational
20
+ end
21
+
22
+ def dup_with_new_parent(new_parent)
23
+ new_node = self.clone
24
+ new_node.update_parent(new_parent)
25
+ new_node
26
+ end
27
+
28
+ def update_parent(new_parent)
29
+ @parent = new_parent
30
+ end
31
+
32
+ def tags
33
+ data_print_from_hash(cloud_tags.data, "Tags")
34
+ end
35
+
36
+ def cloud_tags
37
+ @generated_tags ||= do_generate_cloud_tags
38
+ end
39
+
40
+ def has_tagged_value?(key, values)
41
+ matchers = { :key => key, :values => values }
42
+ cloud_tags.has_key_and_value?(matchers)
43
+ end
44
+
45
+ def update_identifier(new_identifier)
46
+ @identifier = new_identifier
47
+ end
48
+
49
+ def do_generate_cloud_tags
50
+ raise "Should be overidden"
51
+ end
52
+
53
+ def type
54
+ "server"
55
+ end
56
+
57
+ def machines
58
+ [self]
59
+ end
60
+
61
+ def server?
62
+ true
63
+ end
64
+
65
+ def requires_description?
66
+ false
67
+ end
68
+
69
+ def requires_type?
70
+ false
71
+ end
72
+
73
+ def enabled_menu_items
74
+ (super + [:get, :ssh, :tags]) - [:enable, :disable, :enable!, :disable!]
75
+ end
76
+
77
+ def menu_items
78
+ base_items = super.dup
79
+ base_items[:tags] = {
80
+ description: "print out remote EC2 tags"
81
+ }
82
+ base_items[:ssh] = {
83
+ description: "initiate an ssh connection to this server",
84
+ }
85
+ base_items[:get] = {
86
+ description: "Download a file or directory",
87
+ console_only: false,
88
+ usage: "get \"/remote/path\", \"/local/path\"",
89
+ terminal_usage: "get \"/remote/path\" \"/local/path\""
90
+ }
91
+
92
+
93
+ base_items
94
+ end
95
+
96
+ def open_ssh_connection
97
+ ssh_driver.ssh_connection
98
+ end
99
+
100
+ def close_ssh_connection
101
+ ssh_driver.close_ssh_connection
102
+ end
103
+
104
+ def has_ssh_connection?
105
+ ssh_driver.has_open_ssh_con?
106
+ end
107
+
108
+ def has_no_ssh_connection?
109
+ !has_ssh_connection?
110
+ end
111
+
112
+ def ssh
113
+ ssh_driver.do_ssh
114
+ end
115
+
116
+ def execute_script(script_name)
117
+ command_result = ::Bcome::Ssh::ScriptExec.execute(self, script_name)
118
+ return command_result
119
+ end
120
+
121
+ def rsync(local_path, remote_path)
122
+ ssh_driver.rsync(local_path, remote_path)
123
+ end
124
+
125
+ def put(local_path, remote_path)
126
+ ssh_driver.put(local_path, remote_path)
127
+ end
128
+
129
+ def get(remote_path, local_path)
130
+ ssh_driver.get(remote_path, local_path)
131
+ end
132
+
133
+ def ls
134
+ puts "\n" + visual_hierarchy.hierarchy + "\n"
135
+ puts pretty_description
136
+ end
137
+ alias :lsa :ls
138
+
139
+ def ping
140
+ ping_result = ssh_driver.ping
141
+ print_ping_result(ping_result)
142
+ end
143
+
144
+ def print_ping_result(ping_result = { success: true })
145
+ result = {
146
+ namespace => {
147
+ "connection" => ping_result[:success] ? "success" : "failed",
148
+ "ssh_config" => ssh_driver.pretty_config_details
149
+ }
150
+ }
151
+
152
+ unless ping_result[:success]
153
+ result[namespace]["error"] = ping_result[:error].message
154
+ end
155
+
156
+ colour = ping_result[:success] ? :green : :red
157
+
158
+ ap result, {
159
+ :color => {
160
+ hash: colour,
161
+ symbol: colour,
162
+ string: colour,
163
+ keyword: colour,
164
+ variable: colour,
165
+ array: colour
166
+ }
167
+ }
168
+ end
169
+
170
+ def list_attributes
171
+ attribs = {
172
+ "identifier": :identifier,
173
+ "internal ip": :internal_ip_address,
174
+ "public ip": :public_ip_address,
175
+ }
176
+
177
+ attribs.merge!("description": :description ) if has_description?
178
+ attribs
179
+ end
180
+
181
+ def cache_data
182
+ d = { identifier: identifier }
183
+ d[:internal_ip_address] = internal_ip_address if internal_ip_address
184
+ d[:public_ip_address] = public_ip_address if public_ip_address
185
+ d[:description] = description if description
186
+ d[:cloud_tags] = cloud_tags
187
+ d
188
+ end
189
+
190
+ def do_run(raw_commands)
191
+ raw_commands = raw_commands.is_a?(String) ? [raw_commands] : raw_commands
192
+ commands = raw_commands.collect{|raw_command| ::Bcome::Ssh::Command.new({ :node => self, :raw => raw_command }) }
193
+ command_exec = ::Bcome::Ssh::CommandExec.new(commands)
194
+ command_exec.execute!
195
+ commands.each {|c| c.unset_node }
196
+ return commands
197
+ end
198
+
199
+ def run(*raw_commands)
200
+ raise ::Bcome::Exception::MethodInvocationRequiresParameter.new "Please specify commands when invoking 'run'" if raw_commands.empty?
201
+ commands = do_run(raw_commands)
202
+ commands
203
+ end
204
+
205
+ def has_description?
206
+ !@description.nil?
207
+ end
208
+
209
+ def static_server?
210
+ false
211
+ end
212
+
213
+ def dynamic_server?
214
+ !static_server?
215
+ end
216
+
217
+ end
218
+ end
@@ -0,0 +1,39 @@
1
+ module Bcome::Node::Server
2
+ class Dynamic < Bcome::Node::Server::Base
3
+ class << self
4
+ def to_s
5
+ 'dynamic server'
6
+ end
7
+
8
+ def new_from_fog_instance(fog_instance, parent)
9
+ identifier = fog_instance.tags['Name']
10
+
11
+ if parent.override_server_identifier?
12
+ identifier =~ /#{parent.override_identifier}/
13
+ identifier = Regexp.last_match(1) if Regexp.last_match(1)
14
+ end
15
+
16
+ params = {
17
+ identifier: identifier,
18
+ internal_ip_address: fog_instance.private_ip_address,
19
+ public_ip_address: fog_instance.public_ip_address,
20
+ role: fog_instance.tags['function'],
21
+ description: "EC2 server - #{identifier}",
22
+ ec2_server: fog_instance
23
+ }
24
+
25
+ new(parent: parent,
26
+ views: params)
27
+ end
28
+ end
29
+
30
+ def do_generate_cloud_tags
31
+ raw_tags = ec2_server ? ec2_server.tags.deep_symbolize_keys : {}
32
+ ::Bcome::Node::Meta::Cloud.new(raw_tags)
33
+ end
34
+
35
+ def ec2_server
36
+ views[:ec2_server]
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,31 @@
1
+ module Bcome::Node::Server
2
+ class Static < Bcome::Node::Server::Base
3
+ def self.to_s
4
+ 'static server'
5
+ end
6
+
7
+ def initialize(params)
8
+ config = params[:views]
9
+ @identifier = config[:identifier]
10
+ @public_ip_address = config[:public_ip_address]
11
+ @internal_ip_address = config[:internal_ip_address]
12
+ @cloud_tags = config[:cloud_tags]
13
+ verify_we_have_at_least_one_interface(config)
14
+ super
15
+ end
16
+
17
+ attr_reader :cloud_tags
18
+
19
+ def verify_we_have_at_least_one_interface(config)
20
+ raise Bcome::Exception::MissingIpaddressOnServer, config unless has_at_least_one_interface?
21
+ end
22
+
23
+ def has_at_least_one_interface?
24
+ @public_ip_address || @internal_ip_address
25
+ end
26
+
27
+ def static_server?
28
+ true
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,13 @@
1
+ module Bcome::Orchestration
2
+ class Base
3
+ def initialize(node, arguments)
4
+ @node = node
5
+ @arguments = arguments
6
+ end
7
+
8
+ def do_execute
9
+ raise Bcome::Exception::MissingExecuteOnRegistryObject, self.class.to_s unless respond_to?(:execute)
10
+ execute
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,24 @@
1
+ class ::Bcome::Orchestrator
2
+ include ::Singleton
3
+
4
+ attr_reader :context
5
+
6
+ def initialize
7
+ @silence = false
8
+ end
9
+
10
+ def silence_command_output!
11
+ @silence = true
12
+ end
13
+
14
+ def command_output_silenced?
15
+ @silence == true
16
+ end
17
+
18
+ def get(breadcrumb = nil)
19
+ context = ::Bcome::Bootup.traverse(breadcrumb)
20
+ raise Bcome::Exception::NoNodeFoundForBreadcrumb, breadcrumb unless context
21
+ context.load_nodes if context.inventory? && !context.nodes_loaded?
22
+ context
23
+ end
24
+ end
@@ -0,0 +1,31 @@
1
+ module Bcome::Parser
2
+ class BreadCrumb
3
+ attr_reader :crumbs
4
+
5
+ class << self
6
+ def parse(raw_crumbs)
7
+ parser = new(raw_crumbs)
8
+ parser.parse!
9
+ end
10
+ end
11
+
12
+ def initialize(raw_crumbs)
13
+ @raw_crumbs = raw_crumbs.to_s
14
+
15
+ if @raw_crumbs.empty?
16
+ @crumbs = []
17
+ else
18
+ validate!
19
+ parse!
20
+ end
21
+ end
22
+
23
+ def parse!
24
+ @crumbs ||= @raw_crumbs.split(':')
25
+ end
26
+
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
29
+ end
30
+ end
31
+ end