2015-02-06 122 views
5

我在觀看WWDC 2014「與Xcode的持續集成」視頻,它看起來很棒,這些機器人如何用於運行測試。 但我的問題是任何看過視頻的人,當他發送消息給Jeeves說「整合CoffeeBoard」.Bot開始整合。我想知道他是怎麼做到的。開始手動集成Xcode Bot嗎?

我想在github上添加post-receive hook,它在接收任何提交時應該在我的OS X服務器上啓動Xcode bot。我的大部分團隊成員都使用SourceTree或GitHub來管理他們的git,他們不想使用Xcode Source Control。我認爲創建一個機器人並設置其手動啓動的選項將會訣竅。我需要知道的是,「OS X服務器爲您提供了哪些選項可以啓動機器人嗎?」

對不起,如果我不夠清楚。但是對我來說這太令人困惑,因爲他們對觸發器的文檔非常少。儘管他提到它作爲很酷的新功能,他們已沒有任何信息以達到

回答

7

前兩個答案並不完全回答「他們如何做到這一點」的原始問題,以便從Messages應用程序中啓動機器人。

我重新創建了模擬Jeeves虛擬助理與機器人交互(以及獲取天氣)所需的確切工作流和腳本。

的完整詳細信息,請參見鏈接的PDF文檔:

https://s3.amazonaws.com/icefield/IntegratingXcodeBotsWithMessages.pdf

編輯:原來的答案被刪除,由於,我相信,以我通過鏈接到完整的答案引用的事實。此編輯添加完整的實現細節作爲此答案的一部分。我希望這個答案不會太長。

Xcode的集成與搜索引擎的消息

