boxgrinder-build 0.7.1 → 0.8.0

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.
@@ -17,38 +17,39 @@
17
17
  # 02110-1301 USA, or see the FSF site: http://www.fsf.org.
18
18
 
19
19
  require 'fileutils'
20
+ require 'boxgrinder-core/helpers/log-helper'
20
21
  require 'boxgrinder-build/helpers/guestfs-helper'
21
22
 
22
23
  module BoxGrinder
23
24
  class ImageHelper
24
25
  def initialize(config, appliance_config, options = {})
25
- @config = config
26
- @appliance_config = appliance_config
26
+ @config = config
27
+ @appliance_config = appliance_config
27
28
 
28
- @log = options[:log] || Logger.new(STDOUT)
29
- @exec_helper = options[:exec_helper] || ExecHelper.new(:log => @log)
29
+ @log = options[:log] || LogHelper.new
30
+ @exec_helper = options[:exec_helper] || ExecHelper.new(:log => @log)
30
31
  end
31
32
 
32
33
  def mount_image(disk, mount_dir)
33
- offsets = calculate_disk_offsets(disk)
34
+ offsets = calculate_disk_offsets(disk)
34
35
 
35
36
  @log.debug "Mounting image #{File.basename(disk)} in #{mount_dir}..."
36
37
  FileUtils.mkdir_p(mount_dir)
37
38
 
38
- mounts = {}
39
+ mounts = {}
39
40
 
40
41
  offsets.each do |offset|
41
- loop_device = get_loop_device
42
- @exec_helper.execute("losetup -o #{offset.to_s} #{loop_device} #{disk}")
43
- label = @exec_helper.execute("e2label #{loop_device}").strip.chomp.gsub('_', '')
42
+ loop_device = get_loop_device
43
+ @exec_helper.execute("losetup -o #{offset.to_s} #{loop_device} '#{disk}'")
44
+ label = @exec_helper.execute("e2label #{loop_device}").strip.chomp.gsub('_', '')
44
45
  label = '/' if label == ''
45
46
  mounts[label] = loop_device
46
47
  end
47
48
 
48
- @exec_helper.execute("mount #{mounts['/']} #{mount_dir}")
49
+ @exec_helper.execute("mount #{mounts['/']} '#{mount_dir}'")
49
50
 
50
51
  mounts.reject { |key, value| key == '/' }.each do |mount_point, loop_device|
51
- @exec_helper.execute("mount #{loop_device} #{mount_dir}#{mount_point}")
52
+ @exec_helper.execute("mount #{loop_device} '#{mount_dir}#{mount_point}'")
52
53
  end
53
54
 
54
55
  @log.trace "Mounts:\n#{mounts}"
@@ -66,6 +67,33 @@ module BoxGrinder
66
67
  FileUtils.rm_rf(mount_dir)
67
68
  end
68
69
 
70
+ def disk_info(disk)
71
+ YAML.load(@exec_helper.execute("qemu-img info '#{disk}'"))
72
+ end
73
+
74
+ def convert_disk(disk, format, destination)
75
+ @log.debug "Conveting '#{disk}' disk to #{format} format and moving it to '#{destination}'..."
76
+
77
+ unless File.exists?(destination)
78
+ info = disk_info(disk)
79
+
80
+ if info['file format'] == format.to_s
81
+ @exec_helper.execute "cp '#{disk}' '#{destination}'"
82
+ else
83
+
84
+ format_with_options = format.to_s
85
+
86
+ if format == :vmdk
87
+ format_with_options += (`qemu-img --help | grep '\\-6'`.strip.chomp.empty? ? ' -o compat6' : ' -6')
88
+ end
89
+
90
+ @exec_helper.execute "qemu-img convert -f #{info['file format']} -O #{format_with_options} '#{disk}' '#{destination}'"
91
+ end
92
+ else
93
+ @log.debug "Destination already exists, skipping disk conversion."
94
+ end
95
+ end
96
+
69
97
  def get_loop_device
70
98
  begin
71
99
  loop_device = @exec_helper.execute("losetup -f 2>&1").strip
@@ -80,8 +108,8 @@ module BoxGrinder
80
108
  @log.debug "Calculating offsets for '#{File.basename(disk)}' disk..."
