Launch Xcode Bot integration manually?

I watched WWDC 2014 "Continuous Integration with Xcode," and it looks great how bots can be used to run a test. But my question is for anyone who has seen the video when he sends a message to Jeeves saying โ€œintegrate CoffeeBoard.โ€ Bot starts to integrate. I want to know how he did it.

I want to add a post-receive hook on github, which, when I get some kind of commit, should start the Xcode bot on my OS X Server. Most of my team members use SourceTree or GitHub to control their git, and they do not want to use Xcode Source Control. I thought that creating a bot and setting its ability to run manually would do the trick. I need to know: "Does OS X Server mean the choice, like some kind of URL that will launch the bot?"

Sorry if I'm not clear enough. But this is too confusing for me, as they have very little trigger documentation. And although he mentions this as a new new feature, they did not include any information to achieve this.

+5
source share
3 answers

The previous two answers do not exactly answer the original question of โ€œhow did they do itโ€ in order to launch bots from the Messages application.

I recreated the exact workflow and scripts needed to simulate the Jeeves virtual assistant to interact with bots (and to get the weather).

See the linked PDF for more details:

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

Edit: The original answer was deleted due to, I believe, the fact that I was linking to the full answer. This change adds full implementation details as part of this answer. Hope this is not too long for an SO answer.

Xcode bot integration with messaging

