foreman_templates 5.0.0 → 5.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/controllers/api/v2/template_controller.rb +32 -5
- data/app/models/concerns/foreman_templates/provisioning_template_import.rb +88 -54
- data/app/models/concerns/foreman_templates/ptable_import.rb +83 -51
- data/app/models/concerns/foreman_templates/template_import.rb +55 -44
- data/app/models/setting/template_sync.rb +47 -45
- data/app/services/foreman_templates/action.rb +1 -1
- data/app/services/foreman_templates/template_exporter.rb +4 -3
- data/app/services/foreman_templates/template_importer.rb +35 -23
- data/config/routes.rb +4 -5
- data/lib/foreman_templates/engine.rb +3 -0
- data/lib/foreman_templates/version.rb +1 -1
- data/lib/tasks/foreman_templates_tasks.rake +10 -11
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3889bfa2270632f8e0bfacc4a35a1a85c3eca1e6
|
4
|
+
data.tar.gz: 797795544b68cbb4b2d408e8293e6689d1e3ff02
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: da4517d69b72c25c3065db6f0afba3621b6165c9c7a8e317a52e0a4c24a81b369fac520cc50ea102ea7accc8b9934452ef38b4a6e270e15d28c2a606e4105688
|
7
|
+
data.tar.gz: ae821ae58dedaaa29a83e41924b1ac30383c0d40e827c6d039397ac7bfd95f7923c94d418fcc1f89bcc59a020ae3b58b385db90bcb71353420384e8b31d4ba01
|
@@ -1,15 +1,17 @@
|
|
1
1
|
module Api
|
2
2
|
module V2
|
3
3
|
class TemplateController < ::Api::V2::BaseController
|
4
|
-
|
5
4
|
api :POST, "/template/import/", N_("Initiate Import")
|
6
|
-
param :
|
5
|
+
param :verbose, :bool, :required => false, :desc => N_("Set verbosity of import")
|
6
|
+
param :repo, String, :required => false, :desc => N_("Override the default repo from settings.")
|
7
7
|
param :branch, String, :required => false, :desc => N_("Branch in Git repo.")
|
8
8
|
param :prefix, String, :required => false, :desc => N_("The string all imported templates should begin with.")
|
9
9
|
param :dirname, String, :required => false, :desc => N_("The directory within the git tree containing the templates.")
|
10
|
-
param :filter, String, :required => false, :desc => N_("Import names matching this regex (case-insensitive; snippets are not filtered).")
|
10
|
+
param :filter, String, :required => false, :desc => N_("Import templates with names matching this regex (case-insensitive; snippets are not filtered).")
|
11
11
|
param :negate, :bool, :required => false, :desc => N_("Negate the prefix (for purging).")
|
12
|
-
param :associate,
|
12
|
+
param :associate, Setting::TemplateSync.associate_types.keys, :required => false, :desc => N_("Associate to OS's, Locations & Organizations. Options are: always, new or never.")
|
13
|
+
param :force, :bool, :required => false, :desc => N_("Update templates that are locked")
|
14
|
+
|
13
15
|
def import
|
14
16
|
results = ForemanTemplates::TemplateImporter.new({
|
15
17
|
verbose: params['verbose'],
|
@@ -20,8 +22,33 @@ module Api
|
|
20
22
|
filter: params['filter'],
|
21
23
|
associate: params['associate'],
|
22
24
|
negate: params['negate'],
|
25
|
+
force: params['force']
|
23
26
|
}).import!
|
24
|
-
render :json => {:message => results}
|
27
|
+
render :json => { :message => results }
|
28
|
+
end
|
29
|
+
|
30
|
+
api :POST, "/template/export", N_("Initiate Export")
|
31
|
+
param :verbose, :bool, :required => false, :desc => N_("Set verbosity of export")
|
32
|
+
param :repo, String, :required => false, :desc => N_("Override the default repo from settings")
|
33
|
+
param :branch, String, :required => false, :desc => N_("Branch in Git repo.")
|
34
|
+
param :filter, String, :required => false, :desc => N_("Export templates with names matching this regex (case-insensitive; snippets are not filtered).")
|
35
|
+
param :negate, :bool, :required => false, :desc => N_("Negate the prefix (for purging).")
|
36
|
+
param :metadata_export_mode, Setting::TemplateSync.metadata_export_mode_types.keys, :required => false, :desc => N_("Specify how to handle metadata")
|
37
|
+
param :dir, String, :required => false, :desc => N_("The directory within Git repo")
|
38
|
+
def export
|
39
|
+
ForemanTemplates::TemplateExporter.new({
|
40
|
+
verbose: params['verbose'],
|
41
|
+
repo: params['repo'],
|
42
|
+
branch: params['branch'],
|
43
|
+
filter: params['filter'],
|
44
|
+
negate: params['negate'],
|
45
|
+
metadata_export_mode: params['metadata_export_mode'],
|
46
|
+
dir: params['dir']
|
47
|
+
}).export!
|
48
|
+
render :json => { :message => _('Success') }
|
49
|
+
rescue => e
|
50
|
+
logger.debug e
|
51
|
+
render :json => { :message => (_('Something went wrong during export: %s') % e.message) }, :status => 500
|
25
52
|
end
|
26
53
|
end
|
27
54
|
end
|
@@ -1,60 +1,94 @@
|
|
1
|
-
module ForemanTemplates
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
1
|
+
module ForemanTemplates
|
2
|
+
module ProvisioningTemplateImport
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
def import!(name, text, metadata, force = false)
|
7
|
+
# Check for snippet type
|
8
|
+
return import_snippet!(name, text, force) if metadata['snippet'] || metadata['kind'] == 'snippet'
|
9
|
+
|
10
|
+
# Get template type
|
11
|
+
kind = TemplateKind.find_by(name: metadata['kind'])
|
12
|
+
raise NoKindError unless kind
|
13
|
+
|
14
|
+
# Data
|
15
|
+
template = ProvisioningTemplate.where(:name => name).first_or_initialize
|
16
|
+
data = {
|
17
|
+
:template => text,
|
18
|
+
:snippet => false,
|
19
|
+
:template_kind_id => kind.id
|
20
|
+
}
|
21
|
+
oses = map_metadata(metadata, 'oses')
|
22
|
+
locations = map_metadata(metadata, 'locations')
|
23
|
+
organizations = map_metadata(metadata, 'organizations')
|
24
|
+
|
25
|
+
# Printout helpers
|
26
|
+
c_or_u = template.new_record? ? 'Creating' : 'Updating'
|
27
|
+
id_string = template.new_record? ? '' : "id #{template.id}"
|
28
|
+
if template.locked? && !template.new_record? && !force
|
29
|
+
return { :diff => nil,
|
30
|
+
:status => false,
|
31
|
+
:result => "Skipping Template #{id_string}:#{name} - template is locked" }
|
32
|
+
end
|
33
|
+
|
34
|
+
associate_metadata data, template, metadata, oses, organizations, locations
|
35
|
+
|
36
|
+
diff = nil
|
37
|
+
status = nil
|
38
|
+
if template_changed?(data, template)
|
39
|
+
diff = create_diff(data, template)
|
40
|
+
template.ignore_locking do
|
41
|
+
status = template.update_attributes(data)
|
42
|
+
end
|
43
|
+
result = build_associations_result c_or_u, id_string, name, oses, organizations, locations
|
44
|
+
else
|
45
|
+
status = true
|
46
|
+
result = " No change to Template #{id_string}:#{name}"
|
47
|
+
end
|
48
|
+
|
49
|
+
{ :diff => diff, :status => status, :result => result, :errors => template.errors }
|
50
|
+
end
|
51
|
+
|
52
|
+
def associate_metadata(data, template, metadata, oses, organizations, locations)
|
53
|
+
if (metadata['associate'] == 'new' && template.new_record?) || (metadata['associate'] == 'always')
|
54
|
+
data[:operatingsystem_ids] = oses.map(&:id)
|
55
|
+
data[:location_ids] = locations.map(&:id)
|
56
|
+
data[:organization_ids] = organizations.map(&:id)
|
57
|
+
end
|
58
|
+
data
|
59
|
+
end
|
60
|
+
|
61
|
+
def build_associations_result(c_or_u, id_string, name, oses, organizations, locations)
|
62
|
+
res = " #{c_or_u} Template #{id_string}:#{name}"
|
63
|
+
res += "\n Operatingsystem Associations:\n - #{oses.map(&:fullname).join("\n - ")}" unless oses.empty?
|
64
|
+
res += "\n Organizations Associations:\n - #{organizations.map(&:name).join("\n - ")}" unless organizations.empty?
|
65
|
+
res += "\n Location Associations:\n - #{locations.map(&:name).join("\n - ")}" unless locations.empty?
|
66
|
+
res
|
67
|
+
end
|
68
|
+
|
69
|
+
def create_diff(data, template)
|
70
|
+
if template_content_changed?(template.template, data[:template])
|
71
|
+
Diffy::Diff.new(
|
72
|
+
template.template,
|
73
|
+
data[:template],
|
74
|
+
:include_diff_info => true
|
75
|
+
).to_s(:color)
|
76
|
+
else
|
77
|
+
nil
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def template_content_changed?(template_template, data_template)
|
82
|
+
template_template != data_template
|
83
|
+
end
|
84
|
+
|
85
|
+
def associations_changed?(data)
|
86
|
+
!(data[:operatingsystem_ids] || data[:location_ids] || data[:organization_ids]).nil?
|
32
87
|
end
|
33
88
|
|
34
|
-
|
35
|
-
|
36
|
-
template.template,
|
37
|
-
data[:template],
|
38
|
-
:include_diff_info => true
|
39
|
-
).to_s(:color)
|
40
|
-
status = template.update_attributes(data)
|
41
|
-
result = " #{c_or_u} Template #{id_string}:#{name}"
|
42
|
-
result += "\n Operatingsystem Associations:\n - #{oses.map(&:fullname).join("\n - ")}" unless oses.empty?
|
43
|
-
result += "\n Organizations Associations:\n - #{organizations.map(&:name).join("\n - ")}" unless organizations.empty?
|
44
|
-
result += "\n Location Associations:\n - #{locations.map(&:name).join("\n - ")}" unless locations.empty?
|
45
|
-
elsif data[:operatingsystem_ids] || data[:location_ids] || data[:organization_ids]
|
46
|
-
diff = nil
|
47
|
-
status = template.update_attributes(data)
|
48
|
-
result = " #{c_or_u} Template Associations #{id_string}:#{name}"
|
49
|
-
result += "\n Operatingsystem Associations:\n - #{oses.map(&:fullname).join("\n - ")}" unless oses.empty?
|
50
|
-
result += "\n Organizations Associations:\n - #{organizations.map(&:name).join("\n - ")}" unless organizations.empty?
|
51
|
-
result += "\n Location Associations:\n - #{locations.map(&:name).join("\n - ")}" unless locations.empty?
|
52
|
-
else
|
53
|
-
diff = nil
|
54
|
-
status = true
|
55
|
-
result = " No change to Template #{id_string}:#{name}"
|
89
|
+
def template_changed?(data, template)
|
90
|
+
template_content_changed?(template.template, data[:template]) || associations_changed?(data)
|
56
91
|
end
|
57
|
-
{ :diff => diff, :status => status, :result => result }
|
58
92
|
end
|
59
93
|
end
|
60
94
|
end
|
@@ -1,57 +1,89 @@
|
|
1
|
-
module ForemanTemplates
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
1
|
+
module ForemanTemplates
|
2
|
+
module PtableImport
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
def import!(name, text, metadata, force = false)
|
7
|
+
# Check for snippet type
|
8
|
+
return import_snippet!(name, text, force) if metadata['snippet']
|
9
|
+
|
10
|
+
# Data
|
11
|
+
ptable = Ptable.where(:name => name).first_or_initialize
|
12
|
+
data = {
|
13
|
+
:layout => text
|
14
|
+
}
|
15
|
+
oses = map_metadata(metadata, 'oses')
|
16
|
+
locations = map_metadata(metadata, 'locations')
|
17
|
+
organizations = map_metadata(metadata, 'organizations')
|
18
|
+
|
19
|
+
# Printout helpers
|
20
|
+
c_or_u = ptable.new_record? ? 'Creating' : 'Updating'
|
21
|
+
id_string = ptable.new_record? ? '' : "id #{ptable.id}"
|
22
|
+
if ptable.locked? && !ptable.new_record? && !force
|
23
|
+
return { :diff => nil,
|
24
|
+
:status => false,
|
25
|
+
:result => "Skipping Partition Table #{id_string}:#{name} - partition table is locked" }
|
26
|
+
end
|
27
|
+
|
28
|
+
associate_metadata data, ptable, metadata, oses, organizations, locations
|
29
|
+
|
30
|
+
diff = nil
|
31
|
+
status = nil
|
32
|
+
if ptable_changed?(data, ptable)
|
33
|
+
diff = create_diff(data, ptable)
|
34
|
+
ptable.ignore_locking do
|
35
|
+
status = ptable.update_attributes(data)
|
36
|
+
end
|
37
|
+
result = build_associations_result c_or_u, id_string, name, oses, organizations, locations
|
38
|
+
else
|
39
|
+
status = true
|
40
|
+
result = " No change to Ptable #{id_string}:#{name}"
|
41
|
+
end
|
42
|
+
{ :diff => diff, :status => status, :result => result, :errors => ptable.errors }
|
43
|
+
end
|
44
|
+
|
45
|
+
def associate_metadata(data, ptable, metadata, oses, organizations, locations)
|
46
|
+
if (metadata['associate'] == 'new' && ptable.new_record?) || (metadata['associate'] == 'always')
|
47
|
+
data[:operatingsystem_ids] = oses.map(&:id)
|
48
|
+
data[:os_family] = oses.map(&:family).uniq.first
|
49
|
+
data[:location_ids] = locations.map(&:id)
|
50
|
+
data[:organization_ids] = organizations.map(&:id)
|
51
|
+
end
|
52
|
+
data
|
53
|
+
end
|
54
|
+
|
55
|
+
def ptable_content_changed?(data_layout, ptable_layout)
|
56
|
+
data_layout != ptable_layout
|
57
|
+
end
|
58
|
+
|
59
|
+
def associations_changed?(data)
|
60
|
+
!(data[:os_family] || data[:location_ids] || data[:organization_ids]).nil?
|
61
|
+
end
|
62
|
+
|
63
|
+
def create_diff(data, ptable)
|
64
|
+
if ptable_content_changed?(data[:layout], ptable.layout)
|
65
|
+
Diffy::Diff.new(
|
66
|
+
ptable.layout,
|
67
|
+
data[:layout],
|
68
|
+
:include_diff_info => true
|
69
|
+
).to_s(:color)
|
70
|
+
else
|
71
|
+
nil
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def build_associations_result(c_or_u, id_string, name, oses, organizations, locations)
|
76
|
+
res = " #{c_or_u} Ptable #{id_string}:#{name}"
|
77
|
+
res += "\n Operatingsystem Family:\n - #{oses.map(&:family).uniq.first}" unless oses.empty?
|
78
|
+
res += "\n Operatingsystem Associations:\n - #{oses.map(&:fullname).join("\n - ")}" unless oses.empty?
|
79
|
+
res += "\n Organizations Associations:\n - #{organizations.map(&:name).join("\n - ")}" unless organizations.empty?
|
80
|
+
res += "\n Location Associations:\n - #{locations.map(&:name).join("\n - ")}" unless locations.empty?
|
81
|
+
res
|
27
82
|
end
|
28
83
|
|
29
|
-
|
30
|
-
|
31
|
-
ptable.layout,
|
32
|
-
data[:layout],
|
33
|
-
:include_diff_info => true
|
34
|
-
).to_s(:color)
|
35
|
-
status = ptable.update_attributes(data)
|
36
|
-
result = " #{c_or_u} Ptable #{id_string}:#{name}"
|
37
|
-
result += "\n Operatingsystem Family:\n - #{oses.map(&:family).uniq.first}" unless oses.empty?
|
38
|
-
result += "\n Operatingsystem Associations:\n - #{oses.map(&:fullname).join("\n - ")}" unless oses.empty?
|
39
|
-
result += "\n Organizations Associations:\n - #{organizations.map(&:name).join("\n - ")}" unless organizations.empty?
|
40
|
-
result += "\n Location Associations:\n - #{locations.map(&:name).join("\n - ")}" unless locations.empty?
|
41
|
-
elsif data[:os_family] || data[:location_ids] || data[:organization_ids]
|
42
|
-
diff = nil
|
43
|
-
status = ptable.update_attributes(data)
|
44
|
-
result = " #{c_or_u} Ptable Associations #{id_string}:#{name}"
|
45
|
-
result += "\n Operatingsystem Family:\n - #{oses.map(&:family).uniq.first}" unless oses.empty?
|
46
|
-
result += "\n Operatingsystem Associations:\n - #{oses.map(&:fullname).join("\n - ")}" unless oses.empty?
|
47
|
-
result += "\n Organizations Associations:\n - #{organizations.map(&:name).join("\n - ")}" unless organizations.empty?
|
48
|
-
result += "\n Location Associations:\n - #{locations.map(&:name).join("\n - ")}" unless locations.empty?
|
49
|
-
else
|
50
|
-
diff = nil
|
51
|
-
status = true
|
52
|
-
result = " No change to Ptable #{id_string}:#{name}"
|
84
|
+
def ptable_changed?(data, ptable)
|
85
|
+
ptable_content_changed?(data[:layout], ptable.layout) || associations_changed?(data)
|
53
86
|
end
|
54
|
-
{ :diff => diff, :status => status, :result => result }
|
55
87
|
end
|
56
88
|
end
|
57
89
|
end
|
@@ -1,53 +1,64 @@
|
|
1
|
-
module ForemanTemplates
|
2
|
-
|
1
|
+
module ForemanTemplates
|
2
|
+
module TemplateImport
|
3
|
+
extend ActiveSupport::Concern
|
3
4
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
5
|
+
module ClassMethods
|
6
|
+
def import_snippet!(name, text, force = false)
|
7
|
+
# Data
|
8
|
+
snippet = self.where(:name => name).first_or_initialize
|
9
|
+
data = {
|
10
|
+
:template => text,
|
11
|
+
:snippet => true
|
12
|
+
}
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
14
|
+
# Printout helpers
|
15
|
+
c_or_u = snippet.new_record? ? 'Creating' : 'Updating'
|
16
|
+
id_string = snippet.new_record? ? '' : "id #{snippet.id}"
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
status
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
18
|
+
if snippet.locked? && !snippet.new_record? && !force
|
19
|
+
return { :diff => nil,
|
20
|
+
:status => false,
|
21
|
+
:result => "Skipping snippet #{id_string}:#{name} - template is locked" }
|
22
|
+
end
|
23
|
+
|
24
|
+
status = nil
|
25
|
+
if data[:template] != snippet.template
|
26
|
+
diff = Diffy::Diff.new(
|
27
|
+
snippet.template,
|
28
|
+
data[:template],
|
29
|
+
:include_diff_info => true
|
30
|
+
).to_s(:color)
|
31
|
+
snippet.ignore_locking do
|
32
|
+
status = snippet.update_attributes(data)
|
33
|
+
end
|
34
|
+
result = " #{c_or_u} Snippet #{id_string}:#{name}"
|
35
|
+
else
|
36
|
+
diff = nil
|
37
|
+
status = true
|
38
|
+
result = " No change to Snippet #{id_string}:#{name}"
|
39
|
+
end
|
40
|
+
{ :diff => diff, :status => status, :result => result, :errors => snippet.errors }
|
29
41
|
end
|
30
|
-
{ :diff => diff, :status => status, :result => result }
|
31
|
-
end
|
32
42
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
43
|
+
def map_metadata(metadata, param)
|
44
|
+
if metadata[param]
|
45
|
+
case param
|
46
|
+
when 'oses'
|
47
|
+
metadata[param].map do |os|
|
48
|
+
Operatingsystem.all.map { |db| db.to_label =~ /^#{os}/ ? db : nil }
|
49
|
+
end.flatten.compact
|
50
|
+
when 'locations'
|
51
|
+
metadata[param].map do |loc|
|
52
|
+
User.current.my_locations.map { |db| db.name =~ /^#{loc}/ ? db : nil }
|
53
|
+
end.flatten.compact
|
54
|
+
when 'organizations'
|
55
|
+
metadata[param].map do |org|
|
56
|
+
User.current.my_organizations.map { |db| db.name =~ /^#{org}/ ? db : nil }
|
57
|
+
end.flatten.compact
|
58
|
+
end
|
59
|
+
else
|
60
|
+
[]
|
48
61
|
end
|
49
|
-
else
|
50
|
-
[]
|
51
62
|
end
|
52
63
|
end
|
53
64
|
end
|
@@ -1,54 +1,56 @@
|
|
1
|
-
class Setting
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
def self.metadata_export_mode_types
|
11
|
-
{
|
12
|
-
'refresh' => _('Refresh'),
|
13
|
-
'keep' => _('Keep'),
|
14
|
-
'remove' => _('Remove')
|
15
|
-
}
|
16
|
-
end
|
17
|
-
|
18
|
-
|
19
|
-
def self.load_defaults
|
20
|
-
return unless super
|
21
|
-
|
22
|
-
%w(template_sync_filter template_sync_branch).each { |s| Setting::BLANK_ATTRS << s }
|
1
|
+
class Setting
|
2
|
+
class TemplateSync < ::Setting
|
3
|
+
def self.associate_types
|
4
|
+
{
|
5
|
+
'always' => _('Always'),
|
6
|
+
'new' => _('New'),
|
7
|
+
'never' => _('Never')
|
8
|
+
}
|
9
|
+
end
|
23
10
|
|
24
|
-
self.
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
self.set('template_sync_filter', N_('Import or export names matching this regex (case-insensitive; snippets are not filtered)'), nil, N_('Filter')),
|
31
|
-
self.set('template_sync_repo', N_('Default Git repo to sync from'), 'https://github.com/theforeman/community-templates.git', N_('Repo')),
|
32
|
-
self.set('template_sync_negate', N_('Negate the prefix (for purging) / filter (for importing/exporting)'), false, N_('Negate')),
|
33
|
-
self.set('template_sync_branch', N_('Default branch in Git repo'), nil, N_('Branch')),
|
34
|
-
self.set('template_sync_metadata_export_mode', N_('Default metadata export mode, refresh re-renders metadata, keep will keep existing metadata, remove exports template withou metadata'), 'refresh', N_('Metadata export mode'), nil, { :collection => Proc.new { self.metadata_export_mode_types } })
|
35
|
-
].compact.each { |s| self.create! s.update(:category => "Setting::TemplateSync") }
|
11
|
+
def self.metadata_export_mode_types
|
12
|
+
{
|
13
|
+
'refresh' => _('Refresh'),
|
14
|
+
'keep' => _('Keep'),
|
15
|
+
'remove' => _('Remove')
|
16
|
+
}
|
36
17
|
end
|
37
18
|
|
38
|
-
|
39
|
-
|
19
|
+
def self.load_defaults
|
20
|
+
return unless super
|
21
|
+
|
22
|
+
%w(template_sync_filter template_sync_branch).each { |s| Setting::BLANK_ATTRS << s }
|
23
|
+
|
24
|
+
self.transaction do
|
25
|
+
[
|
26
|
+
self.set('template_sync_verbose', N_('Choose verbosity for Rake task importing templates'), false, N_('Verbosity')),
|
27
|
+
self.set('template_sync_associate', N_('Associate templates to OS'), 'new', N_('Associate'), nil, { :collection => Proc.new { self.associate_types } }),
|
28
|
+
self.set('template_sync_prefix', N_('The string all imported templates should begin with'), "Community ", N_('Prefix')),
|
29
|
+
self.set('template_sync_dirname', N_('The directory within the Git repo containing the templates'), '/', N_('Dirname')),
|
30
|
+
self.set('template_sync_filter', N_('Import or export names matching this regex (case-insensitive; snippets are not filtered)'), nil, N_('Filter')),
|
31
|
+
self.set('template_sync_repo', N_('Default Git repo to sync from'), 'https://github.com/theforeman/community-templates.git', N_('Repo')),
|
32
|
+
self.set('template_sync_negate', N_('Negate the prefix (for purging) / filter (for importing/exporting)'), false, N_('Negate')),
|
33
|
+
self.set('template_sync_branch', N_('Default branch in Git repo'), nil, N_('Branch')),
|
34
|
+
self.set('template_sync_metadata_export_mode', N_('Default metadata export mode, refresh re-renders metadata, keep will keep existing metadata, remove exports template withou metadata'), 'refresh', N_('Metadata export mode'), nil, { :collection => Proc.new { self.metadata_export_mode_types } }),
|
35
|
+
self.set('template_sync_force', N_('Should importing overwrite locked templates?'), false, N_('Force import')),
|
36
|
+
].compact.each { |s| self.create! s.update(:category => "Setting::TemplateSync") }
|
37
|
+
end
|
38
|
+
|
39
|
+
true
|
40
|
+
end
|
40
41
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
42
|
+
def validate_template_sync_associate(record)
|
43
|
+
values = record.class.associate_types.keys
|
44
|
+
if record.value && !values.include?(record.value)
|
45
|
+
record.errors[:base] << (_("template_sync_associate must be one of %s") % values.join(', '))
|
46
|
+
end
|
45
47
|
end
|
46
|
-
end
|
47
48
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
49
|
+
def validate_template_sync_metadata_export_mode(record)
|
50
|
+
values = record.class.metadata_export_mode_types.keys
|
51
|
+
if record.value && !values.include?(record.value)
|
52
|
+
record.errors[:base] << (_("template_sync_metadata_export_mode must be one of %s") % values.join(', '))
|
53
|
+
end
|
52
54
|
end
|
53
55
|
end
|
54
56
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'shellwords'
|
2
|
+
|
1
3
|
module ForemanTemplates
|
2
4
|
class TemplateExporter < Action
|
3
5
|
def self.setting_overrides
|
@@ -43,7 +45,7 @@ module ForemanTemplates
|
|
43
45
|
|
44
46
|
status = git_repo.status
|
45
47
|
if status.added.any? || status.changed.any? || status.deleted.any? || status.untracked.any?
|
46
|
-
logger.debug
|
48
|
+
logger.debug 'committing changes in cloned repo'
|
47
49
|
git_repo.commit "Templates export made by Foreman user #{User.current.try(:login) || User::ANONYMOUS_ADMIN}"
|
48
50
|
|
49
51
|
logger.debug "pushing to branch #{branch} at origin #{@repo}"
|
@@ -70,7 +72,7 @@ module ForemanTemplates
|
|
70
72
|
end
|
71
73
|
|
72
74
|
def get_template_filename(template)
|
73
|
-
template.name.downcase.tr(' ', '_') + '.erb'
|
75
|
+
Shellwords.escape(template.name.downcase.tr(' /', '_') + '.erb')
|
74
76
|
end
|
75
77
|
|
76
78
|
def get_dump_dir(template)
|
@@ -105,6 +107,5 @@ module ForemanTemplates
|
|
105
107
|
raise "Unknown metadata export mode #{@metadata_export_mode}"
|
106
108
|
end
|
107
109
|
end
|
108
|
-
|
109
110
|
end
|
110
111
|
end
|
@@ -1,24 +1,18 @@
|
|
1
|
-
class NoKindError <
|
2
|
-
class MissingKindError <
|
1
|
+
class NoKindError < RuntimeError; end
|
2
|
+
class MissingKindError < RuntimeError; end
|
3
3
|
|
4
4
|
module ForemanTemplates
|
5
5
|
class TemplateImporter < Action
|
6
6
|
attr_accessor :metadata, :name, :text
|
7
7
|
|
8
8
|
def self.setting_overrides
|
9
|
-
super + %i(associate)
|
9
|
+
super + %i(associate force)
|
10
10
|
end
|
11
11
|
|
12
12
|
def initialize(args = {})
|
13
13
|
super
|
14
|
-
|
15
|
-
|
16
|
-
@verbose = if @verbose == 'false'
|
17
|
-
false
|
18
|
-
else
|
19
|
-
true
|
20
|
-
end
|
21
|
-
end
|
14
|
+
@verbose = parse_bool(@verbose)
|
15
|
+
@force = parse_bool(@force)
|
22
16
|
end
|
23
17
|
|
24
18
|
def import!
|
@@ -69,23 +63,23 @@ module ForemanTemplates
|
|
69
63
|
if @filter
|
70
64
|
matching = name.match(/#{@filter}/i)
|
71
65
|
matching = !matching if @negate
|
72
|
-
next
|
66
|
+
next unless matching
|
73
67
|
end
|
74
68
|
|
75
69
|
begin
|
76
|
-
# Expects a return of { :diff, :status, :result }
|
70
|
+
# Expects a return of { :diff, :status, :result, :errors }
|
77
71
|
data = if metadata['model'].present?
|
78
|
-
metadata['model'].constantize.import!(name, text, metadata)
|
72
|
+
metadata['model'].constantize.import!(name, text, metadata, @force)
|
79
73
|
else
|
80
74
|
# For backwards-compat before "model" metadata was added
|
81
75
|
case metadata['kind']
|
82
76
|
when 'ptable'
|
83
|
-
Ptable.import!(name, text, metadata)
|
77
|
+
Ptable.import!(name, text, metadata, @force)
|
84
78
|
when 'job_template'
|
85
79
|
# TODO: update REX templates to have `model` and delete this
|
86
80
|
update_job_template(name, text)
|
87
81
|
else
|
88
|
-
ProvisioningTemplate.import!(name, text, metadata)
|
82
|
+
ProvisioningTemplate.import!(name, text, metadata, @force)
|
89
83
|
end
|
90
84
|
end
|
91
85
|
|
@@ -93,12 +87,12 @@ module ForemanTemplates
|
|
93
87
|
data[:diff] = calculate_diff(data[:old], data[:new])
|
94
88
|
end
|
95
89
|
|
96
|
-
if
|
90
|
+
if @verbose
|
97
91
|
result_lines << data[:result]
|
98
92
|
result_lines << data[:diff] unless data[:diff].nil?
|
99
|
-
elsif data[:status] == false
|
100
|
-
result_lines << "Template \"#{name}\": #{data[:result]}"
|
101
93
|
end
|
94
|
+
result_lines << status_to_text(data[:status], name)
|
95
|
+
result_lines << data[:errors] unless data[:errors].empty?
|
102
96
|
rescue MissingKindError
|
103
97
|
result_lines << " Skipping: '#{name}' - No template kind or model detected"
|
104
98
|
next
|
@@ -136,10 +130,12 @@ module ForemanTemplates
|
|
136
130
|
puts 'Deprecation warning: JobTemplate support is moving to the Remote Execution plugin'
|
137
131
|
puts "- please add 'model: JobTemplate' to the metadata in '#{file}' to call the right method"
|
138
132
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
133
|
+
unless defined?(JobTemplate)
|
134
|
+
return {
|
135
|
+
:status => false,
|
136
|
+
:result => 'Skipping job template import, remote execution plugin is not installed.'
|
137
|
+
}
|
138
|
+
end
|
143
139
|
template = JobTemplate.import(
|
144
140
|
text.sub(/^name: .*$/, "name: #{name}"),
|
145
141
|
:update => true
|
@@ -167,5 +163,21 @@ module ForemanTemplates
|
|
167
163
|
template.destroy
|
168
164
|
end
|
169
165
|
end # :purge
|
166
|
+
|
167
|
+
private
|
168
|
+
|
169
|
+
def parse_bool(bool_name)
|
170
|
+
bool_name.is_a?(String) ? bool_name != 'false' : bool_name
|
171
|
+
end
|
172
|
+
|
173
|
+
def status_to_text(status, name)
|
174
|
+
msg = "#{name} - import "
|
175
|
+
msg << if status
|
176
|
+
"success"
|
177
|
+
else
|
178
|
+
'failure'
|
179
|
+
end
|
180
|
+
msg
|
181
|
+
end
|
170
182
|
end
|
171
183
|
end
|
data/config/routes.rb
CHANGED
@@ -1,13 +1,12 @@
|
|
1
1
|
Rails.application.routes.draw do
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
resources :templates, :controller => :template, :only => :import do
|
2
|
+
namespace :api, :defaults => { :format => 'json' } do
|
3
|
+
scope "(:apiv)", :module => :v2, :defaults => { :apiv => 'v2' }, :apiv => /v1|v2/, :constraints => ApiConstraints.new(:version => 2) do
|
4
|
+
resources :templates, :controller => :template, :only => [] do
|
6
5
|
collection do
|
7
6
|
post 'import'
|
7
|
+
post 'export'
|
8
8
|
end
|
9
9
|
end
|
10
10
|
end
|
11
11
|
end
|
12
|
-
|
13
12
|
end
|
@@ -23,6 +23,9 @@ module ForemanTemplates
|
|
23
23
|
permission :import_templates, {
|
24
24
|
:"api/v2/template" => [:import]
|
25
25
|
}, :resource_type => 'Template'
|
26
|
+
permission :export_templates, {
|
27
|
+
:"api/v2/template" => [:export]
|
28
|
+
}, :resource_type => 'Template'
|
26
29
|
end
|
27
30
|
add_all_permissions_to_default_roles
|
28
31
|
end
|
@@ -6,13 +6,13 @@ namespace :templates do
|
|
6
6
|
ActiveSupport::Deprecation.warn('templates:sync task has been renamed to templates:import and will be removed in a future version')
|
7
7
|
end
|
8
8
|
# Available options:
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
9
|
+
# * verbose => Print extra information during the run [false]
|
10
|
+
# * repo => Sync templates from a different Git repo [https://github.com/theforeman/community-templates]
|
11
|
+
# * branch => Branch in Git repo [default branch]
|
12
|
+
# * prefix => The string all imported templates should begin with [Community ]
|
13
|
+
# * dirname => The directory within the git tree containing the templates [/]
|
14
|
+
# * filter => Import names matching this regex (case-insensitive; snippets are not filtered)
|
15
|
+
# * associate => Associate to OS's, Locations & Organizations. Options are: always, new or never [new]
|
16
16
|
|
17
17
|
User.current = User.anonymous_admin
|
18
18
|
|
@@ -54,9 +54,9 @@ namespace :templates do
|
|
54
54
|
User.current = User.anonymous_admin
|
55
55
|
|
56
56
|
ForemanTemplates::TemplateImporter.new({
|
57
|
-
|
58
|
-
|
59
|
-
|
57
|
+
# * negate => negate query [false]
|
58
|
+
# * prefix => The string all templates to purge should ( or not ) begin with [Community ]
|
59
|
+
# * verbose => Print extra information during the run [false]
|
60
60
|
negate: ENV['negate'],
|
61
61
|
prefix: ENV['prefix'],
|
62
62
|
verbose: ENV['verbose'],
|
@@ -69,7 +69,6 @@ namespace :templates do
|
|
69
69
|
ForemanTemplates::Cleaner.new.clean_up!
|
70
70
|
puts 'Clean up finished, you can now remove the plugin from your system'
|
71
71
|
end
|
72
|
-
|
73
72
|
end
|
74
73
|
|
75
74
|
# Tests
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: foreman_templates
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.0.
|
4
|
+
version: 5.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Greg Sutcliffe
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-05-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: diffy
|