#!/usr/bin/env ruby require 'optparse' require 'tmpdir' require 'erb' require 'genZPK' require 'xmlsimple' include GenZPK # Initialize the hash to store the various command line options options = {} opt_parser = OptionParser.new do |opt| opt.banner = "Usage: genZPK -s SYSTEM -v ZPK_VERSION -n PACKAGE_NAME DIR" opt.separator "Initial configuration: genZPK --configure " opt.separator "" opt.on("-s","--system SYSTEM","which system should checks be performed for") do |system| options[:system] = system end opt.on("-v","--app-version ZPK_VERSION","version of zpk to create") do |version| options[:version] = version end opt.on("-n","--name PACKAGE_NAME","name of application to deploy") do |name| options[:name] = name end opt.on("-p","--pull", "run \"git pull origin master\" prior to packaging") do options[:pull] = true end opt.on("--configure","perform the initial configuration to setup checks prior to packaging") do GenZPK::doConfigure exit 0 end opt.on("--version", "prints the version of this application") do puts GenZPK::VERSION exit 0 end opt.on("-h","--help","help") do puts opt_parser end end # Parse options begin opt_parser.parse! rescue OptionParser::InvalidOption # The user goofed. Send themt ot he documentation. warn "Required parameters not set or invalid argument. Please run genZPK --help for more info." exit -1 end # Read in the working directory for the PHP code to be packaged appending a '/' if missing dir = String(ARGV[0]).end_with?('/') ? String(ARGV[0]) : String(ARGV[0]) + "/" # Ensure all the required conditions to run are true requireCondition File.exists?(File.expand_path '~/.genZPK/checks.xml'), "Unable to locate configuration files. Please run genZPK --configure" requireCondition (File.read(File.expand_path('~/.genZPK/checks.xml')).gsub(/\s+/, "") != ""), "Configuration empty. Please run genZPK --configure" requireCondition getZdpack.not_nil?, "unable to locate zdpack executable. Please check zend server installation at /usr/local/zend/" requireCondition dir.not_nil?,"Required parameters not set or invalid argument. Please run genZPK --help for more info." requireCondition options[:system].not_nil?,"Required parameters not set or invalid argument. Please run genZPK --help for more info." requireCondition options[:version].not_nil?,"Required parameters not set or invalid argument. Please run genZPK --help for more info." requireCondition options[:name].not_nil?,"Required parameters not set or invalid argument. Please run genZPK --help for more info." requireCondition File.exists?(dir), "Error: Directory \"#{dir}\" does not exist." # Read in the checks.xml file in using xml-simple. KeyAttr specifies that instead of using a zero-based array to # store sub-nodes, they should be loaded into a hash where the 'name' field is the key. Setting ForceArray to false # removes all arrays unless there are multiple nodes of the same name that KeyAttr doesn't apply to., configHash = XmlSimple.xml_in(File.expand_path( '~/.genZPK/checks.xml'), {'KeyAttr' => 'name', 'ForceArray'=>false}) # If the user specified to pull down new sorcce from git, change the directory and do. Exit on failure. if options[:pull] == true Dir.chdir dir do requireCondition system("git pull origin master"), "Unable to update code via git. Exiting..." end end # If the name key ixists in the hash, that means there is only a single # entry in it so the name of the system doesn't need to be specified. if configHash["system"].has_key? "name" # Ensure that the system specified on the command line matches the system in the config file. Exit otherwise. if not configHash["system"]["name"] == options[:system] warn "Could not find system \"#{options[:system]}\" in configuration. Please run genZPK --configure to setup checks for this system or verify the system name." exit -1 end # If configHash["system"]["checks"]["check"] isn't an array, there is only one check and an array consisting # of that checvk should be loaded into checks if configHash["system"]["checks"]["check"].is_a? Array checks = configHash["system"]["checks"]["check"] else checks = Array.new checks.push configHash["system"]["checks"]["check"] end # Check is any subdomains are setup if configHash["system"].has_key? "subdomains" # Same logic as above. Ensure than subdomainChecks is always an array. if configHash["system"]["subdomains"]["file"].is_a? Array subdomainChecks = configHash["system"]["subdomains"]["file"] else subdomainChecks = Array.new subdomainChecks.push configHash["system"]["subdomains"]["file"] end else # If there aren't any defined, set it to nil. subdomainChecks = nil end else # There are multiple systems configured # Check to make sure system exists, exit otherwise. if not configHash["system"].has_key? options[:system] warn "Could not find system \"#{options[:system]}\" in configuration. Please run genZPK --configure to setup checks for this system or verify the system name." exit -1 end # Ensure checks is an array if there is only a single check. if configHash["system"][options[:system]]["checks"]["check"].is_a? Array checks = configHash["system"][options[:system]]["checks"]["check"] else checks = Array.new checks.push configHash["system"][options[:system]]["checks"]["check"] end # If there are subdomain checks defined, make sure they are saved as an array, otherwise nil. if configHash["system"][options[:system]].has_key? "subdomains" if configHash["system"][options[:system]]["subdomains"]["file"].is_a? Array subdomainChecks = configHash["system"][options[:system]]["subdomains"]["file"] else subdomainChecks = Array.new subdomainChecks.push configHash["system"][options[:system]]["subdomains"]["file"] end else subdomainChecks = nil end end puts "Running checks" puts "-" * 80 successes = 0 # For every check for i in 0..(checks.size - 1) check = checks[i] desc = check["desc"] filepath = check["file"] matchval = check["value"] # Make sure regular expression is valid, exit otherwise. begin regex = /#{check["regex"]["content"]}/ rescue RegexpError => e puts "Invalid regular in configuration. Please run genZPK --configure\n#{e.message}" exit -1 end # match groups should be 1 indexed in the config file. Correct that here. matchgrp = Integer(check["regex"]["group"]) - 1 display = desc # Display an error if the file cannot be found and proceed to the next check if not File.exists?(dir + filepath) display << "." * (80 - "FAIL".size - desc.size) display << "FAIL\n" display << "\tFile not found: #{dir + filepath}" puts display puts "-" * 80 next end # Read in the contents of the file to check and scan it with the given regexp contents = File.read(dir + filepath) matches = contents.scan(regex) results = "" passes = 0 # For every match for i in 0..(matches.size - 1) match = matches[i] #Ensure the match is the right value and increment the number of passed matches if not match[matchgrp] == matchval results << "\t#{dir + filepath}\n" results << "\tValue found: #{match[matchgrp]}\n" results << "\tRequired value: #{matchval}\n\n" else passes += 1 end end # If ever match passed if passes == matches.size # Display a success display << "." * (80 - "SUCCESS".size - desc.size) display << "SUCCESS" puts display # and increment the number of successfl checks successes += 1 else # otherwise display that it failed and proceed to the next check display << "." * (80 - "FAIL".size - desc.size) display << "FAIL" puts display puts results.chomp end puts "-" * 80 end # If no all the checks passed, exit the program. if not successes == checks.size puts "#{successes} of #{checks.size} checks passed. Please check files listed above and try again." exit -1 end puts "All checks passed. Continuing..." puts "\n\n" if not subdomainChecks.nil? puts "Verifying subdomains" puts "-" * 80 successes = 0 # For every subdomain to check for i in 0..(subdomainChecks.size - 1) check = subdomainChecks[i] path = check["path"] display = dir + path # Ensure the file exists if File.exists?(dir + path) # read in its contents contents = File.read(dir + path) fieldPasses = 0 # For each field-value pair in this file check.each do |field, value| # make sure it isn't the file path if field != "path" # If the field is defined if not configHash["definitions"]["definition"][field].nil? # Make sure the regular expession is valid, otherwise exit. begin regex = /#{configHash["definitions"]["definition"][field]["regex"]["content"]}/ rescue RegexpError => e puts "Invalid regular in configuration. Please run genZPK --configure\n#{e.message}" exit -1 end # Offset the match group to be zero-indexed matchgrp = Integer(configHash["definitions"]["definition"][field]["regex"]["group"]) - 1 # scan the file for a match matches = contents.scan(regex) # If there are no matches, let the user know and continue to the next field if matches == [] if display.end_with? "\n" display << "\tField not found in file: #{field}\n" else display << "\n\tField not found in file: #{field}\n" end else # There were matches # Ensure the value is correct and if it isn't display an error and continue to the next field if matches[0][matchgrp] == value fieldPasses += 1 else if display.end_with? "\n" display << "\tField Failed: #{field}\n" display << "\t\tValue: #{matches[0][0]}\n" display << "\t\tDefined value: #{value}\n" else display << "\n\tField Failed: #{field}\n" display << "\t\tValue: #{matches[0][0]}\n" display << "\t\tDefined value: #{value}\n" end end end else # the field isn't defined. # Let the user know and continue to the next field if display.end_with? "\n" display << "\tField not defined: #{field}\n" else display << "\n\tField not defined: #{field}\n" end end end end # Check if all the fields passed and display a success. The number of passes should be one less # than all the fields since we excluded path. if fieldPasses == (check.size - 1) display << "." * (80 - "SUCCESS".size - display.size) display << "SUCCESS" puts display puts "-" * 80 successes += 1 else # one or more fields failed. Let the user know tmpString = "#{fieldPasses} of #{check.size - 1} fields verified" display << tmpString display << "." * (80 - "#{fieldPasses} of #{check.size - 1} fields verified".size - "FAIL".size) display << "FAIL" puts display puts "-" * 80 end else # the specified subdomain file couldn't be found. Display an error display << "." * (80 - "FAIL".size - display.size) display << "FAIL\n" display << "\tFile not found: #{dir + path}" puts display puts "-" * 80 end end #Check to see if all the subdomains passed. If they didn't, exit. if not successes == subdomainChecks.size puts "#{successes} of #{subdomainChecks.size} subdomains passed. Please check files listed above and try again." exit -1 end puts "All subdomains passed. Continuing...\n" end # Ensure the /scripts directory exists in the working directory if not File.exists? dir + "/scripts" FileUtils.mkpath dir + "/scripts" end # get the templates for the deployment configuration and pre-activation script #scriptTemplate = getTemplate 'pre_activate.php' deploymentTemplate = getTemplate 'deployment.xml' # These are used to populate the tamplates releaseName = options[:name] zpkVersion = options[:version] # Generate the files #script = ERB.new(scriptTemplate) deployment = ERB.new(deploymentTemplate) # Write out the files #File.open(dir + '/scripts/pre_activate.php', 'w') {|f| f.puts(script.result(binding))} File.open(dir + '/deployment.xml', 'w') {|f| f.puts(deployment.result(binding))} zpkFile = "#{options[:name]}.zpk" # Continue to increment the zpk suffix until an unused filename is found. while File.exists? zpkFile # Pull the base filename and suffix from the filename. matches = /(.*\.zpk)\.?(.*)?/.match(zpkFile) # if no match found for the suffix, start at zero if matches[2]=="" ver = 0 else ver = Integer(matches[2]) end # increment the suffix by 1 zpkFile = "#{matches[1]}.#{ver + 1}" end # Make a temporary directory to work in Dir.mktmpdir do |tempDir| # get the zdpack executable zdpack = getZdpack # temporarily remove the git repo from the working directory to prevent packaging it. system("mv #{dir}/.git #{tempDir}/") # package the zpk and output it to the temp directory system("#{zdpack} pack --output-dir=#{tempDir} #{dir}") # Move the git repository back and place the zpk in the current directory system("mv #{tempDir}/.git #{dir}/") system("mv #{tempDir}/#{options[:name]}.zpk ./#{zpkFile}") # remove the generated files from the working directory system("rm -r #{dir + '/scripts'}") system("rm #{dir + '/deployment.xml'}") end # Let the user know that nothing screwed up and it worked puts "Successfully created ZPK at ./#{zpkFile}"