-
-
Save jewilmeer/1978838 to your computer and use it in GitHub Desktop.
Fapi
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/ruby | |
require 'net/http' | |
require 'net/https' | |
require 'base64' | |
require 'uri' | |
require 'yaml' | |
require 'cgi' | |
class Net::HTTP | |
alias_method :old_initialize, :initialize | |
def initialize(*args) | |
old_initialize(*args) | |
@ssl_context = OpenSSL::SSL::SSLContext.new | |
@ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE | |
end | |
end | |
module Configuration | |
def config_file | |
"#{ENV['HOME']}/.fapirc" | |
end | |
def init args | |
return false unless args.length == 2 | |
puts "storing credentials" | |
update_configuration(:username => args[0], :password => args[1]) | |
true | |
end | |
def configuration | |
return @configuration if @configuration | |
@configuration = {} | |
@configuration = YAML.load_file(config_file) if File.exists? config_file | |
@configuration | |
end | |
def update_configuration attributes | |
@configuration = configuration.merge(attributes) | |
File.open(config_file, "w") { |f| f.write(@configuration.to_yaml) } | |
end | |
def map args | |
return false unless args.length == 2 | |
configuration[:mappings] ||= {} | |
configuration[:mappings][repository] ||= {} | |
configuration[:mappings][repository][args[0]] = args[1] | |
update_configuration({}) | |
puts %{"#{args[0]}" => "#{args[1]}"} | |
true | |
end | |
def control args | |
return false unless args.length == 1 | |
update_configuration(:control_center => args[0]) | |
puts %{control center set to: #{args[0]}} | |
true | |
end | |
def softfab_configuration | |
return @softfab_configuration if @softfab_configuration | |
return nil unless configuration[:mappings] and configuration[:mappings][repository] | |
mappings = configuration[:mappings][repository] | |
mappings.each do |pattern, configuration_name| | |
match_pattern = pattern.gsub("*", "(.*)") | |
if branch.match(match_pattern) | |
@softfab_configuration = configuration_name | |
end | |
end | |
@softfab_configuration | |
end | |
end | |
module Watches | |
def watch_file | |
"#{ENV['HOME']}/.fapi-watch" | |
end | |
def watch_list | |
watch_list = [] | |
watch_list = YAML.load_file(watch_file)[:list] if File.exists? watch_file | |
watch_list ||= [] | |
watch_list | |
end | |
def write_watch_list list | |
File.open(watch_file, "w") { |f| f.write({ :list => list }.to_yaml) } | |
end | |
def add_to_watch_list job_id | |
list = watch_list | |
list << { "job" => job_id, "repository" => repository, "branch" => branch } | |
write_watch_list list | |
end | |
end | |
module Git | |
def remote | |
@remote ||= `git remote -v | grep \\(push\\)`.split[1] | |
end | |
def repository | |
@repository ||= remote.split("/")[-1] | |
end | |
def branch | |
@branch ||= `git branch | grep \\*`.split[1] | |
end | |
def branch_changes | |
@branch_changes ||= `git status --short --porcelain`.split("\n") | |
end | |
end | |
module SoftfabCommunication | |
def form_encode data | |
params = [] | |
data.each do |field, value| | |
params.push "#{CGI::escape field}=#{CGI::escape value}" | |
end | |
params.join "&" | |
end | |
def softfab_login | |
uri = URI.parse("#{configuration[:control_center]}/Login") | |
headers = { | |
'Content-Type' => "application/x-www-form-urlencoded" | |
} | |
data = { | |
"loginname" => configuration[:username], | |
"loginpass" => configuration[:password], | |
"url" => "Home" | |
} | |
http = Net::HTTP.new(uri.host, uri.port) | |
http.use_ssl = true if uri.scheme == "https" | |
res = http.post(uri.path, form_encode(data), headers) | |
res.response['set-cookie'] | |
end | |
def fetch_configuration_options(config, session) | |
uri = URI.parse("#{configuration[:control_center]}/BatchExecute") | |
headers = { | |
'Cookie' => session | |
} | |
data = { | |
"sel" => config | |
} | |
http = Net::HTTP.new(uri.host, uri.port) | |
http.use_ssl = true if uri.scheme == "https" | |
res = http.get("#{uri.path}?#{form_encode data}", headers) | |
form_contents = /<form([^>]+)>(.*)<\/form>/.match(res.body.gsub("\n", ""))[2] | |
results = {} | |
form_contents.scan(/<input([^>]*)>/) do |hit| | |
name = /name="([^"]*)"/.match(hit[0])[1] | |
value = /value="([^"]*)"/.match(hit[0])[1] | |
results[name] = value | |
end | |
results | |
end | |
def parse_job_status xml_contents | |
xml_contents = xml_contents.gsub("\\\"", "\"") | |
task_results = [] | |
task_exec_states = [] | |
xml_contents.scan(/<task([^>]+)>/) do |task| | |
state = /execstate="([^"]*)"/.match(task[0]) | |
task_exec_states.push(state[1]) if state | |
result = /result="([^"]*)"/.match(task[0]) | |
task_results.push(result[1]) if result | |
end | |
task_results.uniq! | |
task_exec_states.uniq! | |
unfinished_states = (task_exec_states - ["done", "cancelled"]) | |
return "pending" unless unfinished_states.empty? | |
return "ok" if task_results == ["ok"] | |
return "cancelled" if task_results.include? "cancelled" | |
return "error" if task_results.include? "error" | |
return "warning" if task_results.include? "warning" | |
"pending" | |
end | |
def execute_softfab_configuration(config, session) | |
uri = URI.parse("#{configuration[:control_center]}/BatchExecute") | |
headers = { | |
'Cookie' => session, | |
'Content-Type' => "application/x-www-form-urlencoded" | |
} | |
http = Net::HTTP.new(uri.host, uri.port) | |
http.use_ssl = true if uri.scheme == "https" | |
res = http.post(uri.path, form_encode(config), headers) | |
res.response['location'] | |
end | |
def fetch_running_jobs username, session, watch_list | |
uri = URI.parse("#{configuration[:control_center]}/UserDetails") | |
headers = { | |
'Cookie' => session | |
} | |
data = { | |
"user" => username | |
} | |
http = Net::HTTP.new(uri.host, uri.port) | |
http.use_ssl = true if uri.scheme == "https" | |
res = http.get("#{uri.path}?#{form_encode data}", headers) | |
jobs = watch_list | |
included_jobs = watch_list.map { |e| e["job"] } | |
job_match = /<tbody id="jobs">(.*)<\/tbody>/.match(res.body.gsub("\n", "")) | |
if job_match and recent_jobs = job_match[1] | |
recent_jobs.split("<tr").each do |row| | |
row.scan(/class="busy">.+<a href="ShowReport([^<]*)<\/a>/) do |hit| | |
job = /jobId=([^"\\]*)/.match(hit[0])[1] | |
unless included_jobs.include? job | |
name = />(.*)/.match(hit[0])[1] | |
new_watch = {"job" => job, "repository" => name, "branch" => username } | |
jobs << new_watch | |
puts "added watch for: #{name} - #{job}" | |
end | |
end | |
end | |
end | |
jobs | |
end | |
end | |
module Notification | |
def notify_status watch, status | |
print Time.now.strftime "[%a %d %b %H:%M] " | |
puts "#{status}: #{watch["repository"]} - #{watch["branch"]}" | |
icon = case status | |
when "ok" then "laugh" | |
when "cancelled" then "surprise" | |
when "error" then "sick" | |
when "warning" then "crying" | |
end | |
icon_path = "/usr/share/icons/gnome/48x48/emotes/face-#{icon}.png" | |
# if linux | |
`notify-send "#{status} - #{watch["branch"]}" "#{watch["repository"]}" --icon=#{icon_path}` | |
play_sound status | |
end | |
def play_sound status | |
`paplay #{sound_path status}` if File.exists?(sound_path status) | |
end | |
def sound_path status | |
path = '/usr/share/sounds/freedesktop/stereo/' | |
path + { | |
"ok" => 'complete.oga', | |
"cancelled" => 'message.oga', | |
"error" => 'suspend-error.oga', | |
"warning" => 'suspend-error.oga' | |
}[status] | |
end | |
end | |
module Commands | |
def push args | |
unless softfab_configuration | |
puts %{could not find configuration for "#{repository}/#{branch}"} | |
return true | |
end | |
flags = args.collect { |flag| flag["--"] ? flag.gsub("--", "") : nil }.compact | |
if flags.include? "force" | |
unless branch_changes.empty? | |
puts "Your branch has the current uncommitted changes:" | |
branch_changes.each { |change| puts change } | |
return true | |
end | |
`git push origin #{branch}` | |
end | |
print Time.now.strftime "[%a %d %b %H:%M]" | |
puts %{triggering #{configuration[:control_center]} to execute "#{softfab_configuration}" for #{repository}/#{branch}} | |
session = softfab_login | |
configuration_options = fetch_configuration_options(softfab_configuration, session) | |
configuration_options.each do |key, value| | |
configuration_options[key] = branch if key["SCM_BRANCH"] | |
end | |
configuration_options["action"] = "Execute" | |
job_run_location = execute_softfab_configuration(configuration_options, session) | |
job_id = /jobId=([^&]+)/.match(job_run_location)[1] | |
add_to_watch_list job_id | |
puts "job-id: #{job_id}" | |
true | |
end | |
def serve args | |
flags = args.collect { |flag| flag["--"] ? flag.gsub("--", "") : nil }.compact | |
watch_all = flags.include? "auto-build" | |
print Time.now.strftime "[%a %d %b %H:%M] " | |
puts "watching executed jobs. Press Ctrl+C to exit" | |
uri = URI.parse("#{configuration[:control_center]}/GetJobInfo") | |
http = Net::HTTP.new(uri.host, uri.port) | |
http.use_ssl = true if uri.scheme == "https" | |
headers = { | |
"Authorization" => "Basic #{Base64.encode64("#{configuration[:username]}:#{configuration[:password]}")}" | |
} | |
session = watch_all ? softfab_login : nil | |
while true | |
begin | |
keep_watching = [] | |
watch_list.each do |watch| | |
result = http.get("#{uri.path}?jobId=#{watch["job"]}", headers) | |
job_status = parse_job_status result.body | |
if job_status == "pending" | |
keep_watching << watch | |
else | |
notify_status watch, job_status | |
end | |
end | |
if watch_all | |
keep_watching = fetch_running_jobs "svntrigger", session, keep_watching | |
end | |
write_watch_list keep_watching | |
sleep 5 | |
rescue Errno::ENETUNREACH | |
sleep 60 | |
end | |
end | |
rescue Interrupt => e | |
puts "exiting..." | |
return true | |
end | |
end | |
include Configuration, Git, Watches, Commands, SoftfabCommunication, Notification | |
command = ARGV.shift | |
known_commands = { | |
"init" => "initializes the config file in your home folder. usage: init username password", | |
"push" => "pushes the current branch as feature branch run to softfab. Use --force to push your latest commits to the server before running CI", | |
"map" => "link a branch pattern to a softfab configuration for the current repository. usage: map pattern-* configuration", | |
"control" => "set the url to the softfab control center. usage: control https://www.project.domain.com/softfab", | |
"serve" => "watch pushed jobs and notify on result. Use --auto-build to watch auto-build running jobs" | |
} | |
show_help = true | |
show_help = false if known_commands.has_key? command and send command.to_sym, ARGV | |
if show_help | |
puts "commands available:" | |
known_commands.keys.sort.each { |key| puts " #{"%-10s" % key}\t#{known_commands[key]}" } | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment