foreman_templates 7.0.5 → 9.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/app/controllers/api/v2/template_controller.rb +8 -2
- data/app/controllers/concerns/foreman/controller/parameters/template_params.rb +22 -2
- data/app/controllers/ui_template_syncs_controller.rb +1 -1
- data/app/models/setting/template_sync.rb +12 -2
- data/app/services/foreman_templates/export_result.rb +20 -29
- data/app/services/foreman_templates/parse_result.rb +1 -0
- data/app/services/foreman_templates/template_exporter.rb +52 -39
- data/app/services/foreman_templates/template_importer.rb +20 -4
- data/app/views/template_syncs/index.html.erb +6 -2
- data/app/views/ui_template_syncs/template_export_result.rabl +4 -4
- data/db/migrate/20180627134929_change_lock_setting.rb +5 -0
- data/lib/foreman_templates/engine.rb +6 -0
- data/lib/foreman_templates/version.rb +1 -1
- data/package.json +15 -21
- data/webpack/__mocks__/foremanReact/components/Layout/LayoutSelectors.js +4 -0
- data/webpack/__mocks__/foremanReact/components/common/forms/ForemanForm.js +2 -0
- data/webpack/components/NewTemplateSync/__fixtures__/templateSyncSettings.fixtures.js +4 -4
- data/webpack/components/NewTemplateSync/__tests__/__snapshots__/NewTemplateSync.test.js.snap +2 -2
- data/webpack/components/NewTemplateSync/components/NewTemplateSyncForm/NewTemplateSyncForm.js +89 -53
- data/webpack/components/NewTemplateSync/components/NewTemplateSyncForm/NewTemplateSyncFormSelectors.js +10 -13
- data/webpack/components/NewTemplateSync/components/NewTemplateSyncForm/__tests__/NewTemplateSyncFormSelectors.test.js +1 -19
- data/webpack/components/NewTemplateSync/components/NewTemplateSyncForm/__tests__/__snapshots__/NewTemplateSyncFormSelectors.test.js.snap +7 -36
- data/webpack/components/NewTemplateSync/components/NewTemplateSyncForm/index.js +7 -16
- data/webpack/components/NewTemplateSync/components/SyncSettingField.js +5 -11
- data/webpack/components/NewTemplateSync/components/SyncSettingFields.js +8 -25
- data/webpack/components/NewTemplateSync/components/TextButtonField/index.js +27 -20
- data/webpack/components/NewTemplateSync/components/__tests__/SyncSettingField.test.js +2 -1
- data/webpack/components/NewTemplateSync/components/__tests__/SyncSettingFields.test.js +1 -0
- data/webpack/components/NewTemplateSync/components/__tests__/TextButtonField.test.js +4 -4
- data/webpack/components/NewTemplateSync/components/__tests__/__snapshots__/SyncSettingField.test.js.snap +18 -27
- data/webpack/components/NewTemplateSync/components/__tests__/__snapshots__/SyncSettingFields.test.js.snap +5 -3
- data/webpack/components/NewTemplateSync/components/__tests__/__snapshots__/TextButtonField.test.js.snap +8 -91
- data/webpack/components/TemplateSyncResult/__fixtures__/templateSyncResult.fixtures.js +2 -2
- data/webpack/components/TemplateSyncResult/__tests__/__snapshots__/TemplateSyncResult.test.js.snap +3 -1
- data/webpack/components/TemplateSyncResult/__tests__/__snapshots__/TemplateSyncResultReducer.test.js.snap +3 -1
- data/webpack/components/TemplateSyncResult/components/SyncResultList.js +2 -2
- data/webpack/components/TemplateSyncResult/components/SyncedTemplate/__snapshots__/helpers.test.js.snap +37 -0
- data/webpack/components/TemplateSyncResult/components/SyncedTemplate/helpers.js +21 -11
- data/webpack/components/TemplateSyncResult/components/SyncedTemplate/helpers.test.js +21 -0
- data/webpack/components/TemplateSyncResult/components/__tests__/__snapshots__/SyncResultList.test.js.snap +8 -6
- data/webpack/components/TemplateSyncResult/components/__tests__/__snapshots__/SyncedTemplate.test.js.snap +39 -15
- data/webpack/testSetup.js +2 -1
- metadata +8 -8
- data/webpack/__mocks__/foremanReact/components/common/forms/Form.js +0 -2
- data/webpack/components/NewTemplateSync/components/NewTemplateSyncForm/NewTemplateSyncFormConstants.js +0 -1
- data/webpack/components/NewTemplateSync/components/NewTemplateSyncForm/__tests__/NewTemplateSyncForm.test.js +0 -42
- data/webpack/components/NewTemplateSync/components/NewTemplateSyncForm/__tests__/__snapshots__/NewTemplateSyncForm.test.js.snap +0 -186
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 46560d67a9e22719b19e71c43a0298d960ef8859a37fa0fdba12d0a2ea91fa21
|
4
|
+
data.tar.gz: b57fa4cf54bff7ca216d6e3a2abbbe77215e078ee7feb896053603f3d56a07fe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6422b8cd5feb8b24e541dfa819aa8f063f65385fdfe4ad884e48474d1e76ad7d327c13e60336803e48de9034a299b6a01bc727966384e75e73c9162fc29cf069
|
7
|
+
data.tar.gz: ddb404bc9683c683403c4a401ccacb2118dadc197b998272f70192525738fa68746f290264ead24c42a2c4ca97d49db115aafe6f8e3cac2c416eacc6ca420230
|
@@ -3,6 +3,10 @@ module Api
|
|
3
3
|
class TemplateController < ::Api::V2::BaseController
|
4
4
|
include ::Foreman::Controller::Parameters::TemplateParams
|
5
5
|
|
6
|
+
resource_description do
|
7
|
+
resource_id 'templates'
|
8
|
+
end
|
9
|
+
|
6
10
|
def_param_group :foreman_template_sync_params do
|
7
11
|
param :branch, String, :required => false, :desc => N_("Branch in Git repo.")
|
8
12
|
param :repo, String, :required => false, :desc => N_("Override the default repo from settings.")
|
@@ -15,7 +19,7 @@ module Api
|
|
15
19
|
param :prefix, String, :required => false, :desc => N_("The string all imported templates should begin with.")
|
16
20
|
param :associate, Setting::TemplateSync.associate_types.keys, :required => false, :desc => N_("Associate to OS's, Locations & Organizations. Options are: always, new or never.")
|
17
21
|
param :force, :bool, :required => false, :desc => N_("Update templates that are locked")
|
18
|
-
param :lock,
|
22
|
+
param :lock, Setting::TemplateSync.lock_types.keys + ["true", "false", "0", "1"], :required => false, :desc => N_("Lock imported templates")
|
19
23
|
param :verbose, :bool, :required => false, :desc => N_("Show template diff in response")
|
20
24
|
param_group :foreman_template_sync_params
|
21
25
|
param_group :taxonomies, ::Api::V2::BaseController
|
@@ -29,11 +33,13 @@ module Api
|
|
29
33
|
|
30
34
|
api :POST, "/templates/export", N_("Initiate Export")
|
31
35
|
param :metadata_export_mode, Setting::TemplateSync.metadata_export_mode_types.keys, :required => false, :desc => N_("Specify how to handle metadata")
|
36
|
+
param :commit_msg, String, :desc => N_("Custom commit message for templates export")
|
32
37
|
param_group :foreman_template_sync_params
|
33
38
|
param_group :taxonomies, ::Api::V2::BaseController
|
34
39
|
def export
|
35
40
|
@result = ForemanTemplates::TemplateExporter.new(template_export_params).export!
|
36
|
-
|
41
|
+
@result[:templates] = @result[:templates].map(&:to_h)
|
42
|
+
render :json => { :message => @result }, :status => @result[:error] ? 500 : 200
|
37
43
|
end
|
38
44
|
end
|
39
45
|
end
|
@@ -15,7 +15,7 @@ module Foreman
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def extra_export_params
|
18
|
-
|
18
|
+
%i(metadata_export_mode commit_msg)
|
19
19
|
end
|
20
20
|
|
21
21
|
def template_params_filter(extra_params = [])
|
@@ -34,7 +34,27 @@ module Foreman
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def template_import_params
|
37
|
-
add_taxonomy_params(base_import_params(:none))
|
37
|
+
transform_lock_param add_taxonomy_params(base_import_params(:none))
|
38
|
+
end
|
39
|
+
|
40
|
+
def transform_lock_param(params)
|
41
|
+
lock = params[:lock]
|
42
|
+
return params if lock.nil?
|
43
|
+
|
44
|
+
if lock == "true" || lock.is_a?(TrueClass) || lock.to_s == "1"
|
45
|
+
log_deprecated_param(lock)
|
46
|
+
params[:lock] = "lock"
|
47
|
+
end
|
48
|
+
|
49
|
+
if lock == "false" || lock.is_a?(FalseClass) || lock.to_s == "0"
|
50
|
+
log_deprecated_param(lock)
|
51
|
+
params[:lock] = "unlock"
|
52
|
+
end
|
53
|
+
params
|
54
|
+
end
|
55
|
+
|
56
|
+
def log_deprecated_param(value)
|
57
|
+
Logging.logger('app').warn "Using '#{value}' as a value for lock when syncing templates is deprecated and will be removed in the future."
|
38
58
|
end
|
39
59
|
|
40
60
|
def template_export_params
|
@@ -18,7 +18,7 @@ class UiTemplateSyncsController < ApplicationController
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def export
|
21
|
-
@result = ForemanTemplates::TemplateExporter.new(ui_template_export_params).export!
|
21
|
+
@result = OpenStruct.new ForemanTemplates::TemplateExporter.new(ui_template_export_params).export!
|
22
22
|
|
23
23
|
if @result.error
|
24
24
|
render_errors [@result.error]
|
@@ -11,7 +11,7 @@ class Setting
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def self.export_stripped_names
|
14
|
-
%w(metadata_export_mode)
|
14
|
+
%w(metadata_export_mode commit_msg)
|
15
15
|
end
|
16
16
|
|
17
17
|
def self.import_setting_names(except = [])
|
@@ -38,6 +38,15 @@ class Setting
|
|
38
38
|
}
|
39
39
|
end
|
40
40
|
|
41
|
+
def self.lock_types
|
42
|
+
{
|
43
|
+
'lock' => _('Lock'),
|
44
|
+
'keep_lock_new' => _('Keep, lock new'),
|
45
|
+
'keep' => _('Keep, do not lock new'),
|
46
|
+
'unlock' => _('Unlock')
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
41
50
|
def self.metadata_export_mode_types
|
42
51
|
{
|
43
52
|
'refresh' => _('Refresh'),
|
@@ -75,7 +84,8 @@ class Setting
|
|
75
84
|
self.set('template_sync_branch', N_('Default branch in Git repo'), '', N_('Branch')),
|
76
85
|
self.set('template_sync_metadata_export_mode', N_('Default metadata export mode, refresh re-renders metadata, keep will keep existing metadata, remove exports template without metadata'), 'refresh', N_('Metadata export mode'), nil, { :collection => Proc.new { self.metadata_export_mode_types } }),
|
77
86
|
self.set('template_sync_force', N_('Should importing overwrite locked templates?'), false, N_('Force import')),
|
78
|
-
self.set('template_sync_lock', N_('
|
87
|
+
self.set('template_sync_lock', N_('How to handle lock for imported templates?'), 'keep', N_('Lock templates'), nil, { :collection => Proc.new { self.lock_types } }),
|
88
|
+
self.set('template_sync_commit_msg', N_('Custom commit message for templates export'), 'Templates export made by a Foreman user', N_('Commit message'))
|
79
89
|
].compact.each { |s| self.create! s.update(:category => "Setting::TemplateSync") }
|
80
90
|
end
|
81
91
|
|
@@ -1,42 +1,33 @@
|
|
1
1
|
module ForemanTemplates
|
2
2
|
class ExportResult
|
3
|
-
|
4
|
-
attr_reader :templates, :git_user, :branch, :repo
|
3
|
+
attr_reader :template, :name, :template_file, :exported, :additional_info
|
5
4
|
|
6
|
-
def initialize(
|
7
|
-
@
|
8
|
-
@
|
9
|
-
@
|
10
|
-
@
|
11
|
-
@warning = nil
|
12
|
-
@templates = []
|
13
|
-
@exported = false
|
14
|
-
end
|
15
|
-
|
16
|
-
def add_exported_templates(templates)
|
17
|
-
@templates.concat templates
|
5
|
+
def initialize(template, exported = true)
|
6
|
+
@template = template
|
7
|
+
@exported = exported
|
8
|
+
@name = template.name
|
9
|
+
@template_file = template.template_file
|
18
10
|
end
|
19
11
|
|
20
12
|
def to_h
|
21
|
-
{
|
22
|
-
:
|
23
|
-
:
|
24
|
-
:
|
25
|
-
:
|
26
|
-
:
|
13
|
+
{
|
14
|
+
:id => template.id,
|
15
|
+
:name => @name,
|
16
|
+
:exported => @exported,
|
17
|
+
:type => template.class.name.underscore,
|
18
|
+
:additional_info => @additional_info
|
19
|
+
}
|
27
20
|
end
|
28
21
|
|
29
|
-
|
30
|
-
|
31
|
-
def dumped_files_result
|
32
|
-
@templates.map { |template| to_template_h template }
|
22
|
+
def matching_filter
|
23
|
+
generic_info "Skipping, 'name' filtered out based on 'filter' and 'negate' settings"
|
33
24
|
end
|
34
25
|
|
35
|
-
def
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
26
|
+
def generic_info(additional_msg)
|
27
|
+
@exported = false
|
28
|
+
@additional_info = additional_msg
|
29
|
+
Logging.logger('app').debug "Not exporting #{@template.name}: #{additional_msg}"
|
30
|
+
self
|
40
31
|
end
|
41
32
|
end
|
42
33
|
end
|
@@ -14,6 +14,7 @@ module ForemanTemplates
|
|
14
14
|
:changed => changed?,
|
15
15
|
:imported => @imported,
|
16
16
|
:additional_errors => @additional_errors,
|
17
|
+
:additional_info => @additional_info,
|
17
18
|
:exception => @exception ? @exception.message : nil,
|
18
19
|
:validation_errors => errors.to_h,
|
19
20
|
:file => @template_file,
|
@@ -1,30 +1,32 @@
|
|
1
1
|
module ForemanTemplates
|
2
2
|
class TemplateExporter < Action
|
3
3
|
def self.setting_overrides
|
4
|
-
super + %i(metadata_export_mode)
|
4
|
+
super + %i(metadata_export_mode commit_msg)
|
5
|
+
end
|
6
|
+
|
7
|
+
def initialize(args = {})
|
8
|
+
super args
|
9
|
+
@result_lines = []
|
5
10
|
end
|
6
11
|
|
7
12
|
def export!
|
8
|
-
@export_result = ExportResult.new(@repo, @branch, foreman_git_user)
|
9
13
|
if git_repo?
|
10
14
|
export_to_git
|
11
15
|
else
|
12
16
|
export_to_files
|
13
17
|
end
|
14
|
-
|
15
|
-
return @export_result
|
18
|
+
export_result
|
16
19
|
end
|
17
20
|
|
18
21
|
def export_to_files
|
19
22
|
@dir = get_absolute_repo_path
|
20
23
|
verify_path!(@dir)
|
21
24
|
dump_files!
|
22
|
-
@export_result.exported = true
|
23
25
|
end
|
24
26
|
|
25
27
|
def export_to_git
|
26
28
|
@dir = Dir.mktmpdir
|
27
|
-
return
|
29
|
+
return if branch_missing?
|
28
30
|
|
29
31
|
git_repo = Git.clone(@repo, @dir)
|
30
32
|
logger.debug "cloned '#{@repo}' to '#{@dir}'"
|
@@ -33,30 +35,33 @@ module ForemanTemplates
|
|
33
35
|
dump_files!
|
34
36
|
git_repo.add
|
35
37
|
|
36
|
-
|
37
|
-
|
38
|
-
git_repo.
|
38
|
+
new_repo = false
|
39
|
+
begin
|
40
|
+
status = git_repo.status
|
41
|
+
rescue Git::GitExecuteError # no HEAD for repo without commits, git diff-index HEAD fails
|
42
|
+
new_repo = true
|
43
|
+
end
|
44
|
+
if new_repo || status.added.any? || status.changed.any? || status.deleted.any? || status.untracked.any?
|
45
|
+
git_repo.commit commit_msg
|
39
46
|
git_repo.push 'origin', branch
|
40
|
-
@export_result.exported = true
|
41
47
|
else
|
42
|
-
@
|
48
|
+
@warning = 'No change detected, skipping the commit and push'
|
43
49
|
end
|
44
50
|
rescue StandardError => e
|
45
|
-
@
|
51
|
+
@error = e.message
|
46
52
|
ensure
|
47
53
|
FileUtils.remove_entry_secure(@dir) if File.exist?(@dir)
|
48
|
-
@export_result
|
49
54
|
end
|
50
55
|
|
51
56
|
def setup_git_branch(git_repo)
|
52
57
|
logger.debug "checking out branch '#{@branch}'"
|
53
|
-
if git_repo.is_branch?(@branch)
|
58
|
+
if git_repo.is_branch?(@branch) # local branch
|
54
59
|
git_repo.checkout(@branch)
|
55
|
-
|
60
|
+
elsif git_repo.is_remote_branch?(@branch) # if we work with remote branch, checkout and sync
|
56
61
|
git_repo.branch(@branch).checkout
|
57
|
-
|
58
|
-
|
59
|
-
|
62
|
+
git_repo.reset_hard("origin/#{@branch}")
|
63
|
+
else # neither local nor remote
|
64
|
+
git_repo.checkout(@branch, :new_branch => true)
|
60
65
|
end
|
61
66
|
end
|
62
67
|
|
@@ -65,22 +70,18 @@ module ForemanTemplates
|
|
65
70
|
end
|
66
71
|
|
67
72
|
def dump_files!
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
bytes = file.write template.public_send(export_method)
|
77
|
-
logger.debug "finished writing #{bytes}"
|
78
|
-
end
|
73
|
+
templates_to_dump.map do |template|
|
74
|
+
current_dir = get_dump_dir(template)
|
75
|
+
FileUtils.mkdir_p current_dir
|
76
|
+
filename = File.join(current_dir, template.template_file)
|
77
|
+
File.open(filename, 'w+') do |file|
|
78
|
+
logger.debug "Writing to file #{filename}"
|
79
|
+
bytes = file.write template.public_send(export_method)
|
80
|
+
logger.debug "finished writing #{bytes}"
|
79
81
|
end
|
80
|
-
rescue StandardError => e
|
81
|
-
raise PathAccessException, e.message
|
82
82
|
end
|
83
|
-
|
83
|
+
rescue StandardError => e
|
84
|
+
raise PathAccessException, e.message
|
84
85
|
end
|
85
86
|
|
86
87
|
def get_dump_dir(template)
|
@@ -91,18 +92,23 @@ module ForemanTemplates
|
|
91
92
|
end
|
92
93
|
|
93
94
|
def templates_to_dump
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
95
|
+
find_templates.each do |template|
|
96
|
+
if filter.present?
|
97
|
+
exportable = template.name =~ /#{filter}/i ? !negate : negate
|
98
|
+
result = ExportResult.new(template, exportable)
|
99
|
+
next @result_lines << result.matching_filter unless exportable
|
100
|
+
|
101
|
+
@result_lines << result
|
102
|
+
else
|
103
|
+
@result_lines << ExportResult.new(template)
|
104
|
+
end
|
100
105
|
end
|
106
|
+
@result_lines.select(&:exported).map(&:template)
|
101
107
|
end
|
102
108
|
|
103
109
|
def branch_missing?
|
104
110
|
if @branch.blank?
|
105
|
-
@
|
111
|
+
@error = "Please specify a branch when exporting into a git repo"
|
106
112
|
return true
|
107
113
|
end
|
108
114
|
false
|
@@ -124,6 +130,13 @@ module ForemanTemplates
|
|
124
130
|
end
|
125
131
|
end
|
126
132
|
|
133
|
+
def export_result
|
134
|
+
{
|
135
|
+
:templates => @result_lines, :repo => @repo, :branch => @branch,
|
136
|
+
:git_user => foreman_git_user, :error => @error, :warning => @warning
|
137
|
+
}
|
138
|
+
end
|
139
|
+
|
127
140
|
private
|
128
141
|
|
129
142
|
def find_templates
|
@@ -10,7 +10,6 @@ module ForemanTemplates
|
|
10
10
|
super args
|
11
11
|
@verbose = parse_bool(@verbose)
|
12
12
|
@force = parse_bool(@force)
|
13
|
-
@lock = parse_bool(@lock)
|
14
13
|
@result_lines = []
|
15
14
|
end
|
16
15
|
|
@@ -79,11 +78,28 @@ module ForemanTemplates
|
|
79
78
|
end
|
80
79
|
|
81
80
|
def import_options
|
82
|
-
|
81
|
+
lock_predicate = lambda do |template|
|
82
|
+
case @lock
|
83
|
+
when 'lock'
|
84
|
+
return true
|
85
|
+
when 'unlock'
|
86
|
+
return false
|
87
|
+
when 'keep'
|
88
|
+
return template.new_record? ? false : template.locked
|
89
|
+
when 'keep_lock_new'
|
90
|
+
return template.new_record? ? true : template.locked
|
91
|
+
else
|
92
|
+
raise ::Foreman::Exception.new("Unknown lock option type, expected one of #{::Setting::TemplateSync.lock_types.keys}, got #{@lock}")
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
{
|
97
|
+
:force => @force,
|
83
98
|
:associate => @associate,
|
84
|
-
:lock =>
|
99
|
+
:lock => lock_predicate,
|
85
100
|
:organization_params => @taxonomies[:organizations],
|
86
|
-
:location_params => @taxonomies[:locations]
|
101
|
+
:location_params => @taxonomies[:locations]
|
102
|
+
}
|
87
103
|
end
|
88
104
|
|
89
105
|
def template_model(metadata, parse_result)
|
@@ -1,5 +1,9 @@
|
|
1
|
-
|
2
|
-
<%=
|
1
|
+
<% content_for(:javascripts) do %>
|
2
|
+
<%= webpacked_plugins_js_for :foreman_templates %>
|
3
|
+
<% end %>
|
4
|
+
<% content_for(:stylesheets) do %>
|
5
|
+
<%= webpacked_plugins_css_for :foreman_templates %>
|
6
|
+
<% end %>
|
3
7
|
|
4
8
|
<div id="foreman-templates"/>
|
5
9
|
|
@@ -1,7 +1,7 @@
|
|
1
|
-
object @
|
1
|
+
object @template_result
|
2
2
|
|
3
|
-
attributes :name, :template_file
|
3
|
+
attributes :name, :template_file, :additional_info
|
4
4
|
|
5
|
-
node(false) do |
|
6
|
-
partial "ui_template_syncs/template_attrs", :object => template
|
5
|
+
node(false) do |result|
|
6
|
+
partial "ui_template_syncs/template_attrs", :object => result.template
|
7
7
|
end
|
@@ -20,6 +20,12 @@ module ForemanTemplates
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
+
initializer "foreman_templates.load_app_instance_data" do |app|
|
24
|
+
ForemanTemplates::Engine.paths['db/migrate'].existent.each do |path|
|
25
|
+
app.config.paths['db/migrate'] << path
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
23
29
|
initializer 'foreman_templates.register_plugin', :before => :finisher_hook do
|
24
30
|
Foreman::Plugin.register :foreman_templates do
|
25
31
|
requires_foreman '>= 1.24'
|