Like Ruby Metaprogramming, puppet and chef use a lot of Ruby's language features to define a new deployment language.
Share a few in the actual project used in the scene, limited capacity, if there is a better program, please leave a message to me:
RPC Interface templating--using Eval, alias, Defind_method
Require ' RACK/RPC '
class Server < Rack::rpc::server
def hello_world
"Hello, world!"
End
RPC ' Hello_world ' =>: Hello_world
End
Above is an RPC server that writes a function that invokes the RPC command for registration.
Using Define_method, eval, alias method, you can realize a rpc/directory of *.rb files, loading and RPC interface registration function, the implementation code is as follows:
Module RPC require ' RACK/RPC ' #require rpc/*.rb file Dir.glob (File.join (file.dirname), ' RPC ', ' __file__ ')) do *.rb
le| Require file End Class Runner < Rack::rpc::server #include the rpc/*.rb and regsiter RPC call #eg.
RPC/GOD.RB God.hello @ @rpc_list = [] Dir.glob (File.join (File.dirname (__file__), ' RPC ', "*.rb ')) do |file| Rpc_class = File.basename (File). Split ('. RB ') [0].capitalize rpc_list = [] #加载module下的方法到Runner这个类下面 eval Include Frigga::rpc::#{rpc_class} "#获取声明的RPC接口 eval" rpc_list = frigga::rpc::#{rpc_class}::rpc_list "rpc_list.
Each do |rpc_name| #alias一个新的rpc方法, called Old_xxxx_xxxx eval "Alias:old_#{rpc_class.downcase}_#{rpc_name}: #{rpc_name}" #重新定义rpc方法, add a row
Log printing, and then call the Old_xxxx_xxxx RPC method Define_method "#{rpc_class.downcase}_#{rpc_name}". To_sym do |*arg| Logger.info ' [#{request.ip}] called #{rpc_class.downcase}.#{rpc_name} #{arg.join (', ')} ' eval ' OLD_#{RPC_CLASS.DOWNC
Ase}_#{rpc_name} *arg " End #注册RPC调用 RPC "#{rpc_class.downcase}.#{rpc_name}" => "#{rpc_class.downcase}_#{rpc_name}". To_sym #添加到全局变量, rollup all RPC methods @ @rpc_list << "#{rpc_class.downcase}.#{rpc_name}" End-Def help Rpc_ Methods = ([' Help '] + @ @rpc_list. Sort). Join ("\ n") End RPC "Help" =>: Help End #RPC
After completing the above function, it is very convenient to develop RPC interface, such as the following IP address Add, delete, check code, register ip.list, Ip.add and Ip.del methods:
Module RPC
module Ip
#RPC_LIST used for regsiter rpc_call
rpc_list =%w (list add del)
def LIST
$white _lists
End
def add (IP)
if IP =~/^ (25[0-5]|2[0-4]\d|[ 0-1]?\d\d?) \.) {3} (25[0-5]|2[0-4]\d| [0-1]?\d\d?] $/
$white _lists << IP
write_to_file return
' succ '
else return
' fail '
end End
def del (IP)
if $white _lists.include? ( IP)
$white _lists.delete IP
write_to_file return
"SUCC"
else return
"fail"
end End
def write_to_file
File.Open (ip_yml, "w") do |f|
$white _lists.uniq.each {|i| F << "-#{i}\n"} End End end
dsl--Use Instance_eval
Instance_eval is a Swiss army knife in the Ruby language, especially for DSL support.
Let's take a look at the API for setting up file templates in chef (an Open-source Automated deployment tool):
Copy Code code as follows:
Template "/path/to/file.conf" do
SOURCE "File.conf.erb"
Owner "Wilbur"
Mode "0744"
End
In the above code, source, owner, mode need to be passed from the external block to the template inside the block, in order to achieve this goal, the use of the Instance_eval code is as follows:
Class CHEFDSL
def template (path, &block)
templatedsl.new (path, &block)
end
class TEMPLATEDSL
def initialize (path, &block)
@path = Path
instance_eval &block
end
def source (source); @source = source; End
def owner (owner); @owner = owner; End
def mode (mode); @mode = mode; End End
This little trick allows the Templatedsl object to apply block, as it does in its own scope. The block can access and invoke variables and methods in TEMPLATEDSL.
If Instance_eval is not used, as in the following code, Ruby throws a nomethoderror because source, owner, mode cannot be accessed in the block.
Copy Code code as follows:
Class TEMPLATEDSL
Def initialize (path, &block)
@path = Path
Block.call
End
End
Of course, you can also use yeild transfer variables, but not instance_eval concise and flexible.
command-line interaction--using Instance_eval
command line interaction, you can use Highline this gem.
However, highline in some ways can not meet my needs, such as the above described in the Chef template function, to achieve the following effect, greatly simplifying the duplicate code:
Copy Code code as follows:
#检查frigga fail, ask if you want to continue
Tip.ask Frigga_fail? Todo
Banner "Check Some Frigga failed, skip failed host and continue deploy?"
On:yes
On:quit do
Raise Odin::tipquitexcption
End
End
...
#运行时显示结果如下:
Check Some Frigga failed, skip failed host and continue deploy? [Yes/quit]
#输入yes继续, enter quit exit
The implementation code is as follows:
require ' Colorize ' class Tip def self.ask (stat = true, &block) new (&block
). RET if stat = = True End Attr_reader:ret def initialize (&block) @opt = [] @caller = {} @banner = ""
@ret = False Self.instance_eval (&block) print "#{@banner} [#{@opt. Join ('/')}]:". Light_yellow Loop Do x = Gets.chomp.strip.to_sym if @opt. Include? (x) @ret = (@caller [x].call if @caller. Key? x) If @ret = =: Retry print "\n#{@banner} [#{@opt. Join ('/')}]:". Light_yellow Next Else retur
N @ret End Else print input error, please enter [#{@opt. Join ('/')}]: ". Light_yellow End End
def on (opt, &block) @opt << opt @caller [opt] = block if Block_given? End Def banner (str) @banner = str End-end