rubber 2.12.2 → 2.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +27 -0
  3. data/lib/rubber/cli.rb +0 -1
  4. data/lib/rubber/cloud/aws_table_store.rb +1 -1
  5. data/lib/rubber/cloud/fog_storage.rb +1 -1
  6. data/lib/rubber/cloud/vsphere.rb +23 -1
  7. data/lib/rubber/commands/vulcanize.rb +1 -1
  8. data/lib/rubber/dns/aws.rb +4 -3
  9. data/lib/rubber/generator.rb +6 -2
  10. data/lib/rubber/instance.rb +48 -6
  11. data/lib/rubber/recipes/rubber.rb +8 -0
  12. data/lib/rubber/recipes/rubber/deploy.rb +9 -3
  13. data/lib/rubber/recipes/rubber/instances.rb +1 -0
  14. data/lib/rubber/recipes/rubber/setup.rb +10 -2
  15. data/lib/rubber/recipes/rubber/static_ips.rb +1 -1
  16. data/lib/rubber/recipes/rubber/utils.rb +8 -2
  17. data/lib/rubber/version.rb +1 -1
  18. data/templates/apache/config/rubber/deploy-apache.rb +1 -1
  19. data/templates/apache/config/rubber/role/web_tools/tools-apache-vhost.conf +8 -0
  20. data/templates/apache/config/rubber/rubber-apache.yml +2 -2
  21. data/templates/base/config/rubber/deploy-util.rb +3 -3
  22. data/templates/base/config/rubber/rubber.yml +8 -3
  23. data/templates/cassandra/config/rubber/deploy-cassandra.rb +2 -2
  24. data/templates/collectd/config/rubber/role/collectd/collectd-ping.conf +11 -2
  25. data/templates/collectd/config/rubber/role/collectd/thresholds.conf +1 -1
  26. data/templates/collectd/script/collectd/role/redis/redis_info.rb +0 -1
  27. data/templates/cruise/config/rubber/role/cruise/my.cnf +1 -1
  28. data/templates/graphite/config/rubber/deploy-graphite.rb +125 -69
  29. data/templates/graphite/config/rubber/role/graphite_server/carbon.conf +150 -26
  30. data/templates/graphite/config/rubber/role/graphite_server/graphite-carbon-default.conf +8 -0
  31. data/templates/graphite/config/rubber/role/graphite_server/graphite_server-upstart.conf +3 -0
  32. data/templates/graphite/config/rubber/role/graphite_server/storage-schemas.conf +2 -1
  33. data/templates/graphite/config/rubber/role/graphite_web/crontab +1 -1
  34. data/templates/graphite/config/rubber/role/graphite_web/dashboard.conf +1 -1
  35. data/templates/graphite/config/rubber/role/graphite_web/dashboard.html +1 -1
  36. data/templates/graphite/config/rubber/role/graphite_web/graphite.wsgi +4 -1
  37. data/templates/graphite/config/rubber/role/graphite_web/local_settings.py +171 -41
  38. data/templates/graphite/config/rubber/role/graphite_web/uwsgi.ini +6 -3
  39. data/templates/graphite/config/rubber/rubber-graphite.yml +7 -1
  40. data/templates/graphite/templates.rb +9 -0
  41. data/templates/graylog/config/rubber/common/graylog-rsyslog.conf +1 -1
  42. data/templates/graylog/config/rubber/role/graylog_server/graylog2.conf +2 -2
  43. data/templates/jenkins/config/rubber/role/jenkins/jenkins-apache-vhost.conf +5 -0
  44. data/templates/memcached/config/rubber/role/memcached/memcached.conf +1 -1
  45. data/templates/mysql/config/rubber/deploy-mysql.rb +2 -2
  46. data/templates/mysql/config/rubber/role/db/my.cnf +1 -1
  47. data/templates/mysql_cluster/config/rubber/deploy-mysql_cluster.rb +3 -3
  48. data/templates/mysql_cluster/config/rubber/role/mysql_sql/my.cnf +1 -1
  49. data/templates/passenger/config/rubber/role/passenger/passenger-apache-vhost.conf +12 -0
  50. data/templates/passenger/config/rubber/rubber-passenger.yml +1 -1
  51. data/templates/passenger_nginx/config/rubber/role/passenger_nginx/nginx.conf +1 -1
  52. data/templates/percona/config/rubber/deploy-percona.rb +2 -2
  53. data/templates/percona/config/rubber/role/db/my.cnf +1 -1
  54. data/templates/postgresql/config/rubber/deploy-postgresql.rb +4 -8
  55. data/templates/postgresql/config/rubber/role/postgresql/pg_hba.conf +1 -1
  56. data/templates/postgresql/config/rubber/role/postgresql/postgresql.conf +1 -1
  57. data/templates/redis/config/rubber/role/redis/redis.conf +1 -1
  58. data/templates/torquebox/config/rubber/role/app/torquebox-apache-vhost.conf +12 -0
  59. metadata +4 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 331df14a974871cbe3939df12e1ea37fe2d1b625
4
- data.tar.gz: fe492705ec10139570c3d151720cc2ced3821730
3
+ metadata.gz: ab0ab6d9b549a41b15aff33366f303d4245bce3d
4
+ data.tar.gz: 2bb33a094d043c0da799b9cfa9a7049ebde4dfe8
5
5
  SHA512:
6
- metadata.gz: a1dbd4058e402bbecd5046810b8a464c54c15010ba6f543a8ccdfc1c285b9847254463be136db7aa8a1b1c9dc10d627a27618578fb1a0aa2ac32abcc50da6daa
7
- data.tar.gz: 937f8764da1bd2a9681a954c06bb5cdc81506c896eedc577ff1a247e212fa257451b21472ffd2826992f401b6919a420641ca839258c5ba29a73b1298539d58a
6
+ metadata.gz: e328f32286a8d3bad4ecef5d0f248ace669da4eb8e384b169b29c999735d6240eae420560e194ecab82b27a078b16e5d3ab0a11f162675d8fdc4227c324cb0e6
7
+ data.tar.gz: 2aaad28bef809b031f579533debf52ea27cd39de9e2a88ffa6d20d061af12bd0f9df3ba0642e3fb4799b62373c95d7df671536b78ad58acf756a684b9db93293
data/CHANGELOG CHANGED
@@ -1,3 +1,30 @@
1
+ 2.13.0 (10/01/2014)
2
+
3
+ New Features:
4
+ ============
5
+
6
+ [base] Ubuntu 14.04 support.
7
+ [core] Added a way of specifying packages to be installed by OS version. <a682955>
8
+ [core] Added a way to retrieve the OS version for an instance. <1954d06, 59a5929, 491cd7e>
9
+ [core] Added a 'rubber_instance' helper method for use in templates and Capistrano tasks to reference the instance being deployed to. <4281344, 52ff83d, f94db86>
10
+
11
+ Improvements:
12
+ ============
13
+
14
+ [apache] Updated config to work with both Apache 2.2 and 2.4. <8afa6df>
15
+ [graphite] Updated the graphite template to work in both Ubuntu 12.04 and 14.04. <97a86ca, 7fef018, ce328d6>
16
+ [passenger] Updated config to work with both Apache 2.2 and 2.4. <8afa6df>
17
+ [postgresql] Always use the pgdg apt repository since it supports all Ubuntu LTS releases on all supported versions of PostgreSQL. <b1aeb00>
18
+
19
+ Bug Fixes:
20
+ =========
21
+
22
+ [core] Fixed a regression whereby we might try to generate config before all the files have been pushed. <a0d6c32>
23
+ [core] Fixed a regression whereby we would try to deploy to non-Linux servers, even though they're not supported. <513e0e1>
24
+ [core] Worked around an issue with Ubuntu 14.04 and vSphere whereby VMware Tools won't correctly populate the DNS list. <15216b7, d1fceac>
25
+ [core] Make sure VMs can still be refreshed in vSphere even if the VMware Tools installation is out-of-date. <546ec93>
26
+
27
+
1
28
  2.12.2 (08/26/2014)
2
29
 
3
30
  Improvements:
@@ -11,7 +11,6 @@ module Rubber
11
11
  class CLI < Clamp::Command
12
12
 
13
13
  # setup clamp subcommands for each rubber command
14
- command_classes = []
15
14
  Rubber::Commands.constants.each do |c|
16
15
  clazz = Rubber::Commands.const_get(c)
17
16
  if clazz.class == Class && clazz.ancestors.include?(Clamp::Command) &&
@@ -23,7 +23,7 @@ module Rubber
23
23
  end
24
24
 
25
25
  # create the table if needed
26
- def ensure_table_key()
26
+ def ensure_table_key
27
27
  Rubber::Util.retry_on_failure(*RETRYABLE_EXCEPTIONS) do
28
28
  begin
29
29
  @metadata = @table_provider.domain_metadata(@table_key)
@@ -18,7 +18,7 @@ module Rubber
18
18
  end
19
19
 
20
20
  # create the bucket if needed
21
- def ensure_bucket()
21
+ def ensure_bucket
22
22
  Rubber::Util.retry_on_failure(*RETRYABLE_EXCEPTIONS) do
23
23
  @directory = @storage_provider.directories.create(:key => @bucket) unless @directory
24
24
  end
@@ -102,6 +102,24 @@ module Rubber
102
102
  compute_provider.servers.get(instance_id).destroy(:force => true)
103
103
  end
104
104
 
105
+ def after_refresh_instance(instance)
106
+ rubber_cfg = Rubber::Configuration.get_configuration(Rubber.env)
107
+ host_env = rubber_cfg.environment.bind(nil, instance.name)
108
+
109
+ dns_servers = [host_env.public_nic, env.public_nic, host_env.private_nic, env.private_nic].collect { |nic| nic.dns_servers if nic }.compact.first
110
+
111
+ # VMware Tools currently (as of Aug. 25, 2014) has a bug with Ubuntu 14.04 whereby it fails to properly configure
112
+ # DNS when static IP configurations are used in a customization spec. This works around the problem by setting
113
+ # up the resolvconf configuration directly.
114
+ if dns_servers && ! dns_servers.empty?
115
+ contents = dns_servers.map { |server| "nameserver #{server}" }.join("\n")
116
+ contents << "\n"
117
+
118
+ capistrano.put(contents, '/etc/resolvconf/resolv.conf.d/base', :mode => '0644', :hosts => instance.external_ip)
119
+ capistrano.run('resolvconf -u', :hosts => instance.external_ip)
120
+ end
121
+ end
122
+
105
123
  def describe_instances(instance_id=nil)
106
124
  instances = []
107
125
  opts = {}
@@ -118,7 +136,11 @@ module Rubber
118
136
 
119
137
  instance = {}
120
138
  instance[:id] = item.id
121
- instance[:state] = item.tools_state
139
+
140
+ # If the VM is up, but the VMware Tools installation is out-of-date, just treat it as if the tools installation
141
+ # is okay. Otherwise, the rubber:refresh process won't detect that the VM has booted properly, even though
142
+ # it's running just fine. In all other cases, use the state reported by the vSphere API call.
143
+ instance[:state] = item.tools_state == 'toolsOld' ? active_state : item.tools_state
122
144
 
123
145
  # We can't trust the describe operation when the instance is newly created because the VM customization
124
146
  # step likely hasn't completed yet. This means we'll get back the IP address for the VM template, rather
@@ -77,7 +77,7 @@ module Rubber
77
77
  File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'templates'))
78
78
  end
79
79
 
80
- def self.valid_templates()
80
+ def self.valid_templates
81
81
  Dir.entries(self.source_root).delete_if {|e| e =~ /(^\.)|svn|CVS/ }.sort
82
82
  end
83
83
 
@@ -127,7 +127,7 @@ module Rubber
127
127
 
128
128
  def find_hosts(opts = {})
129
129
  opts = setup_opts(opts, [:host, :domain])
130
- result = []
130
+
131
131
  zone = find_or_create_zone(opts[:domain])
132
132
  host = opts_to_host(opts)
133
133
 
@@ -142,13 +142,14 @@ module Rubber
142
142
  hosts = hosts.select {|h| h.name == host[:name] } if opts.has_key?(:host) && opts[:host] != '*'
143
143
  hosts = hosts.select {|h| h.type == host[:type] } if opts.has_key?(:type) && opts[:type] != '*'
144
144
 
145
- return hosts
145
+ hosts
146
146
  end
147
147
 
148
148
  def find_host_records(opts = {})
149
149
  hosts = find_hosts(opts)
150
150
  result = hosts.collect {|h| host_to_opts(h).merge(:domain => opts[:domain]) }
151
- return result
151
+
152
+ result
152
153
  end
153
154
 
154
155
  def create_host_record(opts = {})
@@ -237,14 +237,18 @@ module Rubber
237
237
  binding
238
238
  end
239
239
 
240
- def rubber_env()
240
+ def rubber_env
241
241
  Rubber::Configuration.rubber_env
242
242
  end
243
243
 
244
- def rubber_instances()
244
+ def rubber_instances
245
245
  Rubber.instances
246
246
  end
247
247
 
248
+ def rubber_instance
249
+ rubber_instances[rubber_env.host]
250
+ end
251
+
248
252
  end
249
253
 
250
254
  end
@@ -140,7 +140,7 @@ module Rubber
140
140
  @items.values.find_all {|ic| ic.roles.any? {|r| r.name == role_name && (! options || r.options == options)}}
141
141
  end
142
142
 
143
- def filtered()
143
+ def filtered
144
144
  filtered_results = []
145
145
 
146
146
  validate_filters()
@@ -160,7 +160,7 @@ module Rubber
160
160
  return filtered_results
161
161
  end
162
162
 
163
- def validate_filters()
163
+ def validate_filters
164
164
  aliases = @items.values.collect{|ic| ic.name}
165
165
  [@filters, @filters_negated].flatten.each do |f|
166
166
  raise "Filter doesn't match any hosts: #{f}" if ! aliases.include?(f)
@@ -172,7 +172,7 @@ module Rubber
172
172
  end
173
173
  end
174
174
 
175
- def all_roles()
175
+ def all_roles
176
176
  @items.collect {|n, i| i.role_names}.flatten.uniq
177
177
  end
178
178
 
@@ -195,6 +195,9 @@ module Rubber
195
195
 
196
196
  # The configuration for a single instance
197
197
  class InstanceItem
198
+ UBUNTU_OS_VERSION_CMD = 'lsb_release -sr'.freeze
199
+ VARIABLES_TO_OMIT_IN_SERIALIZATION = ['@capistrano', '@os_version']
200
+
198
201
  attr_reader :name, :domain, :instance_id, :image_type, :image_id, :security_groups
199
202
  attr_accessor :roles, :zone
200
203
  attr_accessor :external_host, :external_ip
@@ -202,6 +205,7 @@ module Rubber
202
205
  attr_accessor :static_ip, :volumes, :partitions, :root_device_type
203
206
  attr_accessor :spot_instance_request_id
204
207
  attr_accessor :provider, :platform
208
+ attr_accessor :capistrano
205
209
 
206
210
  def initialize(name, domain, roles, instance_id, image_type, image_id, security_group_list=[])
207
211
  @name = name
@@ -211,6 +215,7 @@ module Rubber
211
215
  @image_type = image_type
212
216
  @image_id = image_id
213
217
  @security_groups = security_group_list
218
+ @os_version = nil
214
219
  end
215
220
 
216
221
  def self.from_hash(hash)
@@ -226,6 +231,8 @@ module Rubber
226
231
  def to_hash
227
232
  hash = {}
228
233
  instance_variables.each do |iv|
234
+ next if VARIABLES_TO_OMIT_IN_SERIALIZATION.include?(iv.to_s)
235
+
229
236
  name = iv.to_s.gsub(/^@/, '')
230
237
  value = instance_variable_get(iv)
231
238
  value = value.collect {|r| r.to_s } if name == 'roles'
@@ -239,10 +246,10 @@ module Rubber
239
246
  end
240
247
 
241
248
  def full_name
242
- "#@name.#@domain"
249
+ "#{@name}.#{@domain}"
243
250
  end
244
251
 
245
- def role_names()
252
+ def role_names
246
253
  roles.collect {|r| r.name}
247
254
  end
248
255
 
@@ -267,6 +274,41 @@ module Rubber
267
274
  def windows?
268
275
  platform == Rubber::Platforms::WINDOWS
269
276
  end
277
+
278
+ def os_version
279
+ @os_version ||= begin
280
+ os_version_cmd = Rubber.config.os_version_cmd || UBUNTU_OS_VERSION_CMD
281
+
282
+ if capistrano
283
+ @os_version = capistrano.capture(os_version_cmd, :host => self.full_name).chomp
284
+ else
285
+ # If we can't SSH to the machine, we may be able to execute the command locally if this
286
+ # instance item happens to refer to the same machine we're executing on.
287
+ if Socket::gethostname == self.full_name
288
+ @os_version = `#{os_version_cmd}`.chomp
289
+ else
290
+ raise "Unable to get os_version for #{self.full_name}"
291
+ end
292
+ end
293
+ end
294
+ end
295
+
296
+ if RUBY_VERSION < '1.9'
297
+ def to_yaml_properties
298
+ vars = instance_variables.map { |x| x.to_s }
299
+ vars - VARIABLES_TO_OMIT_IN_SERIALIZATION
300
+ end
301
+
302
+ else
303
+ def encode_with(coder)
304
+ vars = instance_variables.map { |x| x.to_s }
305
+ vars = vars - VARIABLES_TO_OMIT_IN_SERIALIZATION
306
+
307
+ vars.each do |var|
308
+ coder[var.gsub('@', '')] = eval(var)
309
+ end
310
+ end
311
+ end
270
312
  end
271
313
 
272
314
  # The configuration for a single role contained in the list
@@ -328,7 +370,7 @@ module Rubber
328
370
  end
329
371
  alias == eql?
330
372
 
331
- def hash()
373
+ def hash
332
374
  @name.hash
333
375
  end
334
376
 
@@ -65,6 +65,8 @@ namespace :rubber do
65
65
  set :rubber_env, rubber_cfg.environment.bind()
66
66
  set :rubber_instances, rubber_cfg.instance
67
67
 
68
+ rubber_cfg.instance.each { |instance| instance.capistrano = self }
69
+
68
70
  # Disable connecting to any Windows instance.
69
71
  # pass -l to bash in :shell to that run also gets full env
70
72
  # use a pty so we don't get "stdin: is not a tty" error output
@@ -147,6 +149,12 @@ namespace :rubber do
147
149
 
148
150
  end
149
151
 
152
+ def top.rubber_instance
153
+ hostname = capture('hostname').chomp
154
+
155
+ rubber_instances[hostname]
156
+ end
157
+
150
158
  Dir[File.join(File.dirname(__FILE__), 'rubber/*.rb')].each do |rubber_part|
151
159
  load(rubber_part)
152
160
  end
@@ -30,6 +30,8 @@ namespace :rubber do
30
30
 
31
31
  namespace :config do
32
32
 
33
+ allow_optional_tasks(self)
34
+
33
35
  desc <<-DESC
34
36
  Pushes and runs rubber configuration on the deployed rails application
35
37
  DESC
@@ -73,7 +75,9 @@ namespace :rubber do
73
75
  rsudo "chown -R #{rubber_env.app_user}:#{rubber_env.app_user} #{current_path}/tmp"
74
76
  end
75
77
 
76
- def push_config
78
+ def push_config(options = {})
79
+ path = options.delete(:deploy_path) || config_path
80
+
77
81
  unless fetch(:rubber_config_files_pushed, false)
78
82
  # Need to do this so we can work with staging instances without having to
79
83
  # checkin instance file between create and bootstrap, as well as during a deploy
@@ -93,7 +97,7 @@ namespace :rubber do
93
97
 
94
98
  push_files.each do |file|
95
99
  dest_file = file.sub(/^#{Rubber.root}\/?/, '')
96
- put(File.read(file), File.join(config_path, dest_file), :mode => "+r")
100
+ put(File.read(file), File.join(path, dest_file), :mode => "+r")
97
101
  end
98
102
  end
99
103
 
@@ -101,7 +105,7 @@ namespace :rubber do
101
105
  secret = rubber_cfg.environment.config_secret
102
106
  if secret && File.exist?(secret)
103
107
  base = rubber_cfg.environment.config_root.sub(/^#{Rubber.root}\/?/, '')
104
- put(File.read(secret), File.join(config_path, base, File.basename(secret)), :mode => "+r")
108
+ put(File.read(secret), File.join(path, base, File.basename(secret)), :mode => "+r")
105
109
  end
106
110
 
107
111
  set :rubber_config_files_pushed, true
@@ -114,6 +118,8 @@ namespace :rubber do
114
118
  force = options[:force] || ENV['FORCE']
115
119
  file = options[:file] || ENV['FILE']
116
120
 
121
+ push_config(:deploy_path => path)
122
+
117
123
  opts = ""
118
124
  opts += " --no_post" if no_post
119
125
  opts += " --force" if force
@@ -315,6 +315,7 @@ namespace :rubber do
315
315
 
316
316
  instance_item = Rubber::Configuration::InstanceItem.new(instance_alias, env.domain, instance_roles, instance_id, ami_type, ami, security_groups)
317
317
  instance_item.spot_instance_request_id = request_id if create_spot_instance
318
+ instance_item.capistrano = self
318
319
  rubber_instances.add(instance_item)
319
320
  rubber_instances.save()
320
321
 
@@ -603,8 +603,7 @@ namespace :rubber do
603
603
  end
604
604
  end
605
605
  end
606
-
607
-
606
+
608
607
  def destroy_dyndns(instance_item)
609
608
  env = rubber_cfg.environment.bind(instance_item.role_names, instance_item.name)
610
609
  if env.dns_provider
@@ -633,10 +632,19 @@ namespace :rubber do
633
632
  expanded_pkg_list << pkg_spec
634
633
  end
635
634
  end
635
+
636
+ @os_specific_opts = get_host_options('os_packages') { |pkg_list| pkg_list.join(' ') }
637
+
636
638
  expanded_pkg_list << 'ec2-ami-tools' if rubber_env.cloud_provider == 'aws'
637
639
  expanded_pkg_list.join(' ')
638
640
  end
639
641
 
642
+ opts.each do |host, packages|
643
+ if @os_specific_opts.has_key?(host)
644
+ opts[host] << " #{@os_specific_opts[host]}"
645
+ end
646
+ end
647
+
640
648
  if upgrade
641
649
  if ENV['NO_DIST_UPGRADE']
642
650
  sudo_script 'upgrade_packages', <<-ENDSCRIPT
@@ -166,7 +166,7 @@ namespace :rubber do
166
166
  logger.info "Run 'cap rubber:describe_static_ips' to check the allocated ones"
167
167
  end
168
168
 
169
- def allocate_static_ip()
169
+ def allocate_static_ip
170
170
  ip = cloud.create_static_ip()
171
171
  fatal "Failed to allocate static ip" if ip.nil?
172
172
  return ip
@@ -207,13 +207,19 @@ namespace :rubber do
207
207
  # be installed.
208
208
  def get_host_options(cfg_name, &block)
209
209
  opts = {}
210
- rubber_instances.each do | ic|
210
+ rubber_instances.each do |ic|
211
211
  env = rubber_cfg.environment.bind(ic.role_names, ic.name)
212
212
  cfg_value = env[cfg_name]
213
+
213
214
  if cfg_value
215
+ if cfg_value.is_a?(Hash)
216
+ cfg_value = cfg_value[ic.os_version]
217
+ end
218
+
214
219
  if block
215
220
  cfg_value = block.call(cfg_value)
216
221
  end
222
+
217
223
  opts["hostvar_#{ic.full_name}"] = cfg_value if cfg_value && cfg_value.strip.size > 0
218
224
  end
219
225
  end
@@ -226,7 +232,7 @@ namespace :rubber do
226
232
  end
227
233
 
228
234
  def update_code_for_bootstrap
229
- unless (fetch(:rubber_code_was_updated, false))
235
+ unless fetch(:rubber_code_was_updated, false)
230
236
  deploy.setup
231
237
  logger.info "updating code for bootstrap"
232
238
  deploy.update_code
@@ -1,3 +1,3 @@
1
1
  module Rubber
2
- VERSION = '2.12.2'.freeze
2
+ VERSION = '2.13.0'.freeze
3
3
  end
@@ -9,7 +9,7 @@ namespace :rubber do
9
9
 
10
10
  task :install, :roles => :apache do
11
11
  rubber.sudo_script 'install_apache', <<-ENDSCRIPT
12
- a2dissite default
12
+ a2dissite *default || true
13
13
 
14
14
  # TODO: remove this once 12.04 is fixed
15
15
  # https://bugs.launchpad.net/ubuntu/+source/mod-proxy-html/+bug/964397