foreman_deployments 0.0.1

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 (133) hide show
  1. checksums.yaml +15 -0
  2. data/LICENSE +619 -0
  3. data/README.md +52 -0
  4. data/Rakefile +30 -0
  5. data/app/controllers/foreman_deployments/api/v2/base_controller.rb +25 -0
  6. data/app/controllers/foreman_deployments/api/v2/deployments_controller.rb +102 -0
  7. data/app/controllers/foreman_deployments/api/v2/stacks_controller.rb +52 -0
  8. data/app/controllers/foreman_deployments/create_resources_controller.rb +62 -0
  9. data/app/controllers/foreman_deployments/deployments_controller.rb +5 -0
  10. data/app/controllers/foreman_deployments/stacks_controller.rb +5 -0
  11. data/app/lib/foreman_deployments/base_dereference_visitor.rb +55 -0
  12. data/app/lib/foreman_deployments/config.rb +16 -0
  13. data/app/lib/foreman_deployments/config/array.rb +91 -0
  14. data/app/lib/foreman_deployments/config/configurator.rb +23 -0
  15. data/app/lib/foreman_deployments/config/hash.rb +81 -0
  16. data/app/lib/foreman_deployments/config/load_visitor.rb +23 -0
  17. data/app/lib/foreman_deployments/config/merge_visitor.rb +23 -0
  18. data/app/lib/foreman_deployments/config/save_visitor.rb +23 -0
  19. data/app/lib/foreman_deployments/inputs/base_input_definition.rb +39 -0
  20. data/app/lib/foreman_deployments/inputs/value.rb +25 -0
  21. data/app/lib/foreman_deployments/planner_visitor.rb +26 -0
  22. data/app/lib/foreman_deployments/registry.rb +67 -0
  23. data/app/lib/foreman_deployments/stack_definition.rb +37 -0
  24. data/app/lib/foreman_deployments/stack_parser.rb +121 -0
  25. data/app/lib/foreman_deployments/task_reference.rb +48 -0
  26. data/app/lib/foreman_deployments/tasks/base_action.rb +6 -0
  27. data/app/lib/foreman_deployments/tasks/base_definition.rb +72 -0
  28. data/app/lib/foreman_deployments/tasks/creation_task_definition.rb +68 -0
  29. data/app/lib/foreman_deployments/tasks/host_creation_task_definition.rb +44 -0
  30. data/app/lib/foreman_deployments/tasks/search_task_definition.rb +55 -0
  31. data/app/lib/foreman_deployments/tasks/stack_deploy_action.rb +10 -0
  32. data/app/lib/foreman_deployments/tasks/wait_until_built_task_definition.rb +65 -0
  33. data/app/lib/foreman_deployments/validation/dereference_visitor.rb +18 -0
  34. data/app/lib/foreman_deployments/validation/remove_ids_visitor.rb +59 -0
  35. data/app/lib/foreman_deployments/validation/validation_error.rb +12 -0
  36. data/app/lib/foreman_deployments/validation/validation_result.rb +26 -0
  37. data/app/lib/foreman_deployments/validation/validation_visitor.rb +20 -0
  38. data/app/lib/foreman_deployments/validation/validator.rb +29 -0
  39. data/app/models/foreman_deployments/concerns/belongs_to_single_taxonomy.rb +42 -0
  40. data/app/models/foreman_deployments/concerns/belongs_to_stack_taxonomy.rb +24 -0
  41. data/app/models/foreman_deployments/configuration.rb +26 -0
  42. data/app/models/foreman_deployments/deployment.rb +57 -0
  43. data/app/models/foreman_deployments/resource_models/create_resource.rb +18 -0
  44. data/app/models/foreman_deployments/stack.rb +20 -0
  45. data/app/views/foreman_deployments/api/v2/deployments/base.json.rabl +3 -0
  46. data/app/views/foreman_deployments/api/v2/deployments/create.json.rabl +3 -0
  47. data/app/views/foreman_deployments/api/v2/deployments/index.json.rabl +3 -0
  48. data/app/views/foreman_deployments/api/v2/deployments/main.json.rabl +5 -0
  49. data/app/views/foreman_deployments/api/v2/deployments/merge_configuration.json.rabl +3 -0
  50. data/app/views/foreman_deployments/api/v2/deployments/replace_configuration.json.rabl +3 -0
  51. data/app/views/foreman_deployments/api/v2/deployments/run.json.rabl +3 -0
  52. data/app/views/foreman_deployments/api/v2/deployments/show.json.rabl +11 -0
  53. data/app/views/foreman_deployments/api/v2/deployments/update.json.rabl +3 -0
  54. data/app/views/foreman_deployments/api/v2/stacks/base.json.rabl +3 -0
  55. data/app/views/foreman_deployments/api/v2/stacks/create.json.rabl +3 -0
  56. data/app/views/foreman_deployments/api/v2/stacks/index.json.rabl +3 -0
  57. data/app/views/foreman_deployments/api/v2/stacks/main.json.rabl +3 -0
  58. data/app/views/foreman_deployments/api/v2/stacks/show.json.rabl +7 -0
  59. data/app/views/foreman_deployments/api/v2/stacks/update.json.rabl +3 -0
  60. data/app/views/foreman_deployments/create_resources/new.html.erb +6 -0
  61. data/config/routes.rb +41 -0
  62. data/db/migrate/20150623140612_create_stacks.rb +10 -0
  63. data/db/migrate/20150814092932_create_deployment.rb +20 -0
  64. data/db/migrate/20150916133305_add_task_to_deployments.rb +5 -0
  65. data/db/migrate/20150917130618_add_taxonomy_to_deployments.rb +8 -0
  66. data/db/seeds.d/03-permissions.rb +14 -0
  67. data/doc/deployment_process.md +112 -0
  68. data/doc/design/capsule_stack.puml +51 -0
  69. data/doc/design/complete_stack.puml +17 -0
  70. data/doc/design/config_resource_overview.puml +15 -0
  71. data/doc/design/design.md +230 -0
  72. data/doc/design/diagrams/capsule_stack.png +0 -0
  73. data/doc/design/diagrams/capsule_stack.svg +1 -0
  74. data/doc/design/diagrams/complete_stack.png +0 -0
  75. data/doc/design/diagrams/complete_stack.svg +1 -0
  76. data/doc/design/diagrams/config_resource_overview.png +0 -0
  77. data/doc/design/diagrams/config_resource_overview.svg +1 -0
  78. data/doc/design/diagrams/ordered_resource_overview.png +0 -0
  79. data/doc/design/diagrams/ordered_resource_overview.svg +1 -0
  80. data/doc/design/diagrams/overview.png +0 -0
  81. data/doc/design/diagrams/overview.svg +1 -0
  82. data/doc/design/diagrams/overview_class.png +0 -0
  83. data/doc/design/diagrams/overview_class.svg +1 -0
  84. data/doc/design/diagrams/sat_stack.png +0 -0
  85. data/doc/design/diagrams/sat_stack.svg +1 -0
  86. data/doc/design/diagrams/solr_usecase.png +0 -0
  87. data/doc/design/diagrams/solr_usecase.svg +1 -0
  88. data/doc/design/examples.md +192 -0
  89. data/doc/design/generate-diagrams.sh +7 -0
  90. data/doc/design/implementation.md +128 -0
  91. data/doc/design/ordered_resource_overview.puml +15 -0
  92. data/doc/design/overview.puml +42 -0
  93. data/doc/design/overview_class.puml +64 -0
  94. data/doc/design/resources.md +134 -0
  95. data/doc/design/sat_stack.puml +37 -0
  96. data/doc/design/shared.puml +171 -0
  97. data/doc/design/solr_usecase.puml +189 -0
  98. data/doc/design/tasks.md +74 -0
  99. data/doc/design/user_interfaces.md +189 -0
  100. data/doc/introduction.md +84 -0
  101. data/doc/writing_stacks.md +102 -0
  102. data/lib/foreman_deployments.rb +7 -0
  103. data/lib/foreman_deployments/engine.rb +94 -0
  104. data/lib/foreman_deployments/monkey_patches.rb +9 -0
  105. data/lib/foreman_deployments/version.rb +3 -0
  106. data/lib/tasks/foreman_deployments_tasks.rake +22 -0
  107. data/locale/Makefile +62 -0
  108. data/test/factories/foreman_deployments.rb +70 -0
  109. data/test/functional/api/v2/deployments_controller_test.rb +318 -0
  110. data/test/functional/api/v2/stacks_controller_test.rb +140 -0
  111. data/test/test_plugin_helper.rb +14 -0
  112. data/test/unit/lib/config/array_test.rb +217 -0
  113. data/test/unit/lib/config/configurator_test.rb +66 -0
  114. data/test/unit/lib/config/hash_test.rb +178 -0
  115. data/test/unit/lib/inputs/value_test.rb +47 -0
  116. data/test/unit/lib/registry_test.rb +117 -0
  117. data/test/unit/lib/stack_definition_test.rb +54 -0
  118. data/test/unit/lib/stack_parser_test.rb +129 -0
  119. data/test/unit/lib/task_reference_test.rb +63 -0
  120. data/test/unit/lib/tasks/base_definition_test.rb +137 -0
  121. data/test/unit/lib/tasks/creation_task_definition_test.rb +57 -0
  122. data/test/unit/lib/tasks/host_creation_task_definition_test.rb +10 -0
  123. data/test/unit/lib/tasks/search_task_definition_test.rb +49 -0
  124. data/test/unit/lib/tasks/stack_deploy_action_test.rb +83 -0
  125. data/test/unit/lib/tasks/wait_until_built_task_definition_test.rb +71 -0
  126. data/test/unit/lib/validation/dereference_visitor_test.rb +48 -0
  127. data/test/unit/lib/validation/remove_ids_visitor_test.rb +90 -0
  128. data/test/unit/lib/validation/validation_result_test.rb +40 -0
  129. data/test/unit/lib/validation/validation_visitor_test.rb +67 -0
  130. data/test/unit/model/configuration_test.rb +88 -0
  131. data/test/unit/model/deployment_test.rb +159 -0
  132. data/test/unit/model/stack_test.rb +33 -0
  133. metadata +241 -0
