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.
- checksums.yaml +15 -0
- data/LICENSE +619 -0
- data/README.md +52 -0
- data/Rakefile +30 -0
- data/app/controllers/foreman_deployments/api/v2/base_controller.rb +25 -0
- data/app/controllers/foreman_deployments/api/v2/deployments_controller.rb +102 -0
- data/app/controllers/foreman_deployments/api/v2/stacks_controller.rb +52 -0
- data/app/controllers/foreman_deployments/create_resources_controller.rb +62 -0
- data/app/controllers/foreman_deployments/deployments_controller.rb +5 -0
- data/app/controllers/foreman_deployments/stacks_controller.rb +5 -0
- data/app/lib/foreman_deployments/base_dereference_visitor.rb +55 -0
- data/app/lib/foreman_deployments/config.rb +16 -0
- data/app/lib/foreman_deployments/config/array.rb +91 -0
- data/app/lib/foreman_deployments/config/configurator.rb +23 -0
- data/app/lib/foreman_deployments/config/hash.rb +81 -0
- data/app/lib/foreman_deployments/config/load_visitor.rb +23 -0
- data/app/lib/foreman_deployments/config/merge_visitor.rb +23 -0
- data/app/lib/foreman_deployments/config/save_visitor.rb +23 -0
- data/app/lib/foreman_deployments/inputs/base_input_definition.rb +39 -0
- data/app/lib/foreman_deployments/inputs/value.rb +25 -0
- data/app/lib/foreman_deployments/planner_visitor.rb +26 -0
- data/app/lib/foreman_deployments/registry.rb +67 -0
- data/app/lib/foreman_deployments/stack_definition.rb +37 -0
- data/app/lib/foreman_deployments/stack_parser.rb +121 -0
- data/app/lib/foreman_deployments/task_reference.rb +48 -0
- data/app/lib/foreman_deployments/tasks/base_action.rb +6 -0
- data/app/lib/foreman_deployments/tasks/base_definition.rb +72 -0
- data/app/lib/foreman_deployments/tasks/creation_task_definition.rb +68 -0
- data/app/lib/foreman_deployments/tasks/host_creation_task_definition.rb +44 -0
- data/app/lib/foreman_deployments/tasks/search_task_definition.rb +55 -0
- data/app/lib/foreman_deployments/tasks/stack_deploy_action.rb +10 -0
- data/app/lib/foreman_deployments/tasks/wait_until_built_task_definition.rb +65 -0
- data/app/lib/foreman_deployments/validation/dereference_visitor.rb +18 -0
- data/app/lib/foreman_deployments/validation/remove_ids_visitor.rb +59 -0
- data/app/lib/foreman_deployments/validation/validation_error.rb +12 -0
- data/app/lib/foreman_deployments/validation/validation_result.rb +26 -0
- data/app/lib/foreman_deployments/validation/validation_visitor.rb +20 -0
- data/app/lib/foreman_deployments/validation/validator.rb +29 -0
- data/app/models/foreman_deployments/concerns/belongs_to_single_taxonomy.rb +42 -0
- data/app/models/foreman_deployments/concerns/belongs_to_stack_taxonomy.rb +24 -0
- data/app/models/foreman_deployments/configuration.rb +26 -0
- data/app/models/foreman_deployments/deployment.rb +57 -0
- data/app/models/foreman_deployments/resource_models/create_resource.rb +18 -0
- data/app/models/foreman_deployments/stack.rb +20 -0
- data/app/views/foreman_deployments/api/v2/deployments/base.json.rabl +3 -0
- data/app/views/foreman_deployments/api/v2/deployments/create.json.rabl +3 -0
- data/app/views/foreman_deployments/api/v2/deployments/index.json.rabl +3 -0
- data/app/views/foreman_deployments/api/v2/deployments/main.json.rabl +5 -0
- data/app/views/foreman_deployments/api/v2/deployments/merge_configuration.json.rabl +3 -0
- data/app/views/foreman_deployments/api/v2/deployments/replace_configuration.json.rabl +3 -0
- data/app/views/foreman_deployments/api/v2/deployments/run.json.rabl +3 -0
- data/app/views/foreman_deployments/api/v2/deployments/show.json.rabl +11 -0
- data/app/views/foreman_deployments/api/v2/deployments/update.json.rabl +3 -0
- data/app/views/foreman_deployments/api/v2/stacks/base.json.rabl +3 -0
- data/app/views/foreman_deployments/api/v2/stacks/create.json.rabl +3 -0
- data/app/views/foreman_deployments/api/v2/stacks/index.json.rabl +3 -0
- data/app/views/foreman_deployments/api/v2/stacks/main.json.rabl +3 -0
- data/app/views/foreman_deployments/api/v2/stacks/show.json.rabl +7 -0
- data/app/views/foreman_deployments/api/v2/stacks/update.json.rabl +3 -0
- data/app/views/foreman_deployments/create_resources/new.html.erb +6 -0
- data/config/routes.rb +41 -0
- data/db/migrate/20150623140612_create_stacks.rb +10 -0
- data/db/migrate/20150814092932_create_deployment.rb +20 -0
- data/db/migrate/20150916133305_add_task_to_deployments.rb +5 -0
- data/db/migrate/20150917130618_add_taxonomy_to_deployments.rb +8 -0
- data/db/seeds.d/03-permissions.rb +14 -0
- data/doc/deployment_process.md +112 -0
- data/doc/design/capsule_stack.puml +51 -0
- data/doc/design/complete_stack.puml +17 -0
- data/doc/design/config_resource_overview.puml +15 -0
- data/doc/design/design.md +230 -0
- data/doc/design/diagrams/capsule_stack.png +0 -0
- data/doc/design/diagrams/capsule_stack.svg +1 -0
- data/doc/design/diagrams/complete_stack.png +0 -0
- data/doc/design/diagrams/complete_stack.svg +1 -0
- data/doc/design/diagrams/config_resource_overview.png +0 -0
- data/doc/design/diagrams/config_resource_overview.svg +1 -0
- data/doc/design/diagrams/ordered_resource_overview.png +0 -0
- data/doc/design/diagrams/ordered_resource_overview.svg +1 -0
- data/doc/design/diagrams/overview.png +0 -0
- data/doc/design/diagrams/overview.svg +1 -0
- data/doc/design/diagrams/overview_class.png +0 -0
- data/doc/design/diagrams/overview_class.svg +1 -0
- data/doc/design/diagrams/sat_stack.png +0 -0
- data/doc/design/diagrams/sat_stack.svg +1 -0
- data/doc/design/diagrams/solr_usecase.png +0 -0
- data/doc/design/diagrams/solr_usecase.svg +1 -0
- data/doc/design/examples.md +192 -0
- data/doc/design/generate-diagrams.sh +7 -0
- data/doc/design/implementation.md +128 -0
- data/doc/design/ordered_resource_overview.puml +15 -0
- data/doc/design/overview.puml +42 -0
- data/doc/design/overview_class.puml +64 -0
- data/doc/design/resources.md +134 -0
- data/doc/design/sat_stack.puml +37 -0
- data/doc/design/shared.puml +171 -0
- data/doc/design/solr_usecase.puml +189 -0
- data/doc/design/tasks.md +74 -0
- data/doc/design/user_interfaces.md +189 -0
- data/doc/introduction.md +84 -0
- data/doc/writing_stacks.md +102 -0
- data/lib/foreman_deployments.rb +7 -0
- data/lib/foreman_deployments/engine.rb +94 -0
- data/lib/foreman_deployments/monkey_patches.rb +9 -0
- data/lib/foreman_deployments/version.rb +3 -0
- data/lib/tasks/foreman_deployments_tasks.rake +22 -0
- data/locale/Makefile +62 -0
- data/test/factories/foreman_deployments.rb +70 -0
- data/test/functional/api/v2/deployments_controller_test.rb +318 -0
- data/test/functional/api/v2/stacks_controller_test.rb +140 -0
- data/test/test_plugin_helper.rb +14 -0
- data/test/unit/lib/config/array_test.rb +217 -0
- data/test/unit/lib/config/configurator_test.rb +66 -0
- data/test/unit/lib/config/hash_test.rb +178 -0
- data/test/unit/lib/inputs/value_test.rb +47 -0
- data/test/unit/lib/registry_test.rb +117 -0
- data/test/unit/lib/stack_definition_test.rb +54 -0
- data/test/unit/lib/stack_parser_test.rb +129 -0
- data/test/unit/lib/task_reference_test.rb +63 -0
- data/test/unit/lib/tasks/base_definition_test.rb +137 -0
- data/test/unit/lib/tasks/creation_task_definition_test.rb +57 -0
- data/test/unit/lib/tasks/host_creation_task_definition_test.rb +10 -0
- data/test/unit/lib/tasks/search_task_definition_test.rb +49 -0
- data/test/unit/lib/tasks/stack_deploy_action_test.rb +83 -0
- data/test/unit/lib/tasks/wait_until_built_task_definition_test.rb +71 -0
- data/test/unit/lib/validation/dereference_visitor_test.rb +48 -0
- data/test/unit/lib/validation/remove_ids_visitor_test.rb +90 -0
- data/test/unit/lib/validation/validation_result_test.rb +40 -0
- data/test/unit/lib/validation/validation_visitor_test.rb +67 -0
- data/test/unit/model/configuration_test.rb +88 -0
- data/test/unit/model/deployment_test.rb +159 -0
- data/test/unit/model/stack_test.rb +33 -0
- 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
|