foreman_remote_execution 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
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!