foreman_remote_execution 0.3.0 → 0.3.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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -5
  3. data/.rubocop_todo.yml +90 -92
  4. data/app/assets/javascripts/job_templates.js +10 -0
  5. data/app/assets/stylesheets/modal_window.css.scss +4 -0
  6. data/app/controllers/api/v2/job_templates_controller.rb +22 -1
  7. data/app/controllers/job_templates_controller.rb +21 -1
  8. data/app/helpers/concerns/foreman_remote_execution/job_templates_extensions.rb +3 -2
  9. data/app/helpers/remote_execution_helper.rb +5 -3
  10. data/app/lib/actions/middleware/bind_job_invocation.rb +1 -1
  11. data/app/lib/actions/remote_execution/run_host_job.rb +15 -8
  12. data/app/lib/actions/remote_execution/run_hosts_job.rb +2 -1
  13. data/app/models/concerns/foreman_remote_execution/exportable.rb +71 -0
  14. data/app/models/foreign_input_set.rb +3 -0
  15. data/app/models/job_invocation.rb +6 -4
  16. data/app/models/job_invocation_composer.rb +1 -1
  17. data/app/models/job_template.rb +22 -6
  18. data/app/models/template_input.rb +4 -0
  19. data/app/models/template_invocation.rb +1 -1
  20. data/app/services/proxy_load_balancer.rb +3 -0
  21. data/app/views/job_invocations/_preview_hosts_modal.html.erb +3 -1
  22. data/app/views/job_templates/_import_job_template_modal.html.erb +20 -0
  23. data/app/views/job_templates/index.html.erb +5 -1
  24. data/config/routes.rb +4 -0
  25. data/lib/foreman_remote_execution/engine.rb +4 -4
  26. data/lib/foreman_remote_execution/version.rb +1 -1
  27. data/locale/Makefile +18 -19
  28. data/locale/action_names.rb +2 -2
  29. data/locale/de/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  30. data/locale/de/foreman_remote_execution.po +823 -0
  31. data/locale/en/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  32. data/locale/en/foreman_remote_execution.po +332 -283
  33. data/locale/en_GB/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  34. data/locale/en_GB/foreman_remote_execution.po +824 -0
  35. data/locale/foreman_remote_execution.pot +277 -131
  36. data/locale/fr/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  37. data/locale/fr/foreman_remote_execution.po +823 -0
  38. data/test/functional/api/v2/job_templates_controller_test.rb +15 -0
  39. data/test/unit/actions/run_host_job_test.rb +50 -0
  40. data/test/unit/concerns/exportable_test.rb +88 -0
  41. data/test/unit/job_invocation_composer_test.rb +1 -1
  42. data/test/unit/job_invocation_test.rb +3 -6
  43. data/test/unit/job_template_test.rb +34 -2
  44. data/test/unit/proxy_load_balancer_test.rb +8 -6
  45. data/test/unit/template_input_test.rb +15 -0
  46. metadata +17 -2
@@ -6,7 +6,7 @@ module Actions
6
6
  def delay(*args)
7
7
  schedule_options, job_invocation = args
8
8
  if !job_invocation.task_id.nil? && job_invocation.task_id != task.id
9
- job_invocation = job_invocation.deep_clone
9
+ job_invocation = job_invocation.deep_clone!
10
10
  args = [schedule_options, job_invocation]
11
11
  end
12
12
  pass(*args).tap { bind(job_invocation) }
@@ -9,7 +9,7 @@ module Actions
9
9
  :link
10
10
  end
11
11
 
12
- def plan(job_invocation, host, template_invocation, proxy)
12
+ def plan(job_invocation, host, template_invocation, proxy, options = {})
13
13
  action_subject(host, :job_category => job_invocation.job_category, :description => job_invocation.description)
14
14
 
15
15
  template_invocation.host_id = host.id
@@ -24,11 +24,19 @@ module Actions
24
24
 
25
25
  raise _('Could not use any template used in the job invocation') if template_invocation.blank?
26
26
 
27
- settings = { :global_proxy => 'remote_execution_global_proxy',
28
- :fallback_proxy => 'remote_execution_fallback_proxy' }
27
+ if proxy.blank?
28
+ offline_proxies = options.fetch(:offline_proxies, [])
29
+ settings = { :count => offline_proxies.count, :proxy_names => offline_proxies.map(&:name).join(', ') }
30
+ raise n_('The only applicable proxy %{proxy_names} is down',
31
+ 'All %{count} applicable proxies are down. Tried %{proxy_names}',
32
+ offline_proxies.count) % settings unless offline_proxies.empty?
29
33
 
30
- raise _('Could not use any proxy. Consider configuring %{global_proxy} ' +
31
- 'or %{fallback_proxy} in settings') % settings if proxy.blank?
34
+ settings = { :global_proxy => 'remote_execution_global_proxy',
35
+ :fallback_proxy => 'remote_execution_fallback_proxy' }
36
+
37
+ raise _('Could not use any proxy. Consider configuring %{global_proxy} ' +
38
+ 'or %{fallback_proxy} in settings') % settings
39
+ end
32
40
 
33
41
  renderer = InputTemplateRenderer.new(template_invocation.template, host, template_invocation)
34
42
  script = renderer.render
@@ -70,9 +78,8 @@ module Actions
70
78
 
71
79
  def find_ip_or_hostname(host)
72
80
  %w(execution primary provision).each do |flag|
73
- if host.send("#{flag}_interface") && host.send("#{flag}_interface").ip.present?
74
- return host.execution_interface.ip
75
- end
81
+ interface = host.send(flag + '_interface')
82
+ return interface.ip if interface && interface.ip.present?
76
83
  end
77
84
 
78
85
  host.interfaces.each do |interface|
@@ -31,7 +31,8 @@ module Actions
31
31
  template_invocation = job_invocation.pattern_template_invocation_for_host(host).deep_clone
32
32
  template_invocation.host_id = host.id
33
33
  proxy = determine_proxy(template_invocation, host, load_balancer)
34
- trigger(RunHostJob, job_invocation, host, template_invocation, proxy)
34
+ trigger(RunHostJob, job_invocation, host, template_invocation, proxy,
35
+ :offline_proxies => load_balancer.offline)
35
36
  end
36
37
  end
37
38
 
@@ -0,0 +1,71 @@
1
+ # This concern makes it easy to export an ActiveRecord object with specified
2
+ # attributes and associations in a particular format. If a specified
3
+ # assocation also includes this concern, then it will likewise be exported.
4
+ #
5
+ # Custom attributes can be specified with a custom export lambda in an options
6
+ # hash.
7
+ #
8
+ # Example:
9
+ # attr_exportable :name, :address, :company => ->(user) { user.company.name }
10
+ #
11
+ module ForemanRemoteExecution
12
+ module Exportable
13
+ extend ActiveSupport::Concern
14
+
15
+ def to_export(include_blank = true)
16
+ self.class.exportable_attributes.keys.inject({}) do |hash, attribute|
17
+ value = export_attr(attribute, self.class.exportable_attributes[attribute], include_blank)
18
+
19
+ # Rails considers false blank, but if a boolean value is explicitly set false, we want to ensure we export it.
20
+ if include_blank || value.present? || value == false
21
+ hash.update(attribute => value)
22
+ else
23
+ hash
24
+ end
25
+ end.stringify_keys
26
+ end
27
+
28
+ # Export a particular attribute or association.
29
+ # - If our exportable_attributes value is callable, we call it with self as an argument
30
+ # - If our object is iterable, then we export each item
31
+ # - If the attribute or association also includes this concern, call to_export on it
32
+ def export_attr(attribute, exporter, include_blank)
33
+ value = if exporter.respond_to?(:call)
34
+ exporter.call(self)
35
+ elsif self.respond_to?(exporter)
36
+ self.send(exporter)
37
+ end
38
+
39
+ value = value.respond_to?(:map) ? export_iterable(value, include_blank) : value
40
+ value.respond_to?(:to_export) ? value.to_export(include_blank) : value
41
+ end
42
+
43
+ # Exports each item in an iterable. If it's a hash, then export each value.
44
+ def export_iterable(items, include_blank)
45
+ if items.is_a?(Hash)
46
+ items.each { |key, value| items[key] = value.respond_to?(:to_export) ? value.to_export(include_blank) : value }
47
+ items.to_hash.stringify_keys
48
+ else
49
+ items.map { |item| item.respond_to?(:to_export) ? item.to_export(include_blank) : item }
50
+ end
51
+ end
52
+
53
+ module ClassMethods
54
+ attr_reader :exportable_attributes
55
+
56
+ # Takes an array of exportable attributes, and a custom exports hash. The
57
+ # custom exports hash should be a key/lambda pair used to export the
58
+ # particular attribute.
59
+ def attr_exportable(*args)
60
+ @exportable_attributes ||= {}
61
+ args.each do |arg|
62
+ if arg.is_a?(Hash)
63
+ @exportable_attributes.merge!(arg)
64
+ else
65
+ @exportable_attributes.merge!(arg => arg)
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -1,8 +1,11 @@
1
1
  class ForeignInputSet < ActiveRecord::Base
