|
#!/usr/bin/env ruby |
|
|
|
require 'yaml' |
|
require 'trollop' |
|
require 'fileutils' |
|
|
|
# Nested map of Stackato version and component name to installed location. |
|
# Short version tuples can be used as wildcards. If a longer tuple is |
|
# defined here and it also matches the given version, it will win over the |
|
# shorter match. |
|
|
|
REPO_MAP = { |
|
"2" => { |
|
"stackato" => "/s/", |
|
|
|
"aok" => "/s/aok/", |
|
|
|
"kato" => "/s/kato/", |
|
|
|
"cloud_controller" => "/s/vcap/cloud_controller/", |
|
"dea" => "/s/vcap/dea/", |
|
"fence" => "/s/vcap/fence/", |
|
"stackato-router" => "/s/vcap/stackato-router/", |
|
"vcap-staging" => "/s/vcap/staging/", |
|
}, |
|
"3" => { |
|
"stackato" => "/s/", |
|
|
|
"kato" => "/s/kato/", |
|
"sentinel" => "/s/code/sentinel/", |
|
|
|
"aok" => "/s/code/aok", |
|
"cloud_controller_ng" => "/s/code/cloud_controller_ng", |
|
"console" => "/s/code/console", |
|
"dea_ng" => "/s/code/dea_ng", |
|
"fence" => "/s/code/fence", |
|
"health_manager" => "/s/code/health_manager", |
|
"stackato-rest" => "/s/code/stackato-rest", |
|
"stackato-router" => "/s/code/stackato-router", |
|
"vcap-common" => "/s/code/common", |
|
|
|
"base" => "/s/code/services/base", |
|
"filesystem" => "/s/code/services/filesystem", |
|
"harbor" => "/s/code/services/harbor", |
|
"memcached" => "/s/code/services/memcached", |
|
"mongodb" => "/s/code/services/mongodb", |
|
"mysql" => "/s/code/services/mysql", |
|
"postgresql" => "/s/code/services/postgresql", |
|
"rabbitmq" => "/s/code/services/rabbitmq", |
|
"redis" => "/s/code/services/redis", |
|
"elasticsearch" => "/s/code/services/elasticsearch", |
|
"stackato-service-postgresql" => "/s/code/services/postgresql", |
|
} |
|
} |
|
|
|
# Versions which compare such that "n.0" > "n". This allows more specific |
|
# version comparisons to rank higher than shorter generic ones. |
|
|
|
class Version < Array |
|
def initialize s |
|
super(s.split(".").map { |e| e.to_i }) |
|
end |
|
def < x |
|
(self <=> x) < 0 |
|
end |
|
def > x |
|
(self <=> x) > 0 |
|
end |
|
def == x |
|
(self <=> x) == 0 |
|
end |
|
end |
|
|
|
opts = Trollop::options do |
|
banner 'kato patch generation' |
|
opt :from, "Commit from which to patch (the patch will not contain this commit)", :type => String |
|
opt :to, "Commit to which to patch", :type => String |
|
opt :commit, "Commit which contains the patch", :type => String |
|
opt :repo, "Full path to the repo", :type => String |
|
opt :stackato_version, "stackato version for this patch", :type => String |
|
opt :name, "Name for this patch", :type => String |
|
opt :get_stackato_path, "Full path to your get-stackato.git repository", :type => String |
|
opt :root, "Root required to run the patch?" |
|
end |
|
|
|
def lookup_repo_map(stackato_version, repo_name) |
|
# Find the closest matching version in the repo map and then look up the |
|
# specified name. Return nil on no matching version or no matching name. |
|
|
|
list = [] |
|
|
|
REPO_MAP.each do |version, dummy| |
|
list << version if Regexp.new("^#{Regexp.escape(version)}") =~ stackato_version |
|
end |
|
|
|
closest = list.sort.last |
|
|
|
return nil unless closest |
|
|
|
REPO_MAP[closest][repo_name] |
|
end |
|
|
|
stackato_version = opts[:stackato_version] |
|
repo = opts[:repo] |
|
|
|
Trollop::die :stackato-version, "must be provided" unless stackato_version |
|
Trollop::die :repo, "must be provided" unless repo |
|
|
|
repo_name = File.basename(repo) |
|
|
|
unless lookup_repo_map(stackato_version, repo_name) |
|
abort "unknown repo!" |
|
end |
|
|
|
md5_prog = nil |
|
uname = `uname`.strip |
|
if uname == "Darwin" |
|
md5_prog = "md5" |
|
elsif uname == "Linux" |
|
md5_prog = "md5sum" |
|
end |
|
|
|
patch = nil |
|
|
|
changed_files = {} |
|
|
|
Dir.chdir(repo) do |
|
if opts[:commit] |
|
patch = `git diff --no-prefix #{opts[:commit]}^1 #{opts[:commit]} 2>/dev/null` |
|
elsif opts[:from] && opts[:to] |
|
patch = `git diff --no-prefix #{opts[:from]} #{opts[:to]} 2>/dev/null` |
|
end |
|
|
|
patch = patch.split "\n" |
|
patch.map! do |line| |
|
next if line =~ /^diff/ |
|
next if line =~ /^index/ |
|
next if line =~ /^new file mode/ |
|
if line =~ /^[\+\-][\+\-][\+\-]/ |
|
delim, tail_path = line.split(' ', 2) |
|
if tail_path.strip == '/dev/null' |
|
line |
|
else |
|
path = File.join(lookup_repo_map(stackato_version, repo_name), tail_path) |
|
if delim == "---" |
|
prev_commit = opts[:from] ? opts[:from] : "#{opts[:commit]}^1" |
|
changed_files[path] = `git show #{prev_commit}:#{tail_path} 2>/dev/null | #{md5_prog} 2>/dev/null`.strip |
|
end |
|
"#{delim} #{path}" |
|
end |
|
else |
|
line |
|
end |
|
end |
|
|
|
patch.delete(nil) |
|
|
|
default_patch_options = "-f -N --reject-file - --quiet -p0" |
|
|
|
if opts[:root] |
|
patch.unshift "sudo patch $REVERSEOPT #{default_patch_options} << 'EOF'" |
|
else |
|
patch.unshift "patch $REVERSEOPT #{default_patch_options} << 'EOF'" |
|
end |
|
|
|
patch << "EOF" |
|
patch << nil |
|
|
|
patch = patch.join "\n" |
|
|
|
patch_header = <<'EOT' |
|
#!/bin/bash |
|
|
|
set -o errexit |
|
|
|
if [[ "$1" == "revert" ]]; then |
|
REVERSEOPT="--reverse" |
|
fi |
|
|
|
EOT |
|
|
|
patch = patch_header + patch |
|
end |
|
|
|
IO.write("patch.sh", patch) |
|
puts "patch.sh generated." |
|
|
|
patch_id = 1 |
|
|
|
version_path = nil |
|
if opts[:get_stackato_path] |
|
version_path = File.expand_path(File.join(opts[:get_stackato_path], "static", "kato-patch", opts[:stackato_version])) |
|
else |
|
version_path = File.expand_path(File.join(File.dirname(File.expand_path($0)), '..', '..', '..', opts[:stackato_version])) |
|
end |
|
|
|
if File.directory? version_path |
|
patch_list = Dir.entries(version_path) |
|
patch_list.delete('.') |
|
patch_list.delete('..') |
|
patch_list.delete('manifest.json') |
|
patch_id = patch_list.length + 1 |
|
end |
|
|
|
manifest = { |
|
"id" => patch_id, |
|
"name" => opts[:name], |
|
"stackato_version" => opts[:stackato_version], |
|
"description" => "", |
|
"roles_to_restart" => [], |
|
"severity" => "required", # or 'optional' |
|
# "changed_files" => changed_files, |
|
} |
|
|
|
IO.write("patch.yml", YAML.dump(manifest) ) |
|
puts "patch.yml generated. Make sure you edit it to add a description, edit the severity if necessary, and specify the roles to restart!" |
|
|
|
patch_path = File.join(version_path, opts[:name]) |
|
FileUtils.mkdir_p(patch_path) |
|
FileUtils.mv(["patch.sh", "patch.yml"], patch_path) |
|
|
|
puts "patch can be found in #{patch_path}." |