@@ -0,0 +1,23 @@
1
+ module ForemanDeployments
2
+ module Config
3
+ class Configurator
4
+ def initialize(stack_definition)
5
+ @stack_definition = stack_definition
6
+ end
7
+
8
+ def configure(configuration)
9
+ ForemanDeployments::Config::LoadVisitor.load(@stack_definition, configuration)
10
+ end
11
+
12
+ def merge(*configurations)
13
+ configurations.each do |c|
14
+ ForemanDeployments::Config::MergeVisitor.merge(@stack_definition, c)
15
+ end
16
+ end
17
+
18
+ def dump(configuration)
19
+ ForemanDeployments::Config::SaveVisitor.save(@stack_definition, configuration)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,81 @@
1
+ module ForemanDeployments
2
+ module Config
3
+ class Hash < ActiveSupport::HashWithIndifferentAccess
4
+ def merge_configuration(values)
5
+ values.each do |key, value|
6
+ configure_key(key, value, :merge_configuration)
7
+ end
8
+ end
9
+
10
+ def configure(values)
11
+ configuration_storage.clear
12
+ values.each do |key, value|
13
+ configure_key(key, value, :configure)
14
+ end
15
+ end
16
+
17
+ def configured
18
+ result = configuration_storage.deep_clone
19
+ each do |key, item|
20
+ if item.respond_to?(:configured)
21
+ result[key.to_s] = item.configured
22
+ else
23
+ result[key.to_s] = item
24
+ end
25
+ end
26
+ result
27
+ end
28
+
29
+ def configuration
30
+ result = configuration_storage.deep_clone
31
+ each do |key, value|
32
+ if value.respond_to?(:configuration)
33
+ result[key.to_s] = value.configuration unless value.configuration.nil?
34
+ end
35
+ end
36
+ result.empty? ? nil : result
37
+ end
38
+
39
+ def transform!(&block)
40
+ keys.each do |key|
41
+ new_key, new_value = block.call(key, self[key])
42
+ self[key] = new_value
43
+ self[new_key] = delete(key) if new_key != key
44
+ end
45
+ self
46
+ end
47
+
48
+ protected
49
+
50
+ def configuration_storage
51
+ @configuration_storage ||= {}
52
+ end
53
+
54
+ def configure_key(key, value, method)
55
+ preconfigured = self[key]
56
+
57
+ if preconfigured.nil?
58
+ update_configuration_storage(key, value)
59
+ elsif preconfigured.respond_to?(method)
60
+ preconfigured.send(method, value)
61
+ else
62
+ fail(InvalidValueException, _("You can't override values hardcoded in the stack definition"))
63
+ end
64
+ end
65
+
66
+ def update_configuration_storage(key, value)
67
+ if value.is_a? ::Hash
68
+ self[key] = Config::Hash.new
69
+ self[key].configure(value)
70
+ elsif value.is_a? ::Array
71
+ self[key] = Config::Array.new(value.size)
72
+ self[key].configure(value)
73
+ elsif value.nil?
74
+ configuration_storage.delete(key.to_s)
75
+ else
76
+ configuration_storage[key.to_s] = value
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,23 @@
1
+ module ForemanDeployments
2
+ module Config
3
+ class LoadVisitor
4
+ def initialize(config_storage)
5
+ @config_storage = config_storage
6
+ end
7
+
8
+ def visit(subject)
9
+ configure_task_definition(subject) if subject.is_a? ForemanDeployments::Tasks::BaseDefinition
10
+ end
11
+
12
+ def self.load(stack_definition, config_storage)
13
+ stack_definition.accept(LoadVisitor.new(config_storage))
14
+ end
15
+
16
+ private
17
+
18
+ def configure_task_definition(task)
19
+ task.configure(@config_storage.get_config_for(task))
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ module ForemanDeployments
2
+ module Config
3
+ class MergeVisitor
4
+ def initialize(config_storage)
5
+ @config_storage = config_storage
6
+ end
7
+
8
+ def visit(subject)
9
+ configure_task_definition(subject) if subject.is_a? ForemanDeployments::Tasks::BaseDefinition
10
+ end
11
+
12
+ def self.merge(stack_definition, config_storage)
13
+ stack_definition.accept(MergeVisitor.new(config_storage))
14
+ end
15
+
16
+ private
17
+
18
+ def configure_task_definition(task)
19
+ task.merge_configuration(@config_storage.get_config_for(task))
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ module ForemanDeployments
2
+ module Config
3
+ class SaveVisitor
4
+ def initialize(config_storage)
5
+ @config_storage = config_storage
6
+ end
7
+
8
+ def visit(subject)
9
+ configure_task_definition(subject) if subject.is_a? ForemanDeployments::Tasks::BaseDefinition
10
+ end
11
+
12
+ def self.save(stack_definition, config_storage)
13
+ stack_definition.accept(SaveVisitor.new(config_storage))
14
+ end
15
+
16
+ private
17
+
18
+ def configure_task_definition(task)
19
+ @config_storage.set_config_for(task, task.configuration)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,39 @@
1
+ module ForemanDeployments
2
+ module Inputs
3
+ class BaseInputDefinition
4
+ attr_reader :configured
5
+
6
+ def initialize(params = {})
7
+ @parameters = params
8
+ end
9
+
10
+ def validate(_value)
11
+ ForemanDeployments::Validation::ValidationResult.new
12
+ end
13
+
14
+ def configure(value)
15
+ validate(value)
16
+ @configured = value
17
+ end
18
+
19
+ def configuration
20
+ @configured
21
+ end
22
+
23
+ def to_hash
24
+ {
25
+ '_type' => 'input',
26
+ '_name' => self.class.tag_name
27
+ }
28
+ end
29
+
30
+ def self.tag_name
31
+ name.split('::').last
32
+ end
33
+
34
+ def self.build(*params)
35
+ new(*params)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,25 @@
1
+ module ForemanDeployments
2
+ module Inputs
3
+ class Value < BaseInputDefinition
4
+ attr_reader :description
5
+
6
+ def initialize(params = {})
7
+ @description = params.delete('description')
8
+ @default = params.delete('default')
9
+ super
10
+ end
11
+
12
+ def configured
13
+ @configured || @default
14
+ end
15
+
16
+ def to_hash
17
+ super.merge(
18
+ 'description' => @description,
19
+ 'default' => @default,
20
+ 'value' => @configured
21
+ )
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,26 @@
1
+ module ForemanDeployments
2
+ class PlannerVisitor < BaseDereferenceVisitor
3
+ def initialize(parent_action)
4
+ super()
5
+ @parent_action = parent_action
6
+ end
7
+
8
+ protected
9
+
10
+ def visit_task_definition(task)
11
+ super
12
+ task.plan(@parent_action) unless cached?(task)
13
+ end
14
+
15
+ def get_dereference(ref)
16
+ ref.dereference(get_planned_output(ref.task))
17
+ end
18
+
19
+ def get_planned_output(task)
20
+ cached(task) do
21
+ task.parameters = dereference(task.parameters)
22
+ task.plan(@parent_action).output
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,67 @@
1
+ module ForemanDeployments
2
+ class Registry
3
+ class TypeException < ::Foreman::Exception; end
4
+
5
+ ALLOWED_TYPES = {
6
+ 'task' => 'ForemanDeployments::Tasks::BaseDefinition',
7
+ 'input' => 'ForemanDeployments::Inputs::BaseInputDefinition'
8
+ }
9
+
10
+ def initialize
11
+ clear!
12
+ end
13
+
14
+ def clear!
15
+ @available = Hash[ALLOWED_TYPES.keys.map { |k| [k, {}] }]
16
+ end
17
+
18
+ def available(type)
19
+ @available[type].clone
20
+ end
21
+
22
+ def register(type, registered_class)
23
+ type = type.to_s
24
+ unless type_valid?(type)
25
+ fail(TypeException, "Type needs to be one of: #{ALLOWED_TYPES.keys.join(', ')}")
26
+ end
27
+ unless class_valid?(type, registered_class)
28
+ fail(TypeException, "Registered class need to be descendant of #{ALLOWED_TYPES[type]}")
29
+ end
30
+ task_name = registered_class.tag_name.to_s
31
+ unless name_valid?(task_name)
32
+ fail(TypeException, "Invalid name #{task_name}")
33
+ end
34
+ @available[type][task_name.to_s] = registered_class.name
35
+ end
36
+
37
+ def available_tasks
38
+ available('task')
39
+ end
40
+
41
+ def register_task(registered_class)
42
+ register('task', registered_class)
43
+ end
44
+
45
+ def available_inputs
46
+ available('input')
47
+ end
48
+
49
+ def register_input(registered_class)
50
+ register('input', registered_class)
51
+ end
52
+
53
+ private
54
+
55
+ def name_valid?(name)
56
+ name =~ /[a-zA-Z0-9][a-zA-Z0-9_]*/
57
+ end
58
+
59
+ def class_valid?(type, registered_class)
60
+ registered_class < ALLOWED_TYPES[type].constantize
61
+ end
62
+
63
+ def type_valid?(type)
64
+ ALLOWED_TYPES.keys.include?(type)
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,37 @@
1
+ module ForemanDeployments
2
+ class StackDefinition
3
+ attr_accessor :tasks
4
+
5
+ def initialize(tasks = {})
6
+ @tasks = tasks
7
+ initialize_tasks
8
+ end
9
+
10
+ def accept(visitor)
11
+ tasks.each do |_task_id, task|
12
+ task.accept(visitor)
13
+ end
14
+ visitor.visit(self)
15
+ end
16
+
17
+ def validate!
18
+ ForemanDeployments::Validation::Validator.validate!(self)
19
+ end
20
+
21
+ def validate
22
+ ForemanDeployments::Validation::Validator.validate(self)
23
+ end
24
+
25
+ def to_hash
26
+ tasks
27
+ end
28
+
29
+ private
30
+
31
+ def initialize_tasks
32
+ tasks.each do |task_id, task|
33
+ task.task_id = task_id.to_s
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,121 @@
1
+ module ForemanDeployments
2
+ class StackParseException < ::Foreman::Exception; end
3
+ class UnknownTaskException < StackParseException; end
4
+ class UnknownYAMLTagException < StackParseException; end
5
+ class UnknownReference < StackParseException; end
6
+
7
+ class ReferenceVisitor
8
+ def initialize
9
+ @references = []
10
+ end
11
+
12
+ def visit(subject)
13
+ if subject.is_a? ForemanDeployments::TaskReference
14
+ save_reference(subject)
15
+ elsif subject.is_a? ForemanDeployments::StackDefinition
16
+ link_references(subject)
17
+ end
18
+ end
19
+
20
+ def save_reference(reference)
21
+ @references << reference
22
+ end
23
+
24
+ def link_references(stack_definition)
25
+ @references.each do |ref|
26
+ task = stack_definition.tasks[ref.task_id]
27
+ if task.nil?
28
+ fail(UnknownReference, _('%s references unknown task') % ref.task_id)
29
+ else
30
+ ref.task = task
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ class StackParser
37
+ TAG_DOMAIN = 'deployments.theforeman.org,2015'
38
+
39
+ def initialize(registry = nil)
40
+ @registry = registry || ForemanDeployments.registry
41
+ end
42
+
43
+ def parse(stack_definition)
44
+ stack_definition = prepare_stack(stack_definition)
45
+
46
+ begin
47
+ parsed_stack = SafeYAML.load(stack_definition.to_s, nil,
48
+ :whitelisted_tags => init_whitelisted_tags,
49
+ :raise_on_unknown_tag => true)
50
+ rescue RuntimeError => e
51
+ raise wrap_exception(e)
52
+ end
53
+
54
+ unless parsed_stack.is_a? Hash
55
+ fail(StackParseException, _('Stack definition is invalid'))
56
+ end
57
+
58
+ definition = ForemanDeployments::StackDefinition.new(parsed_stack)
59
+ definition.accept(ReferenceVisitor.new)
60
+ definition
61
+ end
62
+
63
+ def self.parse(stack_definition)
64
+ StackParser.new.parse(stack_definition)
65
+ end
66
+
67
+ private
68
+
69
+ def init_whitelisted_tags
70
+ whitelist = [domain_prefix('reference')] # WARNING: safe_yaml behaves differently when the whitelist is empty
71
+ YAML.add_domain_type(TAG_DOMAIN, 'reference') do |_tag, params|
72
+ TaskReference.new(params['object'], params['field'])
73
+ end
74
+
75
+ @registry.available_tasks.each do |task_name, task_class|
76
+ whitelist << task_prefix(task_name)
77
+ YAML.add_domain_type(TAG_DOMAIN, 'task:' + task_name) do |_tag, params|
78
+ task_class.constantize.build(params)
79
+ end
80
+ end
81
+
82
+ @registry.available_inputs.each do |input_name, input_class|
83
+ whitelist << input_prefix(input_name)
84
+ YAML.add_domain_type(TAG_DOMAIN, 'input:' + input_name) do |_tag, params|
85
+ input_class.constantize.build(params)
86
+ end
87
+ end
88
+ whitelist
89
+ end
90
+
91
+ def prepare_stack(stack_definition)
92
+ "%TAG ! #{domain_tag}\n---\n#{stack_definition}"
93
+ end
94
+
95
+ def domain_tag
96
+ "tag:#{TAG_DOMAIN}:"
97
+ end
98
+
99
+ def domain_prefix(name = '')
100
+ "#{domain_tag}#{name}"
101
+ end
102
+
103
+ def task_prefix(task_name = '')
104
+ domain_prefix("task:#{task_name}")
105
+ end
106
+
107
+ def input_prefix(input_name = '')
108
+ domain_prefix("input:#{input_name}")
109
+ end
110
+
111
+ def wrap_exception(e)
112
+ if e.message =~ /Unknown YAML tag '#{task_prefix}([^']+)/
113
+ UnknownTaskException.new(_('Unknown stack task %s') % Regexp.last_match[1])
114
+ elsif e.message =~ /Unknown YAML tag '#{domain_tag}([^']+)/
115
+ UnknownYAMLTagException.new(_('Unknown YAML tag %s') % Regexp.last_match[1])
116
+ else
117
+ e
118
+ end
119
+ end
120
+ end
121
+ end