2
+ include ForemanRemoteExecution::Exportable
3
+
2
4
  class CircularDependencyError < Foreman::Exception
3
5
  end
4
6
 
5
7
  attr_accessible :template_id, :target_template_id, :include_all, :include, :exclude
8
+ attr_exportable :exclude, :include, :include_all, :template => ->(input_set) { input_set.template.name }
6
9
 
7
10
  belongs_to :template
8
11
  belongs_to :target_template, :class_name => 'Template'
@@ -93,15 +93,18 @@ class JobInvocation < ActiveRecord::Base
93
93
 
94
94
  def deep_clone
95
95
  JobInvocationComposer.from_job_invocation(self).job_invocation.tap do |invocation|
96
- invocation.task_group = JobInvocationTaskGroup.new.tap(&:save!)
96
+ invocation.task_group = JobInvocationTaskGroup.new
97
97
  invocation.triggering = self.triggering
98
98
  invocation.description_format = self.description_format
99
99
  invocation.description = self.description
100
100
  invocation.pattern_template_invocations = self.pattern_template_invocations.map(&:deep_clone)
101
- invocation.save!
102
101
  end
103
102
  end
104
103
 
104
+ def deep_clone!
105
+ deep_clone.tap(&:save!)
106
+ end
107
+
105
108
  def to_action_input
106
109
  { :id => id, :name => job_category, :description => description }
107
110
  end
@@ -151,7 +154,7 @@ class JobInvocation < ActiveRecord::Base
151
154
  task.main_action.live_output.first['output']
152
155
  end
153
156
 
154
- def generate_description!
157
+ def generate_description
155
158
  key_re = /%\{([^\}]+)\}/
156
159
  template_invocation = pattern_template_invocations.first
157
160
  input_names = template_invocation.template.template_inputs_with_foreign(&:name)
@@ -165,7 +168,6 @@ class JobInvocation < ActiveRecord::Base
165
168
  self.description.gsub!(Regexp.new("%\{#{k}\}"), v || '')
166
169
  end
167
170
  self.description = self.description[0..(JobInvocation.columns_hash['description'].limit - 1)]
168
- save!
169
171
  end
170
172
 
171
173
  private
@@ -468,7 +468,7 @@ class JobInvocationComposer
468
468
  template = job_invocation.pattern_template_invocations.first.try(:template)
469
469
  job_invocation.description_format = template.generate_description_format if template
470
470
  end
471
- job_invocation.generate_description! if job_invocation.description.blank?
471
+ job_invocation.generate_description if job_invocation.description.blank?
472
472
  end
473
473
 
474
474
  def build_effective_user(template_invocation_params)
@@ -1,9 +1,12 @@
1
1
  class JobTemplate < ::Template
2
+ include ForemanRemoteExecution::Exportable
2
3
 
3
4
  class NonUniqueInputsError < Foreman::Exception
4
5
  end
5
6
 
6
7
  attr_accessible :job_category, :provider_type, :description_format, :effective_user_attributes
8
+ attr_exportable :name, :job_category, :description_format, :snippet, :template_inputs,
9
+ :foreign_input_sets, :provider_type, :kind => ->(template) { template.class.name.underscore }
7
10
 
8
11
  include Authorizable
9
12
  extend FriendlyId
@@ -67,16 +70,17 @@ class JobTemplate < ::Template
67
70
  metadata = parse_metadata(contents)
68
71
  return if metadata.blank? || metadata.delete('kind') != 'job_template'
69
72
 
73
+ # Don't look for existing if we should always create a new template
74
+ existing = self.find_by_name(metadata['name']) unless options.delete(:build_new)
70
75
  # Don't update if the template already exists, unless we're told to
71
- existing = self.find_by_name(metadata['name'])
72
76
  return if !options.delete(:update) && existing
73
77
 
74
78
  template = existing || self.new
75
79
  template.sync_inputs(metadata.delete('template_inputs'))