During the 415 WWDC 2014 "Continuous Integration with Xcode 6" session, Apple demonstrated the integration of Xcode bots with the Messages application using custom integration triggers. More specifically, starting from the 23-minute mark of this video session ( https://developer.apple.com/videos/play/wwdc2014-415/ ), Apple demonstrates the use of integration triggers in conjunction with messages to receive integration status on the build server. In addition, through the use of Jeeves' virtual chat member, they demonstrate the ability to start integration directly from the Messages application. The following article provides step-by-step instructions for reproducing this functionality.

Client and Server Configurations

To get started, here are the client and server configurations that I used to simulate Jeeves functionality:

Client OS X Version 10.11 (El Capitan), Xcode 7.0.1

Server OS X Version 10.11 (El Capitan), OS X Server 5.0.4, Xcode 7.0.1, Ruby 2.0.0p645

Network For my development and continuous integration I use an internal network. My OS X Server is in a .local domain, and my development machine is another node on the same internal network. The instructions below should work whether you are using an internal or external server.

Jabber - the basis of messages

Jabber is the source name of an open source protocol, for example, for messaging. Jabber has been renamed Extensible Messaging and Presence Protocol (XMPP). The OS X Messages application is built using Jabber in its core.

Make good use of Jabber (Messages) in this effort, so let's turn it on. In OS X Server, select Services> Messages and go to Messages in the upper right corner. For Jeeves, the message service settings I used are as follows:

Message Service Settings

In the terminal window on your server, if you want to check specific Jabber settings, use

$ sudo serveradmin settings jabber 

Note in particular the values โ€‹โ€‹of jabberClientPortTLS (5222) and jabberClientPortSSL (5223). These are the ports on your server that you will use to communicate with the Jabber service.

Having written most of the scripts for Jeeves well using Ruby, and for this you need the XMPP / Jabber library. In the terminal window on your server, install XMPP4R (XMPP / Jabber library for Ruby) using

 $ gem install xmpp4r 

Create Users for the Jabber Service

Since my Server is a local server without any developer accounts, I had to create accounts for different developers to log in to Jabber. This step may or may not be necessary if your server already has user accounts.

In the OS X Server application on your server, go to the Accounts> Users list and add a new user for each client that will use Jeeves' virtual assistant. Remember to create a new user for Jeeves. For user Tom, here are the settings that were used. Remember to create an email address for each user, but the Mail service should not start. These email addresses will be used to log in to the Jabber service from the Messages application on your client.

User settings

Logging in to Jabber from a customer development machine

With user accounts defined on your server, now is the time to log in to your Jabber account from your client computer. In the Messages application on your client, go to Messages> Preferences> Accounts. Select the + sign in the lower left, select "Other messages ......" and click "Continue." In the Add Messages dialog box, select Jabber for the type of account, and fill in the credentials for your users. Here are the settings I used:

enter image description here

(Note that with SSL enabled, port (5223) matches the jabberClientPortSSL value you specified earlier when checking the Jabber service settings on your server.)

After successfully logging in to Jabber, you can change your account alias on the Chat Settings page of your Jabber account. All other default settings can be left as is.

Create chat

We want all bot integration statuses and communication with our virtual assistant Jeeves to go through the message room. Chats allow you to communicate in groups, but you do not need an invitation to join. To create a chat room, follow these steps:

From the messages, choose File> Go To Chat Room. You should see the account that you are logged into the specified Jabber service. Enter @ rooms..local for the room name and select Go. (Note that I found that the chat should be "rooms..local.com>. Using a word other than" rooms "would not create a chat room.)

Create chat room

Configure server website service

When integration starts from Xcode running on your client machine, pre-integration and post-integration scripts interact with the Jabber service, creating an http call to the file in the OS X Server website service. You must configure the OS X Server Web site service to handle these calls.

You need to change the settings for the site without SSL http (port 80). Here are the settings I used.

Web server settings

Select the port 80 website and select the pencil icon at the bottom so that your settings match these settings.

Web server settings

Select "Change advanced settings ..." and make the appropriate settings. (Enabling "Allow CGI Execution ..." allows you to execute the Ruby script.)

Web server settings

Finally, you will need to include a specific file (message_room - well discussed later) so that it can be run as a Ruby script. To do this, put the following .htaccess file in the default home folder for web servers (usually / Library / Server / Web / Data / Sites / Default).

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

NOTE. In all of the following ruby โ€‹โ€‹scenarios, you will only need to change the variables under the "credentials" comment in each script to match your domain and login credentials.

Preliminary and post-integration scripts When we start integration with Xcode on our client machine, we want to send a message to the Jabber integration chat room so that all chat participants can be notified that the integration has started (and is finished). Add the following scripts before and after integration into your projects on the trigger bot page in Xcode.

This is a script pre-integration trigger:

 #!/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) 

This is the trigger after the script integration:

 #!/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) 

The previous two Ruby scripts call the message_room file, which is located in the home folder of the OS X Server website (usually / Library / Server / Web / Data / Sites / Default). Place the following message_room file in this folder.

 #!/usr/bin/env ruby require 'cgi' require 'json' require 'xmpp4r' require 'xmpp4r/muc' # ------------------------------------------------------------------------------------- # credentials and such domain = "<domain>.local" userId = " jeeves@ #{domain}" userPw = "<jeevesAccountPassword>" roomName = " integration@rooms. #{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) 

Starting integration from the Messages application

We want to be able to give instructions to our virtual assistant Jeeves from the Messages application. We will support three instructions:

  • Jeeves, weather # gets the current weather (excluding zip by default for Cupertino)

  • Jeeves, integration (bot name) # launches integration for this Bot

  • Jeeves, exit # shutdown Jeeves on your OS X server

The following files will be placed in the default folder for OS X Server websites (usually / Library / Server / Web / Data / Sites / Default).

The main file that the virtual assistant, Jeeves, processes is jeevesManager.rb. Run this file to wake Jeeves by typing

 $ ruby ./jeevesManager.rb 

from the default folder of websites on your server.

 #!/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 = " jeeves@ #{domain}" userPw = "<jeevesAccountPassword>" roomName = " integration@rooms. #{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 (mxnil?) # 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 

Two other additional files are used by the Jeeves manager file above. The first of them processes the weather forecast and formats it, and the second processes the start of integration.

 ######### 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 

Finally, this is a script that launches integration from the Messages application

 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 
+7
source

Yes, since I answered here , you first need to find out the _id bot, and then send a POST request to the bot endpoint. See the link for more details.

+2
source

I want to add a post-receive hook on github, which, when I receive some kind of commit, should run the Xcode bot on my OS X server.

If you want to "create a commit," simply select this option when creating the bot. You have the ability to run the bot in manual, periodic or in fix mode. The latter does what you describe. As soon as one of your team members makes changes to your github registry, the Xcode server will build.

+1
source

Source: https://habr.com/ru/post/1212795/


All Articles