Chef源码组织
我们这里讲的Chef源码,是指opscode发布在github的那个名为chef的项目 chef in github: https://github.com/opscode/chef
chef本身就是一个gem,它的源码组织结构,首先是一个gem的组织结构:
组织结构
基本上Chef的组织结构是一个标准的gem包。
一个gem包的主要元素:
lib/
lib下面,是这个gem的核心代码
.gemspec文件
这里是这个gem的规格文件,里面包含了gem的作者信息、lib的目录位置、bin目录位置、运行时依赖包以及开发时依赖包等
bin/
如果你的gem有终端命令,那么就放到这个目录下面吧
tasks/
放置Rack任务的目录。
spec/
Rspec测试目录
Gemfile
bunlder的包管理文件。
回到我们的Chef源码目录里面:
chef.gemspec
这是chef这个gem的规格文件,
Gem::Specification.new do |s|
s.name = 'chef'
s.version = Chef::VERSION
s.platform = Gem::Platform::RUBY
#...
s.required_ruby_version = ">= 1.9.3"
s.add_dependency "mixlib-config", "~> 2.0"
s.add_dependency "mixlib-cli", "~> 1.4"
s.add_dependency "mixlib-log", "~> 1.3"
s.add_dependency "mixlib-authentication", "~> 1.3"
s.add_dependency "mixlib-shellout", ">= 2.0.0.rc.0", "< 3.0"
s.add_dependency "ohai", ">= 7.6.0.rc.0"
#...
s.bindir = "bin"
#...
s.require_path = 'lib'
#...
end
一个gemspec文件,必须是按照这种格式来写。实际上是初始化了一个Gem::Specification类的对象。你也可以理解为,chef这个gem,就是一个gem对象。这个文件指定了chef这个gem的各种属性。
bin
我们再来看下bin目录。 bin里面放置的是chef这个gem所包含的命令:
我们看到chef常用的几个命令了:chef-client、knife、chef-shell、chef-solo等。我们打开chef-client文件看看:
require 'rubygems'
$:.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
require 'chef'
require 'chef/application/client'
Chef::Application::Client.new.run
再打开knife文件看看:
require 'rubygems'
$:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "lib")))
require 'chef/application/knife'
Chef::Application::Knife.new.run
require 'rubygems'
$:.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
require 'chef/application/apply'
Chef::Application::Apply.new.run
我们依次打开这个几个bin文件看了下,除了chef-shell和shef文件之外,结构基本都差不多, 都和上面的代码类似,都是加载了rubygems, 在加载路径里面添加了lib目录, 然后又把lib/chef/applications/下面的相关文件加载,最后用了几乎统一的方法:
Chef::Application::Xxx.new.run
我们学过Ruby的基础知识,可以看得出来,Chef::Application::Xxx肯定是被定义在 chef/application/目录下面的Xxx类。
而chef-shell和shef文件,我们打开后,惊异的发现,这俩文件中代码几乎一模一样:
chef-shell:
begin
require "rubygems"
rescue LoadError
end
require "irb"
require "irb/completion"
require 'irb/ext/save-history'
$:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "lib")))
require "chef/shell"
# On Windows only, enable irb --noreadline because of input problems --
# See CHEF-3284.
IRB.conf[:USE_READLINE] = false if Chef::Platform::windows?
Shell.start
shef:
begin
require "rubygems"
rescue LoadError
end
require "irb"
require "irb/completion"
require 'irb/ext/save-history'
$:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "lib")))
require "chef/shell"
Chef::Log.warn("DEPRECATED: The 'shef' program is renamed to 'chef-shell'")
Shell.start
当我们看到shef中倒数第二行代码的时候:
Chef::Log.warn("DEPRECATED: The 'shef' program is renamed to 'chef-shell'")
可以看得出来,Chef::Log.warn ,应该是Chef的日志输出,warn代表一个警告,里面的字符串意思是:过期声明: 这个shef程序已经被重命名为chef-shell了。
好了,我们已经破案了。 只需要看chef-shell就好了。
chef-shell代码中,require了irb及其组件,并且也加载了chef/shell文件,最后启动了Shell.start命令。
chef-shell命令,实际上是开启了一个加载了chef环境的irb交互shell界面。我们在安装了chef的终端输入这个命令就知道了:
$ chef-shell
loading configuration: none (standalone session)
Session type: standalone
Loading.....done.
This is the chef-shell.
Chef Version: 11.16.0
http://www.opscode.com/chef
http://docs.opscode.com/
run `help' for help, `exit' or ^D to quit.
Ohai2u vagrant@chef-node!
chef >
你可以输入help查看帮助。
lib
我们通过查看bin下面的文件,了解到,一个chef命令的执行,必须先加载lib/chef下面的相关文件,那么我们就去lib目录下面看看。
lib目录下面的文件组织也是有规范的:
这样的组织结构,意味着,当我们使用这个gem的时候,直接:
require 'chef'
就可以了。 require 'chef', 这个命令会直接加载chef gem lib下面的chef.rb,我们看看chef.rb中的代码:
require 'chef/version'
require 'chef/nil_argument'
require 'chef/mash'
require 'chef/exceptions'
require 'chef/log'
require 'chef/config'
require 'chef/providers'
require 'chef/resources'
require 'chef/shell_out'
require 'chef/daemon'
require 'chef/run_status'
require 'chef/handler'
require 'chef/handler/json_file'
require 'chef/monkey_patches/tempfile'
require 'chef/monkey_patches/string'
require 'chef/monkey_patches/numeric'
require 'chef/monkey_patches/object'
require 'chef/monkey_patches/file'
require 'chef/monkey_patches/uri'
chef.rb中就是一堆require, 加载了lib/chef目录下的全部文件。
所以,chef.rb,算是一个入口。核心的代码还在chef目录下面。
但是我们从chef.rb文件中加载的文件名称中,也可以看出chef整体架构的一个大概。
chef分为四部分,由空行来分隔:
#第一部分:
require 'chef/version'
require 'chef/nil_argument'
require 'chef/mash'
require 'chef/exceptions'
require 'chef/log'
require 'chef/config'
require 'chef/providers'
require 'chef/resources'
require 'chef/shell_out'
这部分似乎定义了Chef的版本、配置、日志、resources、providers、shell等应用层面的东西。
# 第二部分
require 'chef/daemon'
这一部分,似乎是定义了chef daemon的功能代码。我们知道,chef-client可以以daemon模式运行。
# 第三部分
require 'chef/run_status'
require 'chef/handler'
require 'chef/handler/json_file'
这一部分,似乎是定义了chef的运行状态、handler的信息, 用于处理chef的内部状态信息。
require 'chef/monkey_patches/tempfile'
require 'chef/monkey_patches/string'
require 'chef/monkey_patches/numeric'
require 'chef/monkey_patches/object'
require 'chef/monkey_patches/file'
require 'chef/monkey_patches/uri'
这一部分,我们看到了monkey_patches 以及熟悉的Ruby内部类同名的文件,几乎可以肯定,这是chef自己对于Ruby提供的类进行了monkey patch,添加了自己要用的方法。 我们在前面讲类与模块那一节,也用这个代码举过例子。
当然,以上都是目测, 让我们继续查看lib/chef下面的文件吧。
lib/chef
我们打开lib/chef目录,发现里面很多文件和文件夹, 跟我们上面根据chef.rb的猜想有点差别。那么我们把chef.rb中require的文件一个个看一下吧,看完后发现,并没有囊括完chef目录中的所有文件。
那这些文件都是干嘛的呢?
先来剧透一下吧。
如果你看过chef-server-webui,应该可以知道,在chef-server-webui里面也用到了chef,主要是用到了chef/rest这部分文件。
还有上面说过的bin目录下的的命令,也需要用到chef/application.rb以及 chef/application/, chef/api_client/等下面的各种文件。
lib/chef下面还有一部分文件是用于测试的,那些我们就暂时不关心了。
我们继续看看chef最重要的东西。
Knife
knife是chef中不可或缺的工具。 我们通过knife命令和chef server、node交互。那么让我们来看下knife的工作机制:
相关文件
knife 相关的最主要的文件是:
- lib/chef/knife.rb
- lib/chef/knife/*.rb
我们打开lib/chef/knife/目录,可以看到里面定义了很多rb文件, 这里几乎定义了全部的knife的命令实现,以及辅助方法。
如果想深入了解的话,具体看代码吧。
Chef Client
chef client的所有行为都在lib/chef/client.rb中。
Chef中的DSL方法
构成recipe的那些chef resource,都在chef/resource.rb和chef/resource/目录下面被定义。
尤其是在chef/resource/目录下面,定义了我们常用的resource:
- file
- template
- execute
- directory
- template
- user
等等。具体可以再深入研究源码。