在WWDC 2014屆415,持續集成和Xcode 6,蘋果展示了集成的Xcode機器人與消息應用程序通過定製集成觸發。更具體地說,從該會話視頻的23分鐘標記開始(https://developer.apple.com/videos/play/wwdc2014-415/),Apple展示了使用集成觸發器與消息一起使用以接收構建服務器上的集成狀態。此外,通過使用虛擬聊天室成員Jeeves,他們展示了直接從Messages應用程序開始集成的能力。以下文章提供了重現該功能的分步說明。

客戶端和服務器的配置

要開始,這裏的客戶端和服務器的配置,我曾經模仿佔道功能:

客戶 OS X版本10.11(埃爾卡皮坦),Xcode 7.0.1

服務器 OS X版本10.11(El Capitan),OS X Server 5.0.4,Xcode 7.0.1, Ruby 2.0.0p645

網絡 對於我的開發和持續集成,我使用內部網絡。我的OS X服務器位於domain.local,而我的開發機器是同一內部網絡上的另一個節點。無論您使用的是內部服務器還是外部服務器,下面的說明都應該有效。

的Jabber - 消息

Jabber的的基礎是一個開源協議例如消息的原始名稱。 Jabber更名爲可擴展消息和呈現協議(XMPP)。 OS X Messages應用程序是使用Jabber核心構建的。

我們將在這項工作中廣泛使用Jabber(消息),因此我們確保它已開啓。從OS X Server應用程序中,選擇服務>消息視圖,然後切換右上角的消息。對於佔道經營,是我使用的消息服務設置如下:

Messages service settings

從終端窗口中你的服務器上,如果你想檢查的Jabber特定的設置,使用

$ sudo serveradmin settings jabber 

注特別是jabberClientPortTLS(5222)和jabberClientPortSSL(5223)值。這些是您的服務器上用於與Jabber服務進行通信的端口。

我們將使用Ruby編寫Jeeves的大部分腳本,我們需要一個XMPP/Jabber庫來完成這個任務。從你的服務器上的一個終端窗口,使用

$ gem install xmpp4r 

對Jabber的服務建立用戶

因爲我的服務器是本地服務器,而無需任何開發者帳戶上安裝XMPP4R(一個XMPP/Jabber的庫紅寶石)它,我需要爲各種開發人員創建帳戶以登錄到Jabber。您可能需要或不需要此步驟,具體取決於您的服務器是否已定義用戶帳戶。

從您的服務器上的OS X Server應用程序,轉到帳戶>用戶列表,併爲每個將使用虛擬Jeeves助理的客戶端添加新用戶。一定要爲Jeeves創建一個新用戶。對於用戶'Tom',這裏是使用的設置。確保爲每個用戶創建一個電子郵件地址,但郵件服務不需要運行。這些電子郵件地址將用於從客戶端的消息應用程序登錄到Jabber服務。

User settings

登錄的Jabber客戶端從開發機

與您的服務器上定義的用戶帳戶,它現在是時候登錄到您的客戶端機器的Jabber帳號。在客戶端的消息應用程序中,轉到消息>首選項>帳戶。選擇左下方的+號,選擇「其他信息帳戶...」,然後按繼續。在「添加郵件帳戶」對話框中,爲帳戶類型選擇Jabber,然後填寫用戶的憑證信息。這裏是我使用的設置:

enter image description here

(注意:使用SSL啓用後,該端口(5223)您的服務器上檢查的Jabber服務的設置,當你前面列出的jabberClientPortSSL值相匹配。)

成功登錄到Jabber服務後,您可以選擇在Jabber帳戶的「聊天設置」頁面下更改您的帳戶暱稱。所有其他默認設置都可以保持原樣。

創建聊天室

我們希望所有的機器人集成狀態和溝通,我們的虛擬助理,佔道經營,要通過信息的聊天室。聊天室允許小組溝通,但您不需要邀請加入。要創建聊天室,請執行以下操作。

從郵件中,選擇「文件」>「轉到聊天室」。您應該看到您登錄到列出的Jabber服務的帳戶。鍵入房間名稱的集成@房間..本地,然後選擇執行。 (請注意,我發現「rooms..local」 .COM需要一個聊天室'>。如果使用其他字比‘房間’不會創建聊天室。)

Create chat room

配置服務器網站服務

當積分從Xcode中的客戶機上運行開始時,前,後集成腳本通過進行HTTP調用對OS X服務器網站服務文件與Jabber服務進行通信。您必須配置OS X Server網站服務來處理這些調用。

您需要修改非SSL http(端口80)站點的設置。這是我使用的設置。

Web server settings

選擇端口80網站,並選擇鉛筆圖標下方,使您的設置進行匹配。

Web server settings

選擇「編輯高級設置...」,並設置符合這些。 (啓用「允許CGI的執行......」可使Ruby腳本執行。)

Web server settings

最後,你需要能夠實現特定的文件(message_room - 我們將在後面討論)被配置爲運行作爲Ruby腳本。爲此,請放置以下內容。htaccess文件在您的Web服務器的默認主文件夾(通常是/ Library/Server/Web/Data/Sites/Default)。

Options +ExecCGI 
<FilesMatch message_room$> 
    SetHandler cgi-script 
</FilesMatch> 

注:以下所有Ruby腳本的,你需要修改的變量只是下每個腳本的「資格證書」的評論,以配合您的域名和登錄證書。

前和後的整合腳本 當我們我們的客戶機上開始從Xcode的集成,我們希望將消息發送到Jabber集成聊天室,以便聊天室的所有成員都可以通知整合已經開始(並完成)。在Xcode的機器人觸發器頁面上,將以下預集成前後腳本添加到項目的bot中。

這是預集成觸發腳本:

#!/usr/bin/env ruby 
require 'json' 
require 'net/http' 
require 'uri' 

# ------------------------------------------------------------------------------------- 
# credentials and such 
domain = "<yourDomain>.local" 

# ------------------------------------------------------------------------------------- 
# our messaging endpoint 
uri = URI.parse("http://#{domain}:80/message_room") 

# ------------------------------------------------------------------------------------- 
# what we want to say 
message = "#{ENV['XCS_BOT_NAME']} integration #{ENV['XCS_INTEGRATION_NUMBER']} is now starting." 

# ------------------------------------------------------------------------------------- 
# build up the request body 
reqBody = {:message => message} 
body = JSON.generate(reqBody) 

# ------------------------------------------------------------------------------------- 
# the connect type 
http = Net::HTTP.new(uri.host, uri.port) 

# ------------------------------------------------------------------------------------- 
# build up the request 
request = Net::HTTP::Post.new(uri.request_uri) 
request.add_field('Content-type', 'application/json') 
request.body = body 

# ------------------------------------------------------------------------------------- 
# send the request and get the response 
response = http.request(request) 

這是整合後觸發腳本:

#!/usr/bin/env ruby 
require 'json' 
require 'net/http' 
require 'uri' 

# ------------------------------------------------------------------------------------- 
# credentials and such 
domain = "<yourDomain>.local" 

# ------------------------------------------------------------------------------------- 
# our messaging endpoint 
uri = URI.parse("http://#{domain}:80/message_room") 

# ------------------------------------------------------------------------------------- 
# what we want to say 
integrationResult = case ENV['XCS_INTEGRATION_RESULT'] 
    when "succeeded" 
     "has completed successfully." 
    when "test-failures" 
     tc = ENV['XCS_TEST_FAILURE_COUNT'].to_i 
     "completed with #{tc} failing #{(tc ==1) ? 'test' : 'tests'}." 
    when "build-errors" 
     ec = ENV['XCS_ERROR_COUNT'].to_i 
     "failed with #{ec} build #{(ec == 1) ? 'error' : 'errors'}." 
    when "warnings" 
     wc = ENV['XCS_WARNING_COUNT'].to_i 
     "completed with #{wc} #{(wc == 1) ? 'warning' : 'warnings'}." 
    when "analyzer-warnings" 
     ic = ENV['XCS_ANALYZER_WARNING_COUNT'].to_i 
     "completed with #{ic} static analysis #{(ic == 1) ? 'issue' : 'issues'}." 
    when "trigger-error" 
     "failed running trigger script." 
    when "checkout-error" 
     "failed to checkout from source control." 
    else 
     "failed with unexpected errors." 
    end 

message = "#{ENV['XCS_BOT_NAME']} integration #{ENV['XCS_INTEGRATION_NUMBER']} #{integrationResult}" 

# ------------------------------------------------------------------------------------- 
# build up the request body 
reqBody = {:message => message} 
body = JSON.generate(reqBody) 

# ------------------------------------------------------------------------------------- 
# the connect type 
http = Net::HTTP.new(uri.host, uri.port) 

# ------------------------------------------------------------------------------------- 
# build up the request 
request = Net::HTTP::Post.new(uri.request_uri) 
request.add_field('Content-type', 'application/json') 
request.body = body 

# ------------------------------------------------------------------------------------- 
# send the request and get the response 
response = http.request(request) 

前兩個的Ruby腳本撥打電話給居住在message_room文件您的OS X服務器網站主文件夾(通常是/ Library/Server/Web/Data/Sites/Default)。將以下message_room文件放入該文件夾中。

#!/usr/bin/env ruby 
require 'cgi' 
require 'json' 
require 'xmpp4r' 
require 'xmpp4r/muc' 

# ------------------------------------------------------------------------------------- 
# credentials and such 
domain = "<domain>.local" 
userId = "[email protected]#{domain}" 
userPw = "<jeevesAccountPassword>" 
roomName = "[email protected]#{domain}" 

# ------------------------------------------------------------------------------------- 
# header sent back 
cgi = CGI.new 
puts cgi.header("type" => "text/html", "status" => "OK") 

# ------------------------------------------------------------------------------------- 
# get the message out of the json formatted text 
keyValue = JSON.parse(cgi.params.keys.first) 
key = "message" 
value = keyValue[key] puts value 

# ------------------------------------------------------------------------------------- 
# create the message to the iChat (jabber) room 
fromJID = Jabber::JID.new(userId) 
jabberClient = Jabber::Client.new(fromJID) 
jabberClient.connect 
jabberClient.auth(userPw) 
jabberClient.send(Jabber::Presence.new.set_type(:available)) 

# ------------------------------------------------------------------------------------- 
# send the message to a chat room 
roomID = roomName + "/" + jabberClient.jid.node 
roomJID = Jabber::JID::new(roomID) 
room = Jabber::MUC::MUCClient.new(jabberClient) room.join(roomJID) 
roomMessage = Jabber::Message.new(roomJID, value) room.send(roomMessage) 

從消息應用程序

開始集成我們希望能夠從消息應用程序中發出我們的虛擬助理佔道指令。我們要支持三條指令:

  1. 佔道,天氣#得到當前的天氣(W/O 拉鍊默認爲庫比蒂諾)

  2. 佔道經營,一體化(BOT名稱)#開始整合對於給定的 博特

  3. 佔道經營,你的OS X Server上退出#關機佔道

以下文件將放置在OS X Server網站的默認文件夾(通常爲/ Library/Server/Web/Data/Sites/Default)中。

處理虛擬助手Jeeves的主要文件是jeevesManager.rb。通過輸入

$ ruby ./jeevesManager.rb 

從服務器上的網站默認文件夾中啓動該文件來喚醒Jeeves。

#!/usr/bin/env ruby 
require 'xmpp4r' 
require 'xmpp4r/muc' 
require 'xmpp4r/delay' 
require './jeevesWeather.rb' 
require './jeevesIntegration.rb' 

# ------------------------------------------------------------------------------------- 
# credentials and such 
domain = "<domain>.local" 
userId = "[email protected]#{domain}" 
userPw = "<jeevesAccountPassword>" 
roomName = "[email protected]#{domain}" 
defaultWeatherZipCode = "95015" 

# ------------------------------------------------------------------------------------- 
# create the client we'll use 
fromJID = Jabber::JID.new(userId) 
jabberClient = Jabber::Client.new(fromJID) 
jabberClient.connect 
jabberClient.auth(userPw) 
jabberClient.send(Jabber::Presence.new.set_type(:available)) 

# ------------------------------------------------------------------------------------- 
# connect to the chatroom 
roomID = roomName + "/" + jabberClient.jid.node 
roomJID = Jabber::JID::new(roomID) 
room = Jabber::MUC::MUCClient.new(jabberClient) room.join(roomJID) 

# ------------------------------------------------------------------------------------- 
# weather 
def getWeather(m) 
    begin 
     words = m.body.downcase.split("weather") 
     where = defaultWeatherZipCode 
     if (words.length == 2) 
      where = words[1].strip 
     end 
     weather = get_weather_for_city(where,'f') 
    rescue 
     weather = "Couldn't get weather for that location - try zip code" 
    end 
    return weather 
end 

# ------------------------------------------------------------------------------------- 
# integration 
def startIntegration(m) 
    begin 
     words = m.body.split("integrate") 
     botName = "Invalid BOT Name" 
     if (words.length == 2) 
      botName = words[1].strip 
     end 
     integrationMessage = jeevesIntegration(botName) 
    rescue 
     integrationMessage = "Failed integrating #{botName}" 
    end 
    return integrationMessage 
end 

# ------------------------------------------------------------------------------------- 
# listen for messages in chatroom (this callback will run in a separate thread) 
room.add_message_callback do |m| 
    if (m.x.nil?) # the msg is current 
     if m.type != :error 
      body = m.body; 
      if (body.downcase.include? "jeeves") 

       # assume Jeeves does not understand command 
       understood = 0 

       # exit Jeeves 
       if (body.downcase.include? "exit") 
        understood = 1 
        message = "Good-bye" 
        mainthread.wakeup 
       end 

       # Weather 
       if (body.downcase.include? "weather") 
        understood = 1 
        message = getWeather(m) 
       end 

       # Integrate BOT 
       if (body.downcase.include? "integrate") 
        understood = 1 
        message = startIntegration(m) 
       end 

       # Jeeves doesn't understand command 
       if (understood == 0) 
        message = "I don't understand that command!" 
       end 

       # let user know what has happened 
       roomMessage = Jabber::Message.new(roomJID, message) 
       room.send(roomMessage) 
      end 
     end 
    end 
end 


# ------------------------------------------------------------------------------------- 
# add the callback to respond to server ping (to keep the connect alive) 
jabberClient.add_iq_callback do |iq_received| 
    if iq_received.type == :get 
     if iq_received.queryns.to_s != 'http://jabber.org/protocol/disco#info' 
      iq = Jabber::Iq.new(:result, jabberClient.jid.node) 
      iq.id = iq_received.id 
      iq.from = iq_received.to 
      iq.to = iq_received.from 
      jabberClient.send(iq) 
     end 
    end 
end 

# ------------------------------------------------------------------------------------- 
# stop the main thread (the call back will still be alive this way) 
print "Connected to chat room...\n" 
Thread.stop 
print "Disconnected from chat room...\n" 

# leave chat room and log out of Jabber 
room.exit 
jabberClient.close 

上面的Jeeves管理器文件使用了兩個其他補充文件。下面的第一個處理獲取天氣預報和格式化,第二個處理開始集成。

######### Weather ######### 
require 'rexml/document' 
require 'open-uri' 
require 'net/smtp' 

# ------------------------------------------------------------------------------------- 
# yahoo weather url info 
# http://developer.yahoo.net/weather/#examples 

# ------------------------------------------------------------------------------------- 
#Returns a hash containing the location and temperature information 
#Accepts US zip codes or Yahoo location id's 
def yahoo_weather_query(loc_id, units) 
    h = {} 
    open("http://xml.weather.yahoo.com/forecastrss?p=#{loc_id}&u=#{units}") do |http| 
    response = http.read 
    doc = REXML::Document.new(response) 
    root = doc.root 
    channel = root.elements['channel'] 
    location = channel.elements['yweather:location'] 
    h[:city] = location.attributes["city"] 
    h[:region] = location.attributes["region"] 
    h[:country] = location.attributes["country"] 
    h[:temp] = channel.elements["item"].elements["yweather:condition"].attributes["temp"]   
    h[:text] = channel.elements["item"].elements["yweather:condition"].attributes["text"] 
    h[:wind_speed] = channel.elements['yweather:wind'].attributes['speed'] 
    h[:humidity] = channel.elements['yweather:atmosphere'].attributes['humidity'] 
    h[:sunrise] = channel.elements['yweather:astronomy'].attributes['sunrise'] 
    h[:sunset] = channel.elements['yweather:astronomy'].attributes['sunset'] 
    h[:forecast_low] = channel.elements["item"].elements['yweather:forecast'].attributes['low'] 
    h[:forecast_high] = channel.elements["item"].elements['yweather:forecast'].attributes['high'] end 
    return h 
end 

# ------------------------------------------------------------------------------------- 
def get_weather_for_city(city_code,units) 
    weather_info = yahoo_weather_query(city_code, units) 
    city = weather_info[:city] 
    region = weather_info[:region] 
    country = weather_info[:country] 
    temp = weather_info[:temp] 
    wind_speed = weather_info[:wind_speed] 
    humidity = weather_info[:humidity] 
    text = weather_info[:text] 
    sunrise = weather_info[:sunrise] 
    sunset = weather_info[:sunset] 
    forecast_low = weather_info[:forecast_low] 
    forecast_high = weather_info[:forecast_high] 

    return "#{city}, #{region}:\n" + " Currently #{temp} degrees, #{humidity}% humidity, #{wind_speed} mph winds, #{text}.\n" + " Forecast: #{forecast_low} low, #{forecast_high} high.\n" + " Sunrise: #{sunrise}, sunset: #{sunset}.\n" 
end 

最後,這是揭開序幕從消息應用程序

require 'json' 
require 'open-uri' 
require 'openssl' 

# ------------------------------------------------------------------------------------- 
def jeevesIntegration(botToIntegrate) 

    # credentials 
    domain = "<domain>.local" 
    endpoint = "https://#{domain}:20343" 
    user = "your-integration-username (not Jeeves)" 
    password = "password" 

    # return message 
    message = "Bot '#{botToIntegrate}' does not exist on server #{domain}" 

    # request JSON construct with all the BOTS 
    botsRequestURI = URI.parse("#{endpoint}/api/bots") 
    output = open(botsRequestURI, {ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE}) 
    bots = JSON.parse(output.readlines.join("")) 

    # loop through full list of BOTS for the one we're interested in 
    bots['results'].each do |bot| 
     botName = bot['name'] 
     if (botName.downcase == botToIntegrate.downcase) 
      botID = bot['_id'] 

      # curl -k -X POST -u "#{user}:#{password}" "#{endpoint}/api/bots/#{botid}/integrations" -i 

      # ------------------------------------------------------------------- 
      # kickoff integration 
      uri = URI.parse(endpoint) 
      http = Net::HTTP.new(uri.host, uri.port) 
      http.use_ssl = true 
      http.verify_mode = OpenSSL::SSL::VERIFY_NONE 
      request = Net::HTTP::Post.new("/api/bots/#{botID}/integrations") 
      request.basic_auth(user, password) 
      response = http.request(request) 
      message = "Integrating #{botName} on server #{domain}" 
     end 
    end 

    return message 
end 
1

我想補充後收到GitHub上的鉤子上收到任何承諾應我的OS X服務器上啓動Xcode的機器人。

如果你想'建立在提交'上,那麼只需在創建機器人時選擇該選項。您可以選擇手動運行機器人,定期提交。後者是你所描述的。只要你的一個團隊成員對你的github repo進行了修改,Xcode服務器就會進行構建。

+0

注意,「凱明」上提交不啓動集成腳本,它會檢查每5分鐘,看是否有提交被推送,然後執行機器人。爲了在提交時獲得實際的集成,你需要一個類似於GitHub的鉤子,並讓該鉤子通過腳本手動運行。 – 2015-05-25 19:12:57

2

是的,由於我是answered here,您首先需要找出機器人_id,然後發送一個POST請求到機器人的端點。詳情請參閱鏈接。