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,48 @@
1
+ module ForemanDeployments
2
+ class TaskReference
3
+ attr_accessor :task, :task_id, :output_key
4
+
5
+ def initialize(task_id, output_key, task = nil)
6
+ @task_id = task_id
7
+ @output_key = output_key
8
+ @task = task
9
+ end
10
+
11
+ def dereference(output)
12
+ output_keys.each do |key|
13
+ return nil if output.nil?
14
+ if output.is_a?(Hash) || output.is_a?(Dynflow::ExecutionPlan::OutputReference)
15
+ output = output[key]
16
+ else
17
+ output = output.send(key)
18
+ end
19
+ end
20
+ return output
21
+ rescue NoMethodError
22
+ return nil
23
+ end
24
+
25
+ def accept(visitor)
26
+ visitor.visit(self)
27
+ end
28
+
29
+ def to_hash
30
+ {
31
+ '_type' => 'reference',
32
+ '_name' => self.class.tag_name,
33
+ 'object' => task_id,
34
+ 'field' => output_key
35
+ }
36
+ end
37
+
38
+ def self.tag_name
39
+ 'reference'
40
+ end
41
+
42
+ private
43
+
44
+ def output_keys
45
+ @output_key.to_s.split('.')
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,6 @@
1
+ module ForemanDeployments
2
+ module Tasks
3
+ class BaseAction < Actions::Base
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,72 @@
1
+ module ForemanDeployments
2
+ module Tasks
3
+ class BaseDefinition
4
+ attr_accessor :task_id, :parameters
5
+ attr_reader :planned
6
+
7
+ def initialize(params = nil)
8
+ @parameters = ForemanDeployments::Config.cast_to_configuration(params || {})
9
+ end
10
+
11
+ def plan(parent_task)
12
+ @planned ||= parent_task.send(:plan_action, dynflow_action, parameters.configured)
13
+ end
14
+
15
+ def dynflow_action
16
+ fail NotImplementedError, "Method 'dynflow_action' method needs to be implemented"
17
+ end
18
+
19
+ def validate
20
+ fail NotImplementedError, "Method 'validate' needs to be implemented"
21
+ end
22
+
23
+ def preliminary_output
24
+ fail NotImplementedError, "Method 'preliminary_output' needs to be implemented"
25
+ end
26
+
27
+ def configure(parameters)
28
+ @parameters.configure(parameters)
29
+ end
30
+
31
+ def merge_configuration(additional_parameters)
32
+ @parameters.merge_configuration(additional_parameters)
33
+ end
34
+
35
+ def configuration
36
+ @parameters.configuration
37
+ end
38
+
39
+ def accept(visitor)
40
+ visit_parameters(visitor, parameters)
41
+ visitor.visit(self)
42
+ end
43
+
44
+ def to_hash
45
+ @parameters.configured.merge(
46
+ '_type' => 'task',
47
+ '_name' => self.class.tag_name
48
+ )
49
+ end
50
+
51
+ def self.tag_name
52
+ name.split('::').last
53
+ end
54
+
55
+ def self.build(params = {})
56
+ new(params)
57
+ end
58
+
59
+ private
60
+
61
+ def visit_parameters(visitor, value)
62
+ if value.respond_to?(:accept)
63
+ value.accept(visitor)
64
+ elsif value.is_a? Array
65
+ value.each { |item| visit_parameters(visitor, item) }
66
+ elsif value.is_a? Hash
67
+ value.each { |_key, item| visit_parameters(visitor, item) }
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,68 @@
1
+ module ForemanDeployments
2
+ module Tasks
3
+ class CreationTaskDefinition < ForemanDeployments::Tasks::BaseDefinition
4
+ class Action < BaseAction
5
+ def run
6
+ obj = CreationTaskDefinition.create_object(input)
7
+ obj.save!
8
+ CreationTaskDefinition.create_output(obj, output)
9
+ end
10
+ end
11
+
12
+ def validate
13
+ obj = self.class.create_object(parameters.configured)
14
+ obj.valid?
15
+ ForemanDeployments::Validation::ValidationResult.new(obj.errors.full_messages)
16
+ rescue ActiveRecord::ActiveRecordError => e
17
+ ForemanDeployments::Validation::ValidationResult.new([e.message])
18
+ end
19
+
20
+ def preliminary_output
21
+ self.class.create_output(self.class.create_object(parameters.configured))
22
+ end
23
+
24
+ def dynflow_action
25
+ self.class::Action
26
+ end
27
+
28
+ def self.create_object(parameters)
29
+ object_type = parameters['class']
30
+ object_params = parameters['params'] || {}
31
+
32
+ object_type = object_type.constantize if object_type.is_a? String
33
+ object = object_type.new
34
+
35
+ set_parameters(object, object_params)
36
+ end
37
+
38
+ def self.set_parameters(object, object_params)
39
+ object_params.each do |key, value|
40
+ if object.respond_to?("#{key}=")
41
+ object.send("#{key}=", value)
42
+ # TODO: else, add warning message
43
+ end
44
+ end
45
+
46
+ object
47
+ end
48
+
49
+ def self.create_output(obj, output_hash = {})
50
+ output_hash['object'] = obj
51
+ output_hash
52
+ end
53
+
54
+ def self.tag_name
55
+ 'CreateResource'
56
+ end
57
+
58
+ def self.build(parameters)
59
+ case parameters['class']
60
+ when 'Host', 'Host::Managed'
61
+ HostCreationTaskDefinition.new(parameters)
62
+ else
63
+ CreationTaskDefinition.new(parameters)
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,44 @@
1
+ module ForemanDeployments
2
+ module Tasks
3
+ class HostCreationTaskDefinition < ForemanDeployments::Tasks::CreationTaskDefinition
4
+ class Action < BaseAction
5
+ def run
6
+ obj = HostCreationTaskDefinition.create_object(input)
7
+ obj.save!
8
+ HostCreationTaskDefinition.create_output(obj, output)
9
+ end
10
+ end
11
+
12
+ def self.create_object(parameters)
13
+ object_type = parameters['class']
14
+ object_params = parameters['params']
15
+ object_params['managed'] ||= true
16
+
17
+ object_type = object_type.constantize if object_type.is_a? String
18
+
19
+ initial_params = {
20
+ :interfaces_attributes => object_params.delete('interfaces_attributes') || {}
21
+ }
22
+
23
+ object = object_type.new(initial_params)
24
+ object = set_parameters(object, object_params)
25
+
26
+ initialize_manged_host(object, object_params) if object.is_a? Host::Managed
27
+
28
+ object
29
+ end
30
+
31
+ def self.initialize_manged_host(object, object_params)
32
+ object_params['build'] ||= true
33
+ # Set compute attributes from a compute profile
34
+ if object.compute_resource_id && object.compute_profile_id
35
+ profile_attributes = object.compute_resource.compute_profile_attributes_for(object.compute_profile_id)
36
+ object.compute_attributes = profile_attributes.merge(object.compute_attributes || {})
37
+ end
38
+ # Merge interfaces from a compute profile
39
+ merge = InterfaceMerge.new
40
+ merge.run(object.interfaces, object.compute_resource.try(:compute_profile_for, object.compute_profile_id))
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,55 @@
1
+ module ForemanDeployments
2
+ module Tasks
3
+ class SearchTaskDefinition < BaseDefinition
4
+ class Action < BaseAction
5
+ def run
6
+ results = SearchTaskDefinition.search(input)
7
+ results = SerializableArray.new(results)
8
+ SearchTaskDefinition.create_output(results, output)
9
+ end
10
+ end
11
+
12
+ class SerializableArray < Array
13
+ def to_hash
14
+ {}
15
+ end
16
+ end
17
+
18
+ def validate
19
+ results = SearchTaskDefinition.search(parameters.configured)
20
+ messages = []
21
+ unless results.any?
22
+ messages = [
23
+ _('%s didn\'t return valid objects') % "#{parameters['class']}.search_for('#{parameters['search_term']}')"]
24
+ end
25
+
26
+ ForemanDeployments::Validation::ValidationResult.new(messages)
27
+ end
28
+
29
+ def preliminary_output
30
+ SearchTaskDefinition.create_output(SearchTaskDefinition.search(parameters.configured))
31
+ end
32
+
33
+ def dynflow_action
34
+ ForemanDeployments::Tasks::SearchTaskDefinition::Action
35
+ end
36
+
37
+ def self.search(parameters)
38
+ object_type = parameters['class']
39
+ object_type = object_type.constantize if object_type.is_a? String
40
+ results = object_type.search_for(parameters[:search_term])
41
+ results
42
+ end
43
+
44
+ def self.create_output(obj, output_hash = {})
45
+ output_hash['results'] = obj
46
+ output_hash['result'] = { 'ids' => obj.map(&:id) }
47
+ output_hash
48
+ end
49
+
50
+ def self.tag_name
51
+ 'FindResource'
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,10 @@
1
+ module ForemanDeployments
2
+ module Tasks
3
+ class StackDeployAction < BaseAction
4
+ def plan(stack_definition)
5
+ stack_definition.accept(ForemanDeployments::PlannerVisitor.new(self))
6
+ plan_self
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,65 @@
1
+ module ForemanDeployments
2
+ module Tasks
3
+ class WaitUntilBuiltTaskDefinition < ForemanDeployments::Tasks::BaseDefinition
4
+ class Action < BaseAction
5
+ include Dynflow::Action::Polling
6
+
7
+ def done?
8
+ external_task['built'] == true
9
+ end
10
+
11
+ def timeout
12
+ input['timeout'] || 2 * 60 * 60 # 2 hours default
13
+ end
14
+
15
+ def invoke_external_task
16
+ schedule_timeout(timeout) unless timeout <= 0
17
+ WaitUntilBuiltTaskDefinition.build_status
18
+ end
19
+
20
+ def poll_external_task
21
+ fail(_("'%s' is a required parameter") % 'host_id') unless input.key?('host_id')
22
+
23
+ host = Host.find(input['host_id'])
24
+ WaitUntilBuiltTaskDefinition.create_output(host, output)
25
+ WaitUntilBuiltTaskDefinition.build_status(host)
26
+ end
27
+
28
+ def poll_interval
29
+ 30
30
+ end
31
+ end
32
+
33
+ def validate
34
+ result = ForemanDeployments::Validation::ValidationResult.new([])
35
+ unless parameters.configured.key?('host_id')
36
+ result.messages << _("'%s' is a required parameter") % 'host_id'
37
+ end
38
+ result
39
+ end
40
+
41
+ def preliminary_output(_parameters)
42
+ WaitUntilBuiltTaskDefinition.create_output(Host::Managed.new)
43
+ end
44
+
45
+ def dynflow_action
46
+ ForemanDeployments::Tasks::WaitUntilBuiltTaskDefinition::Action
47
+ end
48
+
49
+ def self.create_output(host, output_hash = {})
50
+ output_hash['object'] = host
51
+ output_hash['object']['facts'] = host.facts
52
+ output_hash
53
+ end
54
+
55
+ def self.build_status(host = nil)
56
+ status = (!host.nil? && (host.reports.count > 1) && !host.reports.last.error?)
57
+ { 'build' => status }
58
+ end
59
+
60
+ def self.tag_name
61
+ 'WaitUntilBuilt'
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,18 @@
1
+ module ForemanDeployments
2
+ module Validation
3
+ class DereferenceVisitor < ForemanDeployments::BaseDereferenceVisitor
4
+ protected
5
+
6
+ def get_dereference(ref)
7
+ ref.dereference(get_preliminary_output(ref.task))
8
+ end
9
+
10
+ def get_preliminary_output(task)
11
+ cached(task) do
12
+ task.parameters = dereference(task.parameters)
13
+ task.preliminary_output
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,59 @@
1
+ module ForemanDeployments
2
+ module Validation
3
+ class RemoveIdsVisitor
4
+ def visit(subject)
5
+ if subject.is_a? ForemanDeployments::Tasks::CreationTaskDefinition
6
+ remove_ids(subject.parameters)
7
+ end
8
+ end
9
+
10
+ private
11
+
12
+ def remove_ids(value)
13
+ case value
14
+ when Config::Hash
15
+ remove_ids_from_hash(value)
16
+ when Config::Array
17
+ remove_ids_from_array(value)
18
+ when TaskReference
19
+ remove_id_from_reference(value)
20
+ else
21
+ value
22
+ end
23
+ end
24
+
25
+ def remove_ids_from_hash(hash)
26
+ hash.transform! do |key, item|
27
+ if singular_reference?(key, item) || multiple_references?(key, item)
28
+ [key.to_s.sub('_id', ''), remove_ids(item)]
29
+ else
30
+ [key, remove_ids(item)]
31
+ end
32
+ end
33
+ end
34
+
35
+ def remove_ids_from_array(array)
36
+ array.transform! do |item|
37
+ remove_ids(item)
38
+ end
39
+ end
40
+
41
+ def remove_id_from_reference(ref)
42
+ if ref.output_key.end_with?('.ids')
43
+ ref.output_key.gsub!(/[.]ids$/, 's')
44
+ elsif ref.output_key.end_with?('.id')
45
+ ref.output_key.gsub!(/[.]id$/, '')
46
+ end
47
+ ref
48
+ end
49
+
50
+ def singular_reference?(key, value)
51
+ key.to_s.end_with?('_id') && value.is_a?(TaskReference)
52
+ end
53
+
54
+ def multiple_references?(key, values)
55
+ key.to_s.end_with?('_ids') && (values.is_a?(TaskReference) || values.all? { |v| v.is_a?(TaskReference) })
56
+ end
57
+ end
58
+ end
59
+ end