## # The jenkins tasks enable the packaging repo to kick off packaging builds on a # remote jenkins slave. They work in a similar way to the :remote tasks, but # with a few key differences. The jenkins tasks transmit information to a # jenkins coordinator, which handles the rest. The data passed are the # following: # # 1) $PROJECT_BUNDLE - a tar.gz of a git-bundle from HEAD of the current # project, which is cloned on the builder to set up a duplicate of this # environment # # 2) $BUILD_PROPERTIES - a build parameters file, containing all information about the build # # 3) $BUILD_TYPE - the "type" of build, e.g. rpm, deb, gem, etc The jenkins url and job name # are obtained via the team build-data file at # git@github.com/puppetlabs/build-data # # 4) $PROJECT - the project we're building, e.g. facter, puppet. This is used later in # determining the target for the build artifacts on the distribution server # # On the Jenkins end, the job is a parameterized job that accepts four # parameters. Jenkins has the Parameterized Trigger Plugin, Workspace Cleanup # Plugin, and Node and Label Parameter Plugin in use for this job. The # workspace cleanup plugin cleans the workspace before each build. Two are file # parameters, a string parameter, and a Label parameter provided by the Node # and Label Parameter Plugin, as described above. When th pl:jenkins:post task # triggers a build, it passes values for all of these parameters. The Label # parameter is associated with the build type. This way we can queue the job on # a builder with the appropriate capabilities just by assigning a builder the # label "deb" or "rpm," etc. The actual build itself is accomplished via a # shell build task. The contents of the task are: # ################# # # #!/bin/bash # # SHA=$(echo $BUILD_PROPERTIES | cut -d '.' -f1) # # echo "Build type: $BUILD_TYPE" # # ### Create a local clone of the git-bundle that was passed # # The bundle is a tarball, and since this is a project-agnostic # # job, we don't actually know what's in it, just that it's a # # git bundle. # # # # [ -f "PROJECT_BUNDLE" ] || exit 1 # mkdir project && tar -xzf PROJECT_BUNDLE -C project/ # # pushd project # git clone --recursive $(ls) git_repo # # pushd git_repo # # ### Install the packaging gem via Bundler # bundle install # # ### Perform the build # bundle exec rake pl:build_from_params PARAMS_FILE=$WORKSPACE/BUILD_PROPERTIES # # ### Send the results # bundle exec rake pl:jenkins:ship["artifacts"] PARAMS_FILE=$WORKSPACE/BUILD_PROPERTIES # # popd # popd # # ### Create the repositories from our project by trigger a downstream job # ### Because we can't trigger downstream with a File Parameter, we use curl # if [ "$BUILD_TYPE" = "rpm" ] || [ "$BUILD_TYPE" = "deb" ] ; then # curl -i -Fname=PROJECT_BUNDLE -Ffile0=@PROJECT_BUNDLE -FSubmit=Build -Fjson="{\"parameter\":[{\"name\":\"PROJECT_BUNDLE\",\"file\":\"file0\"}]}" \ # http://jenkins-release.delivery.puppetlabs.net/job/puppetlabs-packaging-repo-creation/build # fi # # ### If a downstream job was passed, trigger it now # if [ -n "$DOWNSTREAM_JOB" ] ; then # pushd project # pushd git_repo # bundle exec rake pl:jenkins:post["$DOWNSTREAM_JOB"] PARAMS_FILE=$WORKSPACE/BUILD_PROPERTIES # popd # popd # fi # ################# namespace :pl do namespace :jenkins do ## # Do the heavy lifting. This task generates the URL for the jenkins job and posts it. # It expects a the following arguments # 1. :build_task => The lower-level pl: or pe: task we're executing, e.g. pl:deb_all # task :post_build, :build_task do |t, args| # Check for a dirty tree before allowing a remote build that is doomed to unexpected results Pkg::Util::Git.fail_on_dirty_source # We use JSON for parsing the json part of the submission to JSON Pkg::Util.require_library_or_fail 'json' build_task = args.build_task ## # We set @:task of Pkg::Config manually with our task data so the remote # build knows what to do. Puppetdb needs early knowledge of if this is # a PE build, so we always this along as an environment variable task # argument if its the case. # Pkg::Config.task = { :task => build_task.to_s, :args => nil } Pkg::Config.task[:args] = ["PE_BUILD=true"] if @build_pe # # Determine the type of build we're doing to inform jenkins build_type = case build_task when /deb/ if Pkg::Config.default_cow.split('-')[1] =~ /cumulus/ "cumulus" else "deb" end when /mock/ then "rpm" when /dmg|apple/ then "dmg" when /gem/ then "gem" when /tar/ then "tar" else raise "Could not determine build type for #{build_task}" end # Create a string of metrics to send to Jenkins for data analysis dist = case build_type when /deb/ then Pkg::Config.default_cow.split('-')[1] when /rpm/ if Pkg::Config.pe_version Pkg::Config.final_mocks.split(' ')[0].split('-')[2] else Pkg::Config.final_mocks.split(' ')[0].split('-')[1..2].join("") end when /dmg/ then "apple" when /gem/ then "gem" when /sles/ then "sles" when /tar/ then "tar" else raise "Could not determine build type for #{build_task}" end if Pkg::Config.pe_version metrics = "#{ENV['USER']}~#{Pkg::Config.version}~#{Pkg::Config.pe_version}~#{dist}~#{Pkg::Config.team}" else metrics = "#{ENV['USER']}~#{Pkg::Config.version}~N/A~#{dist}~#{Pkg::Config.team}" end # # Create the data files to send to jenkins properties = Pkg::Config.config_to_yaml bundle = Pkg::Util::Git.bundle('HEAD') # Construct the parameters, which is an array of hashes we turn into JSON parameters = [{ "name" => "BUILD_PROPERTIES", "file" => "file0" }, { "name" => "PROJECT_BUNDLE", "file" => "file1" }, { "name" => "PROJECT", "value" => Pkg::Config.project.to_s }, { "name" => "BUILD_TYPE", "label" => build_type.to_s }, { "name" => "METRICS", "value" => metrics.to_s }] # Initialize the args array that will hold all of the arguments we pass # to the curl utility method. args = [] # If the environment variable "DOWNSTREAM_JOB" was passed, we want to # send this value to the build job as well, so it knows to trigger a # downstream job, and with what URI. if ENV['DOWNSTREAM_JOB'] parameters << { "name" => "DOWNSTREAM_JOB", "value" => ENV['DOWNSTREAM_JOB'] } args << ["-Fname=DOWNSTREAM_JOB", "-Fvalue=#{ENV['DOWNSTREAM_JOB']}"] end # Contruct the json string json = JSON.generate("parameter" => parameters) # Construct the remaining form arguments. For visual clarity, params that are tied # together are on the same line. # args << [ "-Fname=BUILD_PROPERTIES", "-Ffile0=@#{properties}", "-Fname=PROJECT_BUNDLE", "-Ffile1=@#{bundle}", "-Fname=PROJECT", "-Fvalue=#{Pkg::Config.project}", "-Fname=BUILD_TYPE", "-Fvalue=#{build_type}", "-Fname=METRICS", "-Fvalue=#{metrics}", "-FSubmit=Build", "-Fjson=#{json.to_json}", ] # We have several arrays inside args by now, flatten it up. args.flatten! # Construct the job url # job_url = "#{Pkg::Config.jenkins_build_host}/job/#{Pkg::Config.jenkins_packaging_job}" trigger_url = "#{job_url}/build" # Call out to the curl_form_data utility method in 00_utils.rake # begin _, retval = Pkg::Util::Net.curl_form_data(trigger_url, args) if Pkg::Util::Execution.success?(retval) puts "Build submitted. To view your build results, go to #{job_url}" puts "Your packages will be available at http://#{Pkg::Config.builds_server}/#{Pkg::Config.project}/#{Pkg::Config.ref}" else fail "An error occurred submitting the job to jenkins. Take a look at the preceding http response for more info." end ensure # Clean up after ourselves rm bundle rm properties end end end end ## # A task listing for creating jenkins tasks for our various pl: and pe: build # tasks. We can assume deb, mock, but not gem/dmg. # tasks = ["deb", "mock", "tar"] tasks << "gem" if Pkg::Config.build_gem and !Pkg::Config.build_pe tasks << "dmg" if Pkg::Config.build_dmg and !Pkg::Config.build_pe namespace :pl do namespace :jenkins do tasks.each do |build_task| desc "Queue pl:#{build_task} build on jenkins builder" task build_task => "pl:fetch" do Pkg::Util::RakeUtils.invoke_task("pl:jenkins:post_build", "pl:#{build_task}") end end # While pl:remote:deb_all does all cows in serially, with jenkins we # parallelize them. This breaks the cows up and posts a build for all of # them. We have to sleep 5 because jenkins drops the builds when we're # DOSing it with our packaging. desc "Queue pl:deb_all on jenkins builder" task :deb_all => "pl:fetch" do Pkg::Config.cows.split(' ').each do |cow| Pkg::Config.default_cow = cow Pkg::Util::RakeUtils.invoke_task("pl:jenkins:post_build", "pl:deb") sleep 5 end end # This does the mocks in parallel desc "Queue pl:mock_all on jenkins builder" task :mock_all => "pl:fetch" do Pkg::Config.final_mocks.split(' ').each do |mock| Pkg::Config.default_mock = mock Pkg::Util::RakeUtils.invoke_task("pl:jenkins:post_build", "pl:mock") sleep 5 end end task :uber_ship_lite => "pl:fetch" do tasks = %w[ jenkins:retrieve jenkins:sign_all ship_rpms ship_debs ship_dmg ship_swix ship_tar ship_msi ship_gem ] tasks.map { |t| "pl:#{t}" }.each do |t| puts "Running \"#{t}\"" Rake::Task[t].invoke end # mark the build as successfully shipped Rake::Task["pl:jenkins:ship"].invoke("shipped") # add the release to release-metrics begin Rake::Task["pl:update_release_metrics"].invoke rescue StandardError => e fail "Error updating release-metrics:\n#{e}\nYou will need to add this release manually." end end task :stage_nightlies => "pl:fetch" do # debian weirdness: ship_nightly_debs uses the old methodology that posts to # apt.puppet.com; stage_nightly_debs uses the updated methodology that posts to # apt.repos.puppet.com tasks = %w[ jenkins:retrieve jenkins:sign_all ship_nightly_rpms ship_nightly_debs ship_nightly_dmg ship_nightly_swix ship_nightly_msi ] tasks.map { |t| "pl:#{t}" }.each do |t| puts "Running \"#{t}\"" Rake::Task[t].invoke end end task :ship_nightlies => "pl:fetch" do ## nightlies.puppet.com Rake::Task['pl:jenkins:stage_nightlies'].invoke Rake::Task['pl:remote:update_nightly_repos'].invoke Rake::Task['pl:remote:deploy_nightlies_to_s3'].invoke end task :ship_final => "pl:fetch" do Rake::Task['pl:jenkins:uber_ship_lite'].invoke Rake::Task['pl:remote:update_foss_repos'].invoke Rake::Task['pl:remote:deploy_final_builds_to_s3'].invoke Rake::Task['pl:remote:deploy_to_rsync_server'].invoke end task :stage_release_packages => "pl:fetch" do Rake::Task['pl:jenkins:uber_ship_lite'].invoke # Deb packages only appear in the freight directory until repo updates. # We must run that before creating symlinks so we can link from packages # in the apt repository. Rake::Task['pl:remote:update_apt_repo'].invoke Pkg::Util::Ship.update_release_package_symlinks('pkg') end task :stage_nightly_release_packages => "pl:fetch" do Rake::Task['pl:jenkins:stage_nightlies'].invoke # Deb packages only appear in the freight directory until repo updates. # We must run that before creating symlinks so we can link from packages # in the apt repository. Rake::Task['pl:remote:update_nightlies_apt_repo'].invoke Pkg::Util::Ship.update_release_package_symlinks('pkg', true) end desc "Retrieve packages built by jenkins, sign, and ship all!" task :uber_ship => "pl:fetch" do uber_tasks = %w[ jenkins:retrieve jenkins:sign_all uber_ship ship_gem remote:update_apt_repo remote:update_yum_repo remote:update_ips_repo remote:deploy_apt_repo remote:deploy_yum_repo remote:deploy_dmg_repo remote:deploy_swix_repo remote:deploy_msi_repo remote:deploy_tar_repo remote:deploy_apt_repo_to_s3 remote:deploy_yum_repo_to_s3 remote:deploy_downloads_to_s3 remote:deploy_to_rsync_server ] if Pkg::Util.boolean_value(Pkg::Config.answer_override) && !Pkg::Config.foss_only fail "Using ANSWER_OVERRIDE without FOSS_ONLY=true is dangerous!" end # Some projects such as pl-build-tools do not stage to a separate server - so we do to deploy uber_tasks.delete("remote:deploy_apt_repo") if Pkg::Config.apt_host == Pkg::Config.apt_signing_server uber_tasks.delete("remote:deploy_yum_repo") if Pkg::Config.yum_host == Pkg::Config.yum_staging_server uber_tasks.delete("remote:deploy_dmg_repo") if Pkg::Config.dmg_host == Pkg::Config.dmg_staging_server uber_tasks.delete("remote:deploy_swix_repo") if Pkg::Config.swix_host == Pkg::Config.swix_staging_server uber_tasks.delete("remote:deploy_tar_repo") if Pkg::Config.tar_host == Pkg::Config.tar_staging_server if Pkg::Config.s3_ship uber_tasks.delete("remote:deploy_apt_repo") uber_tasks.delete("remote:deploy_yum_repo") uber_tasks.delete("remote:deploy_dmg_repo") uber_tasks.delete("remote:deploy_swix_repo") uber_tasks.delete("remote:deploy_msi_repo") uber_tasks.delete("remote:deploy_tar_repo") else uber_tasks.delete("remote:deploy_apt_repo_to_s3") uber_tasks.delete("remote:deploy_yum_repo_to_s3") uber_tasks.delete("remote:deploy_downloads_to_s3") uber_tasks.delete("remote:deploy_to_rsync_server") end # Delete the ship_gem task if we aren't building gems uber_tasks.delete("ship_gem") unless Pkg::Config.build_gem # I'm adding this check here because if we rework the task ordering we're # probably going to need to muck about in here. -morgan if uber_tasks.first == 'jenkins:retrieve' # We need to run retrieve before we can delete tasks based on what # packages were built. Before this we were deleting tasks based on files # in a directory that hadn't been populated yet, so this would either # fail since all tasks would be removed, or would be running based on # files left over in packaging from the last ship. puts 'Do you want to run pl:jenkins:retrieve?' Rake::Task['pl:jenkins:retrieve'].invoke if Pkg::Util.ask_yes_or_no uber_tasks.delete('jenkins:retrieve') end # Don't update and deploy repos if packages don't exist # If we can't find a certain file type, delete the task if Dir.glob("pkg/**/*.deb").empty? uber_tasks.delete("remote:update_apt_repo") uber_tasks.delete("remote:deploy_apt_repo") end if Dir.glob("pkg/**/*.rpm").empty? uber_tasks.delete("remote:update_yum_repo") uber_tasks.delete("remote:deploy_yum_repo") end if Dir.glob("pkg/**/*.p5p").empty? uber_tasks.delete("remote:update_ips_repo") end if Dir.glob("pkg/**/*.dmg").empty? uber_tasks.delete("remote:deploy_dmg_repo") end if Dir.glob("pkg/**/*.swix").empty? uber_tasks.delete("remote:deploy_swix_repo") end if Dir.glob("pkg/**/*.msi").empty? uber_tasks.delete("remote:deploy_msi_repo") end if Dir.glob("pkg/*.tar.gz").empty? uber_tasks.delete("remote:deploy_tar_repo") end uber_tasks.map { |t| "pl:#{t}" }.each do |t| puts "Do you want to run #{t}?" Rake::Task[t].invoke if Pkg::Util.ask_yes_or_no end puts "Do you want to mark this release as successfully shipped?" Rake::Task["pl:jenkins:ship"].invoke("shipped") if Pkg::Util.ask_yes_or_no end desc "Test shipping by replacing hosts with a VM" task :test_ship, [:vm, :ship_task] do |t, args| vm = args.vm or fail "`vm` is a required argument for #{t}" ship_task = args.ship_task or fail "`ship_task` is a required argument for #{t}" Pkg::Util::Ship.test_ship(vm, ship_task) end end end ## # If this is a PE project, we want PE tasks as well. # if Pkg::Config.build_pe namespace :pe do namespace :jenkins do tasks.each do |build_task| desc "Queue pe:#{build_task} build on jenkins builder" task build_task => "pl:fetch" do Pkg::Util.check_var("PE_VER", Pkg::Config.pe_version) Pkg::Util::RakeUtils.invoke_task("pl:jenkins:post_build", "pe:#{build_task}") end end # While pl:remote:deb_all does all cows in serially, with jenkins we # parallelize them. This breaks the cows up and posts a build for all of # them. We have to sleep 5 because jenkins drops the builds when we're # DOSing it with our packaging. desc "Queue pe:deb_all on jenkins builder" task :deb_all => "pl:fetch" do Pkg::Util.check_var("PE_VER", Pkg::Config.pe_version) Pkg::Config.cows.split(' ').each do |cow| Pkg::Config.default_cow = cow Pkg::Util::RakeUtils.invoke_task("pl:jenkins:post_build", "pe:deb") sleep 5 end end # This does the mocks in parallel desc "Queue pe:mock_all on jenkins builder" task :mock_all => "pl:fetch" do Pkg::Config.final_mocks.split(' ').each do |mock| Pkg::Config.default_mock = mock Pkg::Util::RakeUtils.invoke_task("pl:jenkins:post_build", "pe:mock") sleep 5 end end desc "Retrieve PE packages built by jenkins, sign, and ship all!" task :uber_ship => "pl:fetch" do Pkg::Util.check_var("PE_VER", Pkg::Config.pe_version) ["pl:jenkins:retrieve", "pl:jenkins:sign_all", "pe:ship_rpms", "pe:ship_debs"].each do |task| Rake::Task[task].invoke end Rake::Task["pl:jenkins:ship"].invoke("shipped") end end end end ## # This task allows the packaging repo to post to an arbitrary jenkins job but # it is very limited in that it does not model well the key => value format # used when submitting form data on websites. This is primarily because rake # does not allow us to elegantly pass arbitrary key => value pairs on the # command line and have any idea how to reference them inside rake. We can pass # KEY=VALUE along with our invokation, but unless KEY is statically coded into # our task, we won't know how to reference it. Thus, this task will only take # one argument, the uri of the jenkins job to post to. This can be passed # either as an argument directly or as an environment variable "JOB" with the # uri as the value. The argument is required. The second requirement is that # the job to be called accept a string parameter with the name SHA. This will # be the SHA of the commit of the project source code HEAD, and should be used # by the job to check out this specific ref. To maintain the abstraction of the # jenkins jobs, this specific task passes on no information about the build # itself. The assumption is that the upstream jobs know about their project, # and so do the downstream jobs, but packaging itself has no business knowing # about it. # namespace :pl do namespace :jenkins do desc "Trigger a jenkins uri with SHA of HEAD as a string param, requires \"URI\"" task :post, :uri do |t, args| uri = args.uri || ENV['URI'] unless uri fail 'pl:jenkins:post requires a URI, either via URI= or pl:jenkin:post[URI]' end # We use JSON for parsing the json part of the submission. begin require 'json' rescue LoadError => e fail "Error: could not load 'json' gem: #{e}" end # Assemble the JSON string for the JSON parameter json = JSON.generate("parameter" => [{ "name" => "SHA", "value" => Pkg::Config.ref.to_s }]) # Assemble our arguments to the post args = [ "-Fname=SHA", "-Fvalue=#{Pkg::Config.ref}", "-Fjson=#{json.to_json}", "-FSubmit=Build" ] _, retval = Pkg::Util::Net.curl_form_data(uri, args) unless Pkg::Util::Execution.success?(retval) fail "An error occurred attempting to trigger the job at \"#{uri}\". " \ "See the preceding http response for more information." end puts "Job triggered at #{uri}." end end end