簡單看了下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還是比較簡單的,希望大家能夠學到點東西