76
80
  template.sync_foreign_input_sets(metadata.delete('foreign_input_sets'))
77
- template.assign_attributes(metadata.merge(:template => contents.gsub(/<%\#.+?.-?%>\n?/m, '')).merge(options).except('feature'))
78
- template.assign_taxonomies if template.new_record?
79
81
  template.sync_feature(metadata.delete('feature'))
82
+ template.assign_attributes(metadata.merge(:template => contents.gsub(/<%\#.+?.-?%>\n?/m, '').strip).merge(options))
83
+ template.assign_taxonomies if template.new_record?
80
84
 
81
85
  template
82
86
  end
@@ -88,6 +92,19 @@ class JobTemplate < ::Template
88
92
  template
89
93
  end
90
94
 
95
+ def metadata
96
+ "<%#\n#{to_export(false).to_yaml.sub(/\A---$/, '').strip}\n%>\n\n"
97
+ end
98
+
99
+ # 'Package Action - SSH Default' => 'package_action_ssh_default.erb'
100
+ def filename
101
+ name.downcase.delete('-').gsub(/\s+/, '_') + '.erb'
102
+ end
103
+
104
+ def to_erb
105
+ metadata + template
106
+ end
107
+
91
108
  # Override method in Taxonomix as Template is not used attached to a Host,
92
109
  # and matching a Host does not prevent removing a template from its taxonomy.
93
110
  def used_taxonomy_ids(type)
@@ -140,7 +157,7 @@ class JobTemplate < ::Template
140
157
  def sync_inputs(inputs)
141
158
  inputs ||= []
142
159
  # Build a hash where keys are input names
143
- inputs = inputs.inject({}) { |h, input| h.update(input['name'] => input ) }
160
+ inputs = inputs.inject({}) { |h, input| h.update(input['name'] => input) }
144
161
 
145
162
  # Sync existing inputs
146
163
  template_inputs.each do |existing_input|
@@ -180,7 +197,6 @@ class JobTemplate < ::Template
180
197
  def sync_feature(feature_name)
181
198
  if feature_name && (feature = RemoteExecutionFeature.feature(feature_name))
182
199
  feature.job_template ||= self
183
- feature.save!
184
200
  end
185
201
  end
186
202
 
@@ -203,4 +219,4 @@ class JobTemplate < ::Template
203
219
  errors.add(:base, _('This template is locked. Please clone it to a new template to customize.'))
204
220
  end
205
221
  end
206
- end
222
+ end
@@ -1,4 +1,6 @@
1
1
  class TemplateInput < ActiveRecord::Base
2
+ include ForemanRemoteExecution::Exportable
3
+
2
4
  class ValueNotReady < ::Foreman::Exception
3
5
  end
4
6
  class UnsatisfiedRequiredInput < ::Foreman::Exception
@@ -11,6 +13,8 @@ class TemplateInput < ActiveRecord::Base
11
13
  :puppet_class_name, :puppet_parameter_name, :description, :template_id,
12
14
  :options, :advanced
13
15
 
16
+ attr_exportable(*self.accessible_attributes - %w(template_id))
17
+
14
18
  belongs_to :template
15
19
  has_many :template_invocation_input_values, :dependent => :destroy
16
20
 
@@ -36,7 +36,7 @@ class TemplateInvocation < ActiveRecord::Base
36
36
  end
37
37
 
38
38
  def deep_clone!
39
- deep_clone.tap { |clone| clone.input_values.each(&:save!) }
39
+ deep_clone.tap(&:save!)
40
40
  end
41
41
 
42
42
  private
@@ -1,4 +1,7 @@
1
1
  class ProxyLoadBalancer
2
+
3
+ attr_reader :offline
4
+
2
5
  def initialize
3
6
  @tasks = {}
4
7
  @offline = []
@@ -1,3 +1,5 @@
1
+ <% stylesheet 'modal_window' %>
2
+
1
3
  <!-- modal window -->
2
4
  <div class="modal fade" id="previewHostsModal" role="dialog" aria-hidden="true" data-url="<%= preview_hosts_job_invocations_path %>">
3
5
  <div class="modal-dialog">
@@ -6,7 +8,7 @@
6
8
  <button type="button" class="close" onclick="close_preview_hosts_modal(); return false;"><span aria-hidden="true">&times;</span><span class="sr-only"><%= _('Close') %></span></button>
7
9
  <h4 class="modal-title">Preview Hosts</h4>
8
10
  </div>
9
- <div class="modal-body" style="overflow-y: auto; max-height: 500px;">
11
+ <div class="modal-body">
10
12
  </div>
11
13
  </div>
12
14
  </div>
@@ -0,0 +1,20 @@
1
+ <% stylesheet 'modal_window' %>
2
+
3
+ <!-- modal window -->
4
+ <div class="modal fade" id="importJobTemplateModal" role="dialog" aria-hidden="true">
5
+ <div class="modal-dialog">
6
+ <div class="modal-content">
7
+ <div class="modal-header">
8
+ <button type="button" class="close" onclick="close_import_job_template_modal(); return false;"><span aria-hidden="true">&times;</span><span class="sr-only"><%= _('Close') %></span></button>
9
+ <h4 class="modal-title">Import Job Template</h4>
10
+ </div>
11
+ <div class="modal-body">
12
+ <%= form_for :imported_template, :url => import_job_templates_path do |f| %>
13
+ <%= file_field_f f, :template, :size => "col-md-10", :help_block => _("Select an ERB file to upload in order to import a job template. The template must contain metadata in the first ERB comment.") %>
14
+ <%= checkbox_f(f, :overwrite, :label => _('Overwrite'), :help_block => _("Whether to overwrite the template if it already exists")) %>
15
+ <%= submit_or_cancel f, false, :cancel_path => 'javascript:close_import_job_template_modal();' %>
16
+ <% end %>
17
+ </div>
18
+ </div>
19
+ </div>
20
+ </div>
@@ -1,9 +1,11 @@
1
1
  <%= include_javascript if SETTINGS[:version].short == '1.9' %>
2
+ <%= javascript 'job_templates' %>
2
3
  <%= javascript 'lookup_keys' %>
3
4
  <%= javascript 'template_input' %>
4
5
 
5
6
  <% title _("Job Templates") %>
6
7
  <% title_actions(documentation_button_rex('3.1JobTemplates'),
8
+ link_to_function(_('Import'), 'show_import_job_template_modal();', :class => 'btn btn-default'),
7
9
  display_link_if_authorized(_("New Job Template"),
8
10
  hash_for_new_job_template_path)) %>
9
11
 
@@ -19,7 +21,7 @@
19
21
  <tbody>
20
22
  <% for job_template in @templates %>
21
23
  <tr>
22
- <td class="display-two-pane"><%= link_to_if_authorized trunc_with_tooltip(job_template),
24
+ <td class="display-two-pane ellipsis"><%= link_to_if_authorized job_template,
23
25
  hash_for_edit_job_template_path(:id => job_template).merge(:auth_object => job_template, :authorizer => authorizer) %></td>
24
26
  <td align='center'><%= checked_icon job_template.snippet %></td>
25
27
  <td align='center'>
@@ -33,3 +35,5 @@
33
35
  </tbody>
34
36
  </table>
35
37
  <%= will_paginate_with_info @templates %>
38
+
39
+ <%= render :partial => 'import_job_template_modal' %>
data/config/routes.rb CHANGED
@@ -3,11 +3,13 @@ Rails.application.routes.draw do
3
3
  member do
4
4
  get 'clone_template'
5
5
  get 'lock'
6
+ get 'export'
6
7
  get 'unlock'
7
8
  post 'preview'
8
9
  end
9
10
  collection do
10
11
  post 'preview'
12
+ post 'import'
11
13
  get 'revision'
12
14
  get 'auto_complete_search'
13
15
  get 'auto_complete_job_category'
@@ -45,9 +47,11 @@ Rails.application.routes.draw do
45
47
  resources :job_templates, :except => [:new, :edit] do
46
48
  (resources :locations, :only => [:index, :show]) if SETTINGS[:locations_enabled]
47
49
  (resources :organizations, :only => [:index, :show]) if SETTINGS[:organizations_enabled]
50
+ get :export, :on => :member
48
51
  post :clone, :on => :member
49
52
  collection do
50
53
  get 'revision'
54
+ post 'import'
51
55
  end
52
56
  end
53
57
 
@@ -35,12 +35,12 @@ module ForemanRemoteExecution
35
35
 
36
36
  # Add permissions
37
37
  security_block :foreman_remote_execution do
38
- permission :view_job_templates, { :job_templates => [:index, :show, :revision, :auto_complete_search, :auto_complete_job_category, :preview],
39
- :'api/v2/job_templates' => [:index, :show, :revision],
38
+ permission :view_job_templates, { :job_templates => [:index, :show, :revision, :auto_complete_search, :auto_complete_job_category, :preview, :export],
39
+ :'api/v2/job_templates' => [:index, :show, :revision, :export],
40
40
  :'api/v2/template_inputs' => [:index, :show],
41
41
  :'api/v2/foreign_input_sets' => [:index, :show]}, :resource_type => 'JobTemplate'
42
- permission :create_job_templates, { :job_templates => [:new, :create, :clone_template],
43
- :'api/v2/job_templates' => [:create, :clone] }, :resource_type => 'JobTemplate'
42
+ permission :create_job_templates, { :job_templates => [:new, :create, :clone_template, :import],
43
+ :'api/v2/job_templates' => [:create, :clone, :import] }, :resource_type => 'JobTemplate'
44
44
  permission :edit_job_templates, { :job_templates => [:edit, :update],
45
45
  :'api/v2/job_templates' => [:update],
46
46
  :'api/v2/template_inputs' => [:create, :update, :destroy],
@@ -1,3 +1,3 @@
1
1
  module ForemanRemoteExecution
2
- VERSION = '0.3.0'
2
+ VERSION = '0.3.1'
3
3
  end
data/locale/Makefile CHANGED
@@ -10,9 +10,10 @@ DOMAIN = foreman_remote_execution
10
10
  VERSION = $(shell ruby -e 'require "rubygems";spec = Gem::Specification::load(Dir.glob("../*.gemspec")[0]);puts spec.version')
11
11
  POTFILE = $(DOMAIN).pot
12
12
  MOFILE = $(DOMAIN).mo
13
- POFILES = $(shell find . -name '*.po')
13
+ POFILES = $(shell find . -name '$(DOMAIN).po')
14
14
  MOFILES = $(patsubst %.po,%.mo,$(POFILES))
15
15
  POXFILES = $(patsubst %.po,%.pox,$(POFILES))
16
+ EDITFILES = $(patsubst %.po,%.edit.po,$(POFILES))
16
17
 
17
18
  %.mo: %.po
18
19
  mkdir -p $(shell dirname $@)/LC_MESSAGES
@@ -29,14 +30,10 @@ all-mo: $(MOFILES)
29
30
  cat $@
30
31
  ! grep -q msgid $@
31
32
 
32
- check: $(POXFILES)
33
- msgfmt -c ${POTFILE}
33
+ %.edit.po:
34
+ touch $@
34
35
 
35
- # Merge PO files
36
- update-po:
37
- for f in $(shell find ./ -name "*.po") ; do \
38
- msgmerge -N --backup=none -U $$f ${POTFILE} ; \
39
- done
36
+ check: $(POXFILES)
40
37
 
41
38
  # Unify duplicate translations
42
39
  uniq-po:
@@ -44,19 +41,21 @@ uniq-po:
44
41
  msguniq $$f -o $$f ; \
45
42
  done
46
43
 
47
- tx-pull:
44
+ tx-pull: $(EDITFILES)
48
45
  tx pull -f
49
- for f in $(POFILES) ; do \
46
+ for f in $(EDITFILES) ; do \
50
47
  sed -i 's/^\("Project-Id-Version: \).*$$/\1$(DOMAIN) $(VERSION)\\n"/' $$f; \
51
48
  done
52
- -git commit -a -m "i18n - pulling from tx"
53
49
 
54
- reset-po:
55
- # merging po files is unnecessary when using transifex.com
56
- git checkout -- ../locale/*/*po
50
+ tx-update: tx-pull
51
+ @echo
52
+ @echo Run rake plugin:gettext[$(DOMAIN)] from the Foreman installation, then make -C locale mo-files to finish
53
+ @echo
54
+
55
+ mo-files: $(MOFILES)
56
+ git add $(POFILES) $(POTFILE) ../locale/*/LC_MESSAGES
57
+ git commit -m "i18n - pulling from tx"
58
+ @echo
59
+ @echo Changes commited!
60
+ @echo
57
61
 
58
- tx-update: tx-pull reset-po $(MOFILES)
59
- # amend mo files
60
- git add ../locale/*/LC_MESSAGES
61
- git commit -a --amend -m "i18n - pulling from tx"
62
- -echo Changes commited!