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,52 @@
1
+ # Foreman Multi-Host Deployments
2
+
3
+ A plugin adding Foreman Multi-Host Deployment support.
4
+
5
+
6
+ ## Documentation
7
+
8
+ * [Introduction](doc/introduction.md)
9
+ * [How to write stacks](doc/writing_stacks.md)
10
+ * [Create, configure and deploy a stack](doc/deployment_process.md)
11
+ * Other media:
12
+ * [Development preview demo](https://www.youtube.com/watch?v=U5qtriQfu6A)
13
+
14
+ ## Design documentation
15
+
16
+ * [Design](doc/design/design.md)
17
+ * [List of deployment tasks](doc/design/tasks.md)
18
+ * [Examples and usecases](doc/design/examples.md)
19
+ * [User interfaces](doc/design/user_interfaces.md)
20
+ * Other sources:
21
+ * [Redmine tracker](http://projects.theforeman.org/issues/10092)
22
+ * [Deep dive](https://www.youtube.com/watch?v=ISQdESgmOqo) about the task-centric approach ([presentation](https://tstrachota.fedorapeople.org/slides/mhd_presentation/))
23
+ * The original design:
24
+ * Foreman [Deep dive](https://www.youtube.com/watch?v=CFPLGfA6-jU) ([presentation](https://drive.google.com/file/d/0B4ZecfmLdabga0c2SGNqN1ZDQzg/view?usp=sharing))
25
+ * Cfgmgmtcamp [presentation](http://blog.pitr.ch/presentations/2015/cfgmgmtcamp/)
26
+ * Code walk-through [session](https://bluejeans.com/s/83Q9/)
27
+
28
+
29
+ ## Installation
30
+
31
+ Add `gem 'foreman_deployments', path: '../foreman_deployments'` to your `Gemfile.local.rb` file.
32
+ For more information see
33
+ [How to Install a Plugin](http://projects.theforeman.org/projects/foreman/wiki/How_to_Install_a_Plugin).
34
+
35
+
36
+ ## Copyright
37
+
38
+ Copyright (c) 2014-2015 Red Hat Inc
39
+
40
+ This program is free software: you can redistribute it and/or modify
41
+ it under the terms of the GNU General Public License as published by
42
+ the Free Software Foundation, either version 3 of the License, or
43
+ (at your option) any later version.
44
+
45
+ This program is distributed in the hope that it will be useful,
46
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
47
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
48
+ GNU General Public License for more details.
49
+
50
+ You should have received a copy of the GNU General Public License
51
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
52
+
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+
8
+ Bundler::GemHelper.install_tasks
9
+
10
+ require 'rake/testtask'
11
+
12
+ Rake::TestTask.new(:test) do |t|
13
+ t.libs << 'lib'
14
+ t.libs << 'test'
15
+ t.pattern = 'test/**/*_test.rb'
16
+ t.verbose = false
17
+ end
18
+
19
+ task default: :test
20
+
21
+ begin
22
+ require 'rubocop/rake_task'
23
+ RuboCop::RakeTask.new
24
+ rescue LoadError
25
+ puts 'Rubocop not loaded.'
26
+ end
27
+
28
+ task :default do
29
+ Rake::Task['rubocop'].execute
30
+ end
@@ -0,0 +1,25 @@
1
+ require_dependency 'foreman_deployments/stack_parser'
2
+
3
+ module ForemanDeployments
4
+ module Api
5
+ module V2
6
+ class BaseController < ::Api::V2::BaseController
7
+ rescue_from StackParseException, :with => :unprocessable_entity_error
8
+
9
+ resource_description do
10
+ api_version 'v2'
11
+ end
12
+
13
+ def unprocessable_entity_error(exception)
14
+ render_error 'standard_error', :locals => { :exception => exception }, :status => :unprocessable_entity
15
+ end
16
+
17
+ def resource_class_for(resource)
18
+ return "ForemanDeployments::#{resource.classify}".constantize
19
+ rescue NameError
20
+ super
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,102 @@
1
+ module ForemanDeployments
2
+ module Api
3
+ module V2
4
+ class DeploymentsController < BaseController
5
+ include ::Api::TaxonomyScope
6
+
7
+ before_filter :find_resource, :only => [:show, :replace_configuration, :merge_configuration, :run]
8
+
9
+ rescue_from ForemanDeployments::Config::InvalidValueException, :with => :unprocessable_entity_error
10
+ rescue_from ForemanDeployments::Validation::ValidationError, :with => :unprocessable_entity_error
11
+
12
+ def_param_group :deployment do
13
+ param :deployment, Hash, :required => true, :action_aware => true do
14
+ param :name, String, :required => true, :desc => N_('Name for the deployment')
15
+ param :stack_id, :identifier, :required => true, :desc => N_('Id of the stack to deploy')
16
+ end
17
+ end
18
+
19
+ api :POST, '/deployments/', N_('Create a deployment')
20
+ param_group :deployment, :as => :create
21
+ def create
22
+ deployment_params = params[:deployment]
23
+
24
+ if deployment_params[:stack_id]
25
+ stack = ForemanDeployments::Stack.authorized(:view_stacks).find(deployment_params.delete(:stack_id))
26
+ end
27
+ deployment_params[:configuration] = ForemanDeployments::Configuration.new(:stack => stack)
28
+
29
+ @deployment = ForemanDeployments::Deployment.new(deployment_params)
30
+ process_response @deployment.save
31
+ end
32
+
33
+ api :GET, '/deployments/', N_('List deployments')
34
+ api :GET, '/locations/:location_id/deployments/', N_('List of deployments per location')
35
+ api :GET, '/organizations/:organization_id/deployments/', N_('List of deployments per organization')
36
+ param_group :taxonomy_scope, ::Api::V2::BaseController
37
+ param_group :search_and_pagination, ::Api::V2::BaseController
38
+ def index
39
+ @deployments = resource_scope_for_index
40
+ end
41
+
42
+ api :POST, '/deployments/:id/configuration/', N_('Merge a configuration update with current values')
43
+ param :id, :identifier, :required => true
44
+ param :values, Hash,
45
+ :required => true,
46
+ :desc => N_('Hash with configuration update for tasks (Format: task ID => configuration values).')
47
+ def merge_configuration
48
+ configuration_update = ForemanDeployments::Configuration.new(:values => params[:values])
49
+
50
+ config = @deployment.configurator
51
+ config.merge(@deployment.configuration, configuration_update)
52
+ config.dump(@deployment.configuration)
53
+
54
+ @deployment.configuration.save!
55
+ end
56
+
57
+ api :PUT, '/deployments/:id/configuration/', N_('Configure a deployment')
58
+ param :id, :identifier, :required => true
59
+ param :values, Hash,
60
+ :required => true,
61
+ :desc => N_('Hash with configuration for tasks (Format: task ID => configuration values).')
62
+ def replace_configuration
63
+ new_configuration = ForemanDeployments::Configuration.new(:values => params[:values])
64
+
65
+ config = @deployment.configurator
66
+ config.configure(new_configuration)
67
+ config.dump(@deployment.configuration)
68
+
69
+ @deployment.configuration.save!
70
+ end
71
+
72
+ api :GET, '/deployments/:id/', N_('Get information about a deployment')
73
+ param :id, :identifier, :required => true
74
+ def show
75
+ config = @deployment.configurator
76
+ config.configure(@deployment.configuration)
77
+
78
+ @validation_result = @deployment.parsed_stack.validate
79
+ end
80
+
81
+ api :POST, '/deployments/:id/run/', N_('Start a deployment')
82
+ param :id, :identifier, :required => true
83
+ def run
84
+ @deployment.run
85
+ end
86
+
87
+ private
88
+
89
+ def action_permission
90
+ case params[:action]
91
+ when 'merge_configuration', 'replace_configuration'
92
+ 'create'
93
+ when 'run'
94
+ 'run'
95
+ else
96
+ super
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,52 @@
1
+ module ForemanDeployments
2
+ module Api
3
+ module V2
4
+ class StacksController < BaseController
5
+ include ::Api::TaxonomyScope
6
+
7
+ before_filter :find_resource, :only => [:show, :update]
8
+
9
+ def_param_group :stack do
10
+ param :stack, Hash, :required => true, :action_aware => true do
11
+ param :name, String, :required => true, :desc => N_('Name for the stack')
12
+ param :definition, String, :required => true, :desc => N_('Stack definition in YAML format')
13
+ end
14
+ end
15
+
16
+ api :POST, '/stacks/', N_('Import a stack')
17
+ param_group :stack, :as => :create
18
+ def create
19
+ @stack = Stack.new(params[:stack])
20
+ # parse the stack to see if it's valid
21
+ ForemanDeployments::StackParser.parse(@stack.definition)
22
+ process_response @stack.save
23
+ end
24
+
25
+ api :PUT, '/stacks/:id/', N_('Update imported stack')
26
+ param_group :stack, :as => :update
27
+ param :id, :identifier, :required => true
28
+ def update
29
+ if !@stack.configurations.empty? && !params[:stack][:definition].nil?
30
+ render :json => { :error => _("Can't update stack that has been configured") }, :status => :unprocessable_entity
31
+ else
32
+ ForemanDeployments::StackParser.parse(params[:stack][:definition]) if params[:stack][:definition]
33
+ process_response @stack.update_attributes(params[:stack])
34
+ end
35
+ end
36
+
37
+ api :GET, '/stacks/', N_('List saved stacks')
38
+ api :GET, '/locations/:location_id/stacks/', N_('List of stacks per location')
39
+ api :GET, '/organizations/:organization_id/stacks/', N_('List of stacks per organization')
40
+ def index
41
+ @stacks = resource_scope_for_index
42
+ end
43
+
44
+ api :GET, '/stacks/:id/', N_('Get information about a stack')
45
+ param :id, :identifier, :required => true
46
+ def show
47
+ @parsed_stack = ForemanDeployments::StackParser.parse(@stack.definition)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,62 @@
1
+ module ForemanDeployments
2
+ class CreateResourcesController < ApplicationController
3
+ def index
4
+ end
5
+
6
+ def new
7
+ @resource = ResourceModels::CreateResource.new
8
+ @resource.resource_type = User
9
+ @resource.definition_hash = {
10
+ login: 'my_user1',
11
+ mail: 'test@foreman.org'
12
+ }
13
+
14
+ @user = @resource.new_resource
15
+ end
16
+
17
+ # rubocop:disable Metrics/MethodLength
18
+ def create
19
+ params[:user].delete :admin if params[:user]
20
+
21
+ # stack definition
22
+ # will be selected from DB as Stack object
23
+ stack = [
24
+ 'user1: !task:SearchResource',
25
+ ' class: User',
26
+ ' search_term: "1"',
27
+ 'usergroup1: !task:CreateResource',
28
+ ' class: Usergroup',
29
+ ' params:',
30
+ ' name: zzz',
31
+ ' user_ids:',
32
+ ' !reference',
33
+ ' object: user1',
34
+ ' field: result.ids'
35
+ ].join("\n")
36
+
37
+ # parse the stack
38
+ stack_definition = ForemanDeployments::StackParser.parse(stack)
39
+
40
+ # configure with user input
41
+ # stack_definition.tasks['user1'].configure('params' => params[:user])
42
+ stack_definition.tasks['usergroup1'].configure({})
43
+
44
+ # validate
45
+ result = ForemanDeployments::Validation::Validator.validate(stack_definition)
46
+ fail "Validation failed:\n #{result.messages}" unless result.valid?
47
+
48
+ ForemanTasks.sync_task(Tasks::StackDeployAction, stack_definition)
49
+
50
+ redirect_to foreman_tasks_tasks_path
51
+ end
52
+
53
+ def edit
54
+ end
55
+
56
+ def update
57
+ end
58
+
59
+ def destroy
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,5 @@
1
+ module ForemanDeployments
2
+ class DeploymentsController < ApplicationController
3
+ include Foreman::Controller::AutoCompleteSearch
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module ForemanDeployments
2
+ class StacksController < ApplicationController
3
+ include Foreman::Controller::AutoCompleteSearch
4
+ end
5
+ end
@@ -0,0 +1,55 @@
1
+ module ForemanDeployments
2
+ class BaseDereferenceVisitor
3
+ def initialize
4
+ @task_cache = {}
5
+ end
6
+
7
+ def visit(subject)
8
+ if subject.is_a? ForemanDeployments::Tasks::BaseDefinition
9
+ visit_task_definition(subject)
10
+ end
11
+ end
12
+
13
+ protected
14
+
15
+ def visit_task_definition(definition)
16
+ definition.parameters = dereference(definition.parameters)
17
+ end
18
+
19
+ def dereference(value)
20
+ case value
21
+ when ForemanDeployments::TaskReference
22
+ return get_dereference(value)
23
+ when Config::Array
24
+ return value.transform! { |obj| dereference(obj) }
25
+ when Config::Hash
26
+ return value.transform! { |key, obj| [key, dereference(obj)] }
27
+ else
28
+ return value
29
+ end
30
+ end
31
+
32
+ def get_dereference(_ref)
33
+ fail NotImplementedError, "Method 'get_dereference' needs to be implemented"
34
+ end
35
+
36
+ def cached(task)
37
+ key = cache_key(task)
38
+ if cached?(task)
39
+ @task_cache[key]
40
+ else
41
+ @task_cache[key] = yield
42
+ end
43
+ end
44
+
45
+ def cached?(task)
46
+ @task_cache.key?(cache_key(task))
47
+ end
48
+
49
+ private
50
+
51
+ def cache_key(task)
52
+ task.task_id.to_s
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,16 @@
1
+ module ForemanDeployments
2
+ module Config
3
+ class InvalidValueException < ::Foreman::Exception; end
4
+
5
+ def self.cast_to_configuration(value)
6
+ case value
7
+ when ::Hash
8
+ ForemanDeployments::Config::Hash[value.map { |key, obj| [key, cast_to_configuration(obj)] }]
9
+ when ::Array
10
+ ForemanDeployments::Config::Array[*value.map { |obj| cast_to_configuration(obj) }]
11
+ else
12
+ value
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,91 @@
1
+ module ForemanDeployments
2
+ module Config
3
+ class Array < ::Array
4
+ def merge_configuration(values)
5
+ values = ensure_hash(values)
6
+ values.map do |index, value|
7
+ configure_index(index, value, :merge_configuration)
8
+ end
9
+ end
10
+
11
+ def configure(values)
12
+ values = ensure_hash(values)
13
+ configuration_storage.clear
14
+ values.map do |index, value|
15
+ configure_index(index, value, :configure)
16
+ end
17
+ end
18
+
19
+ def configured
20
+ map.with_index do |item, index|
21
+ if item.respond_to?(:configured)
22
+ item.configured
23
+ elsif item.nil?
24
+ configuration_storage[index]
25
+ else
26
+ item
27
+ end
28
+ end
29
+ end
30
+
31
+ def configuration
32
+ result = configuration_storage.deep_clone
33
+ each_with_index do |item, index|
34
+ if item.respond_to?(:configuration)
35
+ result[index] = item.configuration unless item.configuration.nil?
36
+ end
37
+ end
38
+ result.empty? ? nil : result
39
+ end
40
+
41
+ def transform!(&block)
42
+ self.map! do |item|
43
+ block.call(item)
44
+ end
45
+ self
46
+ end
47
+
48
+ protected
49
+
50
+ def configuration_storage
51
+ @configuration_storage ||= {}
52
+ end
53
+
54
+ def ensure_hash(values)
55
+ if values.is_a? ::Hash
56
+ ::Hash[values.map { |index, value| sanitize_pair(index, value) }]
57
+ elsif values.is_a? ::Array
58
+ ::Hash[values.map.with_index { |value, index| sanitize_pair(index, value) }]
59
+ else
60
+ fail(InvalidValueException, _("Unexpected type '%s', only hash or array is accepted") % values.class)
61
+ end
62
+ end
63
+
64
+ def sanitize_pair(index, value)
65
+ index = Integer(index)
66
+ if (index >= size) || (index < 0)
67
+ fail(InvalidValueException, _('Can\'t configure items outside the range'))
68
+ end
69
+ [index, value]
70
+ rescue ArgumentError
71
+ raise(InvalidValueException, _("Key '%s' isn't numeric value") % index)
72
+ end
73
+
74
+ def configure_index(index, value, method)
75
+ preconfigured = self[index]
76
+
77
+ if preconfigured.nil?
78
+ if value.nil?
79
+ configuration_storage.delete(index)
80
+ else
81
+ configuration_storage[index] = value
82
+ end
83
+ elsif preconfigured.respond_to?(method)
84
+ preconfigured.send(method, value)
85
+ else
86
+ fail(InvalidValueException, _("You can't override values hardcoded in the stack definition"))
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end