简单看了下VMC的源码,写一个类似提纲的东西。文章里面主要包括VMC各个代码块和他们实现的功能,通过一个命令的实现流程来分析。
VMC执行一条命令的流程:
1. bin/vmc
vmc是一个gem包,所以我们可以在自己的gem文件夹下面找到他们,执行vmc命令其实就是运行bin目录下的vmc文件。这个文件很简单,首先require lib/cli.rb,然后调用VMC::Cli::Runner.run。
2. Cli.rb
前面说到vmc require了Cli.rb文件,而Cli.rb包含了几乎所有需要用到的模块,包括micro的以及普通的Cli。由于我没用过micro的命令,所以本文不对micro作出分析。我们看下Cli.rb文件中有关Cli的model(代码中的autoload相当于lazy的require,只有在需要时才require):
module Cli
autoload :Config, "#{ROOT}/cli/config"
autoload :Framework, "#{ROOT}/cli/frameworks"
autoload :Runner, "#{ROOT}/cli/runner"
autoload :ZipUtil, "#{ROOT}/cli/zip_util"
autoload :ServicesHelper, "#{ROOT}/cli/services_helper"
autoload :TunnelHelper, "#{ROOT}/cli/tunnel_helper"
autoload :ManifestHelper, "#{ROOT}/cli/manifest_helper"
autoload :ConsoleHelper, "#{ROOT}/cli/console_helper"
module Command
autoload :Base, "#{ROOT}/cli/commands/base"
autoload :Admin, "#{ROOT}/cli/commands/admin"
autoload :Apps, "#{ROOT}/cli/commands/apps"
autoload :Micro, "#{ROOT}/cli/commands/micro"
autoload :Misc, "#{ROOT}/cli/commands/misc"
autoload :Services, "#{ROOT}/cli/commands/services"
autoload :User, "#{ROOT}/cli/commands/user"
autoload :Manifest, "#{ROOT}/cli/commands/manifest"
end
这里主要分成两块:一是cli目录下的一些工具类,提供了Cli需要的一些通用功能,而另外一部分是在commands目录下,代表了各种不同类型的命令,比如有关于app的(apps),或者有关于service的(services)。
3. Runner.run
前面提到bin/vmc执行命令就是调用Runner.run。那么这个函数在vmc/lib/cli/runner.rb文件中。这个文件主要解析命令以及命令的参数,然后生成一个command,就是前面Cli.rb中提到的各种commands。解析命令其实比较简单,就是分析是哪种类型的(app?service?misc?。。。),然后命令是什么(list?target?。。。),最后参数是什么。
在获得这些数据之后就会尝试生成一个command:
cmd = VMC::Cli::Command.const_get(@namespace.to_s.capitalize)
cmd.new(@options).send(@action, *@args.collect(&:dup))
这两行代码的意思是:首先在Command空间下找到namespace对应的常量string(namespace就是对应的类名,比如app的就是Apps,admin就是Admin)。获得这个常量string是为了new一个实例出来,cmd.new(@options)意思就是new一个这个cmd string命名的类。然后.send()是调用这个类的一个函数,函数名就是第一个参数@action,第二个参数就是这个函数的参数。可以看到ruby的反射真是非常的简单。
举个例子:比如我执行命令vmc stop appname。那么这个命令会在runner.rb中解析为:namespace:apps,action:stop,args:appname。然后new一个Apps实例,调用它的stop函数,把appname传进去。
4. 实际执行
从上面的分析我们的流程已经到了command里面了,那么每个command会对命令作出相应的操作。那么我们看Apps类好了,我们看一个list函数,这个函数对应了vmc apps命令(这里list是apps的别名,所以我们可以不使用vmc apps list命令)。那么看这个函数的代码:
def list
apps = client.apps
apps.sort! {|a, b| a[:name] <=> b[:name] }
return display JSON.pretty_generate(apps || []) if @options[:json]
display "\n"
return display "No Applications" if apps.nil? || apps.empty?
apps_table = table do |t|
t.headings = 'Application', '# ', 'Health', 'URLS', 'Services'
apps.each do |app|
t << [app[:name], app[:instances], health(app), app[:uris].join(', '), app[:services].join(', ')]
end
end
display apps_table
end
有一行代码:apps = client.apps,我们暂时不管它,只需要知道它是获得了当前cf中的所有app,赋值给apps。那么后面就是在display了,可以返回json格式的,或者table格式的。在table中我们可以看到熟悉的apps table header。
那么回到略过的那一行,我们需要知道client在哪里:这个client其实就是vmc/lib/vmc/client.rb中Client类的实例,那么我们开始看Client类:
5. Client.rb
Client负责一个任务:和CF的cloud controller交互。比如vmc apps,需要给cc发送一个获得所有apps的请求。所以前面提到的client.apps就是调用它的apps函数,这个函数最终会通过request函数发出请求。
至此我们的流程已经全部走通了,VMC还是比较简单的,希望大家能够学到点东西