81
109
  loop_device = get_loop_device
82
110
 
83
- @exec_helper.execute("losetup #{loop_device} #{disk}")
84
- offsets = @exec_helper.execute("parted #{loop_device} 'unit B print' | grep -e '^ [0-9]' | awk '{ print $2 }'").scan(/\d+/)
111
+ @exec_helper.execute("losetup #{loop_device} '#{disk}'")
112
+ offsets = @exec_helper.execute("parted #{loop_device} 'unit B print' | grep -e '^ [0-9]' | awk '{ print $2 }'").scan(/\d+/)
85
113
  # wait one secont before freeing loop device
86
114
  sleep 1
87
115
  @exec_helper.execute("losetup -d #{loop_device}")
@@ -93,21 +121,21 @@ module BoxGrinder
93
121
 
94
122
  def create_disk(disk, size)
95
123
  @log.trace "Preparing disk..."
96
- @exec_helper.execute "dd if=/dev/zero of=#{disk} bs=1 count=0 seek=#{size * 1024}M"
124
+ @exec_helper.execute "dd if=/dev/zero of='#{disk}' bs=1 count=0 seek=#{size * 1024}M"
97
125
  @log.trace "Disk prepared"
98
126
  end
99
127
 
100
- def create_filesystem(disk, options = {})
128
+ def create_filesystem(loop_device, options = {})
101
129
  options = {
102
- :type => @appliance_config.hardware.partitions['/']['type'],
103
- :label => '/'
130
+ :type => @appliance_config.hardware.partitions['/']['type'],
131
+ :label => '/'
104
132
  }.merge(options)
105
133
 
106
134
  @log.trace "Creating filesystem..."
107
135
 
108
136
  case options[:type]
109
137
  when 'ext3', 'ext4'
110
- @exec_helper.execute "mke2fs -T #{options[:type]} -L '#{options[:label]}' -F #{disk}"
138
+ @exec_helper.execute "mke2fs -T #{options[:type]} -L '#{options[:label]}' -F #{loop_device}"
111
139
  else
112
140
  raise "Unsupported filesystem specified: #{options[:type]}"
113
141
  end
@@ -117,12 +145,12 @@ module BoxGrinder
117
145
 
118
146
  def sync_files(from_dir, to_dir)
119
147
  @log.debug "Syncing files between #{from_dir} and #{to_dir}..."
120
- @exec_helper.execute "rsync -Xura #{from_dir}/* #{to_dir}"
148
+ @exec_helper.execute "rsync -Xura #{from_dir.gsub(' ', '\ ')}/* '#{to_dir}'"
121
149
  @log.debug "Sync finished."
122
150
  end
123
151
 
124
152
  def customize(disk_path)
125
- GuestFSHelper.new(disk_path, :log => @log).customize do |guestfs, guestfs_helper|
153
+ GuestFSHelper.new(disk_path, :log => @log).customize(:ide_disk => ((@appliance_config.os.name == 'rhel' or @appliance_config.os.name == 'centos') and @appliance_config.os.version == '5') ? true : false) do |guestfs, guestfs_helper|
126
154
  yield guestfs, guestfs_helper
127
155
  end
128
156
  end
@@ -16,12 +16,12 @@
16
16
  # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
17
17
  # 02110-1301 USA, or see the FSF site: http://www.fsf.org.
18
18
 
19
- require 'logger'
19
+ require 'boxgrinder-core/helpers/log-helper'
20
20
 
21
21
  module BoxGrinder
22
22
  class LinuxHelper
23
23
  def initialize( options = {} )
24
- @log = options[:log] || Logger.new(STDOUT)
24
+ @log = options[:log] || LogHelper.new
25
25
  end
26
26
 
27
27
  def kernel_version( guestfs )
@@ -16,15 +16,16 @@
16
16
  # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
17
17
  # 02110-1301 USA, or see the FSF site: http://www.fsf.org.
18
18
 
19
+ require 'boxgrinder-core/helpers/log-helper'
20
+
19
21
  module BoxGrinder
20
22
  class PackageHelper
21
- def initialize(config, appliance_config, dir, options = {})
22
- @config = config
23
+ def initialize(config, appliance_config, options = {})
24
+ @config = config
23
25
  @appliance_config = appliance_config
24
- @dir = dir
25
26
 
26
- @log = options[:log] || Logger.new(STDOUT)
27
- @exec_helper = options[:exec_helper] || ExecHelper.new({:log => @log})
27
+ @log = options[:log] || LogHelper.new
28
+ @exec_helper = options[:exec_helper] || ExecHelper.new(:log => @log)
28
29
  end
29
30
 
30
31
  def package(dir, package, type = :tar)
@@ -38,17 +39,13 @@ module BoxGrinder
38
39
  case type
39
40
  when :tar
40
41
  package_name = File.basename(package, '.tgz')
41
- symlink = "#{File.dirname(package)}/#{package_name}"
42
+ symlink = "#{File.dirname(package)}/#{package_name}"
42
43
 
43
44
  FileUtils.ln_s(File.expand_path(dir), symlink)
44
-
45
- Dir.chdir(File.dirname(package)) do
46
- @exec_helper.execute "tar -hcvzf #{package_name}.tgz #{package_name}"
47
- end
48
-
45
+ @exec_helper.execute "tar -C '#{File.dirname(package)}' -hcvzf '#{package}' '#{package_name}'"
49
46
  FileUtils.rm(symlink)
50
47
  else
51
- raise "Only tar format is currently supported."
48
+ raise "Specified format: '#{type}' is currently unsupported."
52
49
  end
53
50
 
54
51
  @log.info "Appliance #{@appliance_config.name} packaged."
@@ -16,14 +16,15 @@
16
16
  # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
17
17
  # 02110-1301 USA, or see the FSF site: http://www.fsf.org.
18
18
 
19
- require 'boxgrinder-build/managers/plugin-manager'
20
19
  require 'rubygems'
20
+ require 'boxgrinder-build/managers/plugin-manager'
21
+ require 'boxgrinder-core/helpers/log-helper'
21
22
 
22
23
  module BoxGrinder
23
24
  class PluginHelper
24
- def initialize( options = {} )
25
- @log = options[:log] || Logger.new(STDOUT)
26
- @options = options[:options]
25
+ def initialize( config, options = {} )
26
+ @options = config
27
+ @log = options[:log] || LogHelper.new
27
28
  end
28
29
 
29
30
  def load_plugins
@@ -40,27 +41,18 @@ module BoxGrinder
40
41
  self
41
42
  end
42
43
 
43
- def parse_plugin_list
44
- plugins = []
45
-
46
- unless @options.plugins.nil?
47
- plugins = @options.plugins.gsub('\'', '').gsub('"', '').split(',')
48
- plugins.each { |plugin| plugin.chomp!; plugin.strip! }
49
- end
50
-
51
- plugins
52
- end
53
-
54
44
  def read_and_require
55
- plugins = %w(boxgrinder-build-fedora-os-plugin boxgrinder-build-rhel-os-plugin boxgrinder-build-centos-os-plugin boxgrinder-build-ec2-platform-plugin boxgrinder-build-vmware-platform-plugin boxgrinder-build-s3-delivery-plugin boxgrinder-build-sftp-delivery-plugin boxgrinder-build-local-delivery-plugin boxgrinder-build-ebs-delivery-plugin) + parse_plugin_list
45
+ plugins = %w(boxgrinder-build-fedora-os-plugin boxgrinder-build-rhel-os-plugin boxgrinder-build-centos-os-plugin boxgrinder-build-ec2-platform-plugin boxgrinder-build-vmware-platform-plugin boxgrinder-build-virtualbox-platform-plugin boxgrinder-build-s3-delivery-plugin boxgrinder-build-sftp-delivery-plugin boxgrinder-build-local-delivery-plugin boxgrinder-build-ebs-delivery-plugin) + @options.additional_plugins
56
46
 
57
47
  plugins.flatten.each do |plugin|
58
- @log.trace "Requiring plugin '#{plugin}'..."
48
+ @log.trace "Loading plugin '#{plugin}'..."
59
49
 
60
50
  begin
61
51
  require plugin
62
- rescue LoadError
63
- @log.warn "Specified gem: '#{plugin}' wasn't found. Make sure its name is correct, skipping..." unless plugin.match(/^boxgrinder-build-(.*)-plugin/)
52
+ @log.trace "- OK"
53
+ rescue LoadError => e
54
+ @log.trace "- Not found: #{e.message.strip.chomp}"
55
+ @log.warn "Specified plugin: '#{plugin}' wasn't found. Make sure its name is correct, skipping..." unless plugin.match(/^boxgrinder-build-(.*)-plugin/)
64
56
  end
65
57
  end
66
58
  end
@@ -0,0 +1,82 @@
1
+ # Copyright 2010 Red Hat, Inc.
2
+ #
3
+ # This is free software; you can redistribute it and/or modify it
4
+ # under the terms of the GNU Lesser General Public License as
5
+ # published by the Free Software Foundation; either version 3 of
6
+ # the License, or (at your option) any later version.
7
+ #
8
+ # This software is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11
+ # Lesser General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU Lesser General Public
14
+ # License along with this software; if not, write to the Free
15
+ # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
16
+ # 02110-1301 USA, or see the FSF site: http://www.fsf.org.
17
+
18
+ require 'thor'
19
+
20
+ class Thor
21
+ module CoreExt
22
+ class HashWithIndifferentAccess < ::Hash
23
+ def initialize(hash={})
24
+ super()
25
+ hash.each do |key, value|
26
+ self[convert_key(key)] = value
27
+ end
28
+
29
+ to_boolean(self)
30
+ end
31
+
32
+ def to_boolean(h)
33
+ h.each do |k, v|
34
+ if v.is_a?(Hash)
35
+ to_boolean(v)
36
+ next
37
+ end
38
+
39
+ next unless v.is_a?(String)
40
+
41
+ case v
42
+ when /^true$/i then
43
+ h[k] = true
44
+ when /^false$/i then
45
+ h[k] = false
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ module BoxGrinder
54
+ class ThorHelper < Thor
55
+ class << self
56
+ def help(shell)
57
+ boxgrinder_header(shell)
58
+ super(shell)
59
+ end
60
+
61
+ def task_help(shell, task_name)
62
+ boxgrinder_header(shell)
63
+
64
+ help_method = "#{task_name}_help".to_sym
65
+ send(help_method, shell) if respond_to?(help_method) and method(help_method).arity == 1
66
+ super(shell, task_name)
67
+
68
+ shell.say
69
+ end
70
+
71
+ def boxgrinder_header(shell)
72
+ shell.say
73
+ shell.say "BoxGrinder Build:"
74
+ shell.say " A tool for building VM images from simple definition files."
75
+ shell.say
76
+ shell.say "Documentation:"
77
+ shell.say " http://community.jboss.org/docs/DOC-14358"
78
+ shell.say
79
+ end
80
+ end
81
+ end
82
+ end
@@ -20,8 +20,8 @@ require 'singleton'
20
20
 
21
21
  module BoxGrinder
22
22
  module Plugins
23
- def plugin( args )
24
- PluginManager.instance.register_plugin( args )
23
+ def plugin(args)
24
+ PluginManager.instance.register_plugin(args)
25
25
  end
26
26
  end
27
27
  end
@@ -34,11 +34,11 @@ module BoxGrinder
34
34
  include Singleton
35
35
 
36
36
  def initialize
37
- @plugins = { :delivery => {}, :os => {}, :platform => {}}
37
+ @plugins = {:delivery => {}, :os => {}, :platform => {}}
38
38
  end
39
39
 
40
- def register_plugin( info )
41
- validate_plugin_info( info )
40
+ def register_plugin(info)
41
+ validate_plugin_info(info)
42
42
 
43
43
  raise "We already have registered plugin for #{info[:name]}." unless @plugins[info[:name]].nil?
44
44
 
@@ -53,13 +53,13 @@ module BoxGrinder
53
53
  self
54
54
  end
55
55
 
56
- def validate_plugin_info( info )
56
+ def validate_plugin_info(info)
57
57
  raise "No name specified for your plugin" if info[:name].nil?
58
58
  raise "No class specified for your plugin" if info[:class].nil?
59
59
  raise "No type specified for your plugin" if info[:type].nil?
60
60
  end
61
61
 
62
- def initialize_plugin( type, name )
62
+ def initialize_plugin(type, name)
63
63
  plugins = @plugins[type]
64
64
  # this should never happen
65
65
  raise "There are no #{type} plugins." if plugins.nil?
@@ -68,26 +68,11 @@ module BoxGrinder
68
68
 
69
69
  begin
70
70
  plugin = plugin_info[:class].new
71
- rescue => e
72
- raise "Error while initializing #{plugin_info[:class]} plugin.", e
71
+ rescue
72
+ raise "Error while initializing '#{plugin_info[:class].to_s}' plugin."
73
73
  end
74
74
 
75
- [ plugin, plugin_info ]
76
- end
77
-
78
- def plugin_types( type )
79
- types = []
80
-
81
- available_plugins_for_selected_type = @plugins[type]
82
-
83
- unless available_plugins_for_selected_type.nil?
84
- available_plugins_for_selected_type.each_value do |info|
85
- types << info[:types] unless info[:types].nil?
86
- types << info[:name]
87
- end
88
- end
89
-
90
- types.flatten
75
+ [plugin, plugin_info]
91
76
  end
92
77
 
93
78
  attr_reader :plugins
@@ -28,31 +28,31 @@ require 'logger'
28
28
  module BoxGrinder
29
29
  class BasePlugin
30
30
  def initialize
31
- @plugin_config = {}
31
+ @plugin_config = {}
32
32
 
33
- @deliverables = OpenCascade.new
34
- @supported_oses = OpenCascade.new
33
+ @deliverables = OpenCascade.new
34
+ @supported_oses = OpenCascade.new
35
35
  @target_deliverables = OpenCascade.new
36
- @dir = OpenCascade.new
36
+ @dir = OpenCascade.new
37
37
  end
38
38
 
39
39
  def init(config, appliance_config, options = {})
40
- @config = config
41
- @appliance_config = appliance_config
42
- @options = options
43
- @log = options[:log] || Logger.new(STDOUT)
44
- @exec_helper = options[:exec_helper] || ExecHelper.new(:log => @log)
45
- @image_helper = options[:image_helper] || ImageHelper.new(@config, @appliance_config, :log => @log)
46
- @plugin_info = options[:plugin_info]
47
- @previous_plugin_info = options[:previous_plugin_info]
48
- @previous_deliverables = options[:previous_deliverables] || {}
40
+ @config = config
41
+ @appliance_config = appliance_config
42
+ @options = options
43
+ @log = options[:log] || Logger.new(STDOUT)
44
+ @exec_helper = options[:exec_helper] || ExecHelper.new(:log => @log)
45
+ @image_helper = options[:image_helper] || ImageHelper.new(@config, @appliance_config, :log => @log)
46
+ @previous_plugin_info = options[:previous_plugin_info]
47
+ @previous_deliverables = options[:previous_deliverables] || OpenCascade.new
49
48
 
50
- @dir.base = "#{@appliance_config.path.build}/#{@plugin_info[:name]}-plugin"
51
- @dir.tmp = "#{@dir.base}/tmp"
49
+ @plugin_info = options[:plugin_info]
52
50
 
53
- @config_file = "#{ENV['HOME']}/.boxgrinder/plugins/#{@plugin_info[:name]}"
51
+ @dir.base = "#{@appliance_config.path.build}/#{@plugin_info[:name]}-plugin"
52
+ @dir.tmp = "#{@dir.base}/tmp"
54
53
 
55
54
  read_plugin_config
55
+ merge_plugin_config
56
56
 
57
57
  @move_deliverables = true
58
58
  @initialized = true
@@ -67,7 +67,7 @@ module BoxGrinder
67
67
  raise "Please specify deliverables as Hash, not #{deliverable.class}." unless deliverable.is_a?(Hash)
68
68
 
69
69
  deliverable.each do |name, path|
70
- @deliverables[name] = "#{@dir.tmp}/#{path}"
70
+ @deliverables[name] = "#{@dir.tmp}/#{path}"
71
71
  @target_deliverables[name] = "#{@dir.base}/#{path}"
72
72
  end
73
73
  end
@@ -80,6 +80,7 @@ module BoxGrinder
80
80
  end
81
81
 
82
82
  def is_supported_os?
83
+ return true if @supported_oses.empty?
83
84
  return false unless !@supported_oses[@appliance_config.os.name].nil? and @supported_oses[@appliance_config.os.name].include?(@appliance_config.os.version)
84
85
  true
85
86
  end
@@ -108,10 +109,8 @@ module BoxGrinder
108
109
  def validate_plugin_config(fields = [], doc = nil)
109
110
  more_info = doc.nil? ? '' : "See #{doc} for more info"
110
111
 
111
- raise "Not valid configuration file for #{info[:name]} plugin. Please create valid '#{@config_file}' file. #{more_info}" if @plugin_config.nil?
112
-
113
112
  fields.each do |field|
114
- raise "Please specify a valid '#{field}' key in plugin configuration file: '#{@config_file}'. #{more_info}" if @plugin_config[field].nil?
113
+ raise "Please specify a valid '#{field}' key in BoxGrinder configuration file: '#{@config.file}' or use CLI '--#{@plugin_info[:type]}-config #{field}:DATA' argument. #{more_info}" if @plugin_config[field].nil?
115
114
  end
116
115
  end
117
116
 
@@ -120,18 +119,18 @@ module BoxGrinder
120
119
  end
121
120
 
122
121
  def run(*args)
122
+ unless is_supported_os?
123
+ @log.error "#{@plugin_info[:full_name]} plugin supports following operating systems: #{supported_oses}. Your appliance contains #{@appliance_config.os.name} #{@appliance_config.os.version} operating system which is not supported by this plugin, sorry."
124
+ return
125
+ end
126
+
123
127
  FileUtils.rm_rf @dir.tmp
124
128
  FileUtils.mkdir_p @dir.tmp
125
129
 
126
130
  execute(*args)
127
131
 
128
- after_execute
129
- end
132
+ # TODO execute post commands for platform plugins here?
130
133
 
131
- def after_init
132
- end
133
-
134
- def after_execute
135
134
  @deliverables.each do |name, path|
136
135
  @log.trace "Moving '#{path}' deliverable to target destination '#{@target_deliverables[name]}'..."
137
136
  FileUtils.mv(path, @target_deliverables[name])
@@ -140,6 +139,12 @@ module BoxGrinder
140
139
  FileUtils.rm_rf @dir.tmp
141
140
  end
142
141
 
142
+ def after_init
143
+ end
144
+
145
+ def after_execute
146
+ end
147
+
143
148
  def deliverables_exists?
144
149
  raise "You can only check deliverables after the plugin is initialized, please initialize the plugin using init method." if @initialized.nil?
145
150
 
@@ -163,16 +168,28 @@ module BoxGrinder
163
168
  @plugin_config[key] = @plugin_config[key].nil? ? value : @plugin_config[key]
164
169
  end
165
170
 
171
+ # This reads the plugin config from file
166
172
  def read_plugin_config
167
- return unless File.exists?(@config_file)
173
+ return if @config[:plugins].nil? or @config[:plugins][@plugin_info[:name].to_s].nil?
168
174
 
169
- @log.debug "Reading configuration file for #{self.class.name}."
175
+ @log.debug "Reading configuration for #{@plugin_info[:full_name]} plugin."
170
176
 
171
- begin
172
- @plugin_config = YAML.load_file(@config_file)
173
- rescue
174
- raise "An error occurred while reading configuration file '#{@config_file}' for #{self.class.name}. Is it a valid YAML file?"
175
- end
177
+ @plugin_config = @config[:plugins][@plugin_info[:name].to_s]
178
+ end
179
+
180
+ # This merges the plugin config with configuration provided in command line
181
+ def merge_plugin_config
182
+ config =
183
+ case @plugin_info[:type]
184
+ when :os
185
+ @config.os_config
186
+ when :platform
187
+ @config.platform_config
188
+ when :delivery
189
+ @config.delivery_config
190
+ end
191
+
192
+ @plugin_config.merge!(config)
176
193
  end
177
194
  end
178
195
  end