Permalink
Cannot retrieve contributors at this time
Fetching contributors…
| # frozen_string_literal: true | |
| # | |
| # Copyright (C) 2015-2017 Harald Sitter <sitter@kde.org> | |
| # | |
| # This library is free software; you can redistribute it and/or | |
| # modify it under the terms of the GNU Lesser General Public | |
| # License as published by the Free Software Foundation; either | |
| # version 2.1 of the License, or (at your option) version 3, or any | |
| # later version accepted by the membership of KDE e.V. (or its | |
| # successor approved by the membership of KDE e.V.), which shall | |
| # act as a proxy defined in Section 6 of version 3 of the license. | |
| # | |
| # This library is distributed in the hope that it will be useful, | |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| # Lesser General Public License for more details. | |
| # | |
| # You should have received a copy of the GNU Lesser General Public | |
| # License along with this library. If not, see <http://www.gnu.org/licenses/>. | |
| # All the methods we have are task helpers, so they are fairly spagetthi. | |
| # Blocks are tasks, so they are even worse offenders. | |
| # Overengineering this into objects is probably not a smart move so let's ignore | |
| # this (for now anyway). | |
| # rubocop:disable Metrics/BlockLength, Metrics/MethodLength | |
| require 'etc' | |
| require 'fileutils' | |
| require 'mkmf' | |
| require 'open-uri' | |
| require 'tmpdir' | |
| require_relative 'lib/rake/bundle' | |
| require_relative 'ci-tooling/lib/nci' | |
| DIST = ENV.fetch('DIST') | |
| # These will be installed in one-go before the actual deps are being installed. | |
| # This should only include stuff which is needed to make the actual DEP | |
| # installation work! | |
| EARLY_DEPS = [ | |
| 'python-apt-common', # Remove this once python-apt gets a Stretch template | |
| 'eatmydata' # We disable fsync from apt and dpkg. | |
| ].freeze | |
| # Core is not here because it is required as a build-dep or anything but | |
| # simply a runtime (or provision time) dep of the tooling. | |
| CORE_RUNTIME_DEPS = %w[apt-transport-https software-properties-common].freeze | |
| DEPS = %w[xz-utils dpkg-dev dput debhelper pkg-kde-tools devscripts | |
| python-launchpadlib ubuntu-dev-tools gnome-pkg-tools git dh-systemd | |
| zlib1g-dev python-paramiko sudo locales mercurial pxz aptitude | |
| autotools-dev cdbs dh-autoreconf germinate gnupg2 | |
| gobject-introspection sphinx-common po4a pep8 pyflakes ppp-dev dh-di | |
| libgirepository1.0-dev libglib2.0-dev bash-completion | |
| python3-setuptools python3-setuptools-scm python-setuptools python-setuptools-scm dkms | |
| mozilla-devscripts libffi-dev subversion libssl-dev libcurl4-gnutls-dev | |
| libhttp-parser-dev javahelper rsync].freeze + CORE_RUNTIME_DEPS | |
| # FIXME: code copy from install_check | |
| def install_fake_pkg(name) | |
| require_relative 'ci-tooling/lib/dpkg' | |
| Dir.mktmpdir do |tmpdir| | |
| Dir.chdir(tmpdir) do | |
| FileUtils.mkpath("#{name}/DEBIAN") | |
| File.write("#{name}/DEBIAN/control", <<-CONTROL.gsub(/^\s+/, '')) | |
| Package: #{name} | |
| Version: 999:999 | |
| Architecture: all | |
| Maintainer: Harald Sitter <sitter@kde.org> | |
| Description: fake override package for ci install checks | |
| CONTROL | |
| system("dpkg-deb -b #{name} #{name}.deb") | |
| DPKG.dpkg(['-i', "#{name}.deb"]) | |
| end | |
| end | |
| end | |
| def custom_version_id | |
| require_relative 'ci-tooling/lib/dci' | |
| return unless DCI.series.keys.include?(DIST) | |
| file = '/etc/os-release' | |
| os_release = File.readlines(file) | |
| # Strip out any lines starting with VERSION_ID | |
| # so that we don't end up with an endless number of VERSION_ID entries | |
| os_release.reject! { |l| l.start_with?('VERSION_ID') } | |
| system('dpkg-divert', '--local', '--rename', '--add', file) || raise | |
| os_release << "VERSION_ID=\"#{DCI.series[DIST]}\"\n" | |
| File.write(file, os_release.join) | |
| end | |
| def cleanup_rubies | |
| # We can have two rubies at a time, the system ruby and our ruby. We'll do | |
| # general purpose cleanup on all possible paths but then rip apart the system | |
| # ruby if we have our own ruby installed. This way we do not have unused gems | |
| # in scenarios where we used the system ruby previously but now use a custom | |
| # one. | |
| # Gem cache and doc. Neither shoud be needed at runtime. | |
| FileUtils.rm_rf(Dir.glob('/var/lib/gems/*/{cache,doc}/*'), | |
| verbose: true) | |
| FileUtils.rm_rf(Dir.glob('/usr/local/lib/ruby/gems/*/{cache,doc}/*'), | |
| verbose: true) | |
| # libgit2 cmake build tree | |
| FileUtils.rm_rf(Dir.glob('/var/lib/gems/*/gems/rugged-*/vendor/*/build'), | |
| verbose: true) | |
| FileUtils.rm_rf(Dir.glob('/usr/local/lib/ruby/gems/*/gems/rugged-*/vendor/*/build'), | |
| verbose: true) | |
| # Other compiled extension artifacts not used at runtime | |
| FileUtils.rm_rf(Dir.glob('/var/lib/gems/*/gems/*/ext/*/*.{so,o}'), | |
| verbose: true) | |
| FileUtils.rm_rf(Dir.glob('usr/local/lib/ruby/gems/*/gems/*/ext/*/*.{so,o}'), | |
| verbose: true) | |
| return unless find_executable('ruby').include?('local') | |
| puts 'Mangling system ruby' | |
| # All gems in all versions. | |
| FileUtils.rm_rf(Dir.glob('/var/lib/gems/*/*'), verbose: true) | |
| end | |
| # openqa | |
| task :deploy_openqa do | |
| # Only openqa on neon dists and if explicitly enabled. | |
| next unless NCI.series.keys.include?(DIST) && | |
| ENV.include?('PANGEA_PROVISION_AUTOINST') | |
| Dir.mktmpdir do |tmpdir| | |
| system 'git clone --depth 1 ' \ | |
| "https://github.com/apachelogger/kde-os-autoinst #{tmpdir}/" | |
| Dir.chdir('/opt') { sh "#{tmpdir}/bin/install.rb" } | |
| end | |
| end | |
| desc 'deploy inside the container' | |
| task :deploy_in_container => %i[align_ruby deploy_openqa] do | |
| home = '/var/lib/jenkins' | |
| # Deploy ci-tooling and bundle. We later use internal libraries to provision | |
| # so we need all dependencies met as early as possible in the process. | |
| # FIXME: copy from above | |
| tooling_path = '/tooling-pending' | |
| final_path = '/tooling' | |
| final_ci_tooling_compat_path = File.join(home, 'tooling') | |
| final_ci_tooling_compat_compat_path = File.join(home, 'ci-tooling') | |
| File.write("#{Dir.home}/.gemrc", <<-EOF) | |
| install: --no-document | |
| update: --no-document | |
| EOF | |
| Dir.chdir(tooling_path) do | |
| begin | |
| Gem::Specification.find_by_name('bundler') | |
| sh 'gem update bundler' | |
| rescue Gem::LoadError | |
| sh 'gem install bundler' | |
| end | |
| # Add debug for checking what version is being used | |
| bundle(*%w[--version]) | |
| bundle_args = ['install'] | |
| bundle_args << "--jobs=#{[Etc.nprocessors / 2, 1].max}" | |
| bundle_args << '--local' | |
| bundle_args << '--no-cache' | |
| bundle_args << '--frozen' | |
| bundle_args << '--system' | |
| # FIXME: this breaks deployment on nodes, for now disable this | |
| # https://github.com/blue-systems/pangea-tooling/issues/17 | |
| #bundle_args << '--without' << 'development' << 'test' | |
| bundle(*bundle_args) | |
| # Clean up now unused gems. This prevents unused versions of a gem | |
| # lingering in the image blowing up its size. | |
| clean_args = ['clean'] | |
| clean_args << '--verbose' | |
| clean_args << '--force' # Force system clean! | |
| bundle(*clean_args) | |
| # Trap common exit signals to make sure the ownership of the forwarded | |
| # volume is correct once we are done. | |
| # Otherwise it can happen that bundler left root owned artifacts behind | |
| # and the folder becomes undeletable. | |
| %w[EXIT HUP INT QUIT TERM].each do |signal| | |
| Signal.trap(signal) do | |
| next unless Etc.passwd { |u| break true if u.name == 'jenkins' } | |
| FileUtils.chown_R('jenkins', 'jenkins', tooling_path, verbose: true, | |
| force: true) | |
| end | |
| end | |
| FileUtils.rm_rf(final_path) | |
| FileUtils.mkpath(final_path, verbose: true) | |
| FileUtils.cp_r('./.', final_path, verbose: true) | |
| [final_ci_tooling_compat_path, | |
| final_ci_tooling_compat_compat_path].each do |compat| | |
| if File.symlink?(compat) | |
| FileUtils.rm(compat, verbose: true) | |
| elsif File.exist?(compat) | |
| FileUtils.rm_r(compat, verbose: true) | |
| end | |
| # Make sure the parent exists, in case of /var/lib/jenkins on slaves | |
| # that is not the case for new builds. | |
| FileUtils.mkpath(File.dirname(compat)) | |
| FileUtils.ln_s("#{final_path}/ci-tooling", compat, verbose: true) | |
| end | |
| end | |
| require_relative 'ci-tooling/lib/apt' | |
| File.write('force-unsafe-io', '/etc/dpkg/dpkg.cfg.d/00_unsafeio') | |
| File.open('/etc/dpkg/dpkg.cfg.d/00_paths', 'w') do |file| | |
| # Do not install locales other than en/en_US. | |
| # Do not install manpages, infopages, groffpages. | |
| # Do not install docs. | |
| path = { | |
| rxcludes: %w[ | |
| /usr/share/locale/**/** | |
| /usr/share/man/**/** | |
| /usr/share/info/**/** | |
| /usr/share/groff/**/** | |
| /usr/share/doc/**/** | |
| ], | |
| excludes: %w[ | |
| /usr/share/locale/* | |
| /usr/share/man/* | |
| /usr/share/info/* | |
| /usr/share/groff/* | |
| /usr/share/doc/* | |
| ], | |
| includes: %w[ | |
| /usr/share/locale/en | |
| /usr/share/locale/en_US | |
| /usr/share/locale/locale.alias | |
| ] | |
| } | |
| path[:excludes].each { |e| file.write("path-exclude=#{e}") } | |
| path[:includes].each { |i| file.write("path-include=#{i}") } | |
| path[:rxcludes].each do |ruby_exclude| | |
| Dir.glob(ruby_exclude).each do |match| | |
| next if path[:includes].any? { |i| File.fnmatch(i, match) } | |
| next unless File.exist?(match) | |
| FileUtils.rm_rf(match) | |
| end | |
| end | |
| end | |
| Apt.install(*EARLY_DEPS) || raise | |
| # Force eatmydata on the installation binaries to completely bypass fsyncs. | |
| # This gives a 20% speed improvement on installing plasma-desktop+deps. That | |
| # is ~1 minute! | |
| %w[dpkg apt-get apt].each do |bin| | |
| file = "/usr/bin/#{bin}" | |
| next if File.exist?("#{file}.distrib") # Already diverted | |
| File.open("#{file}.pangea", File::RDWR | File::CREAT, 0o755) do |f| | |
| f.write(<<-SCRIPT) | |
| #!/bin/sh | |
| /usr/bin/eatmydata #{bin}.distrib "$@" | |
| SCRIPT | |
| end | |
| system('dpkg-divert', '--local', '--rename', '--add', file) || raise | |
| File.symlink("#{file}.pangea", file) | |
| end | |
| # Turn fc-cache into a dud to prevent cache generation. Utterly pointless | |
| # in a build environment. | |
| %w[fc-cache].each do |bin| | |
| file = "/usr/bin/#{bin}" | |
| next if File.exist?("#{file}.distrib") # Already diverted | |
| system('dpkg-divert', '--local', '--rename', '--add', file) || raise | |
| # Fuck you dpkg. Fuck you so much. | |
| FileUtils.mv(file, "#{file}.distrib") if File.exist?(file) | |
| File.symlink('/bin/true', file) | |
| end | |
| # Install a fake fonts-noto CJK to bypass it's incredibly long unpack. Given | |
| # the size of the package it takes *seconds* to unpack but in CI environments | |
| # it adds no value. | |
| install_fake_pkg('fonts-noto-cjk') | |
| require_relative 'ci-tooling/lib/retry' | |
| Retry.retry_it(times: 5, sleep: 8) do | |
| # NOTE: apt.rb automatically runs update the first time it is used. | |
| raise 'Dist upgrade failed' unless Apt.dist_upgrade | |
| raise 'Apt install failed' unless Apt.install(*DEPS) | |
| raise 'Autoremove failed' unless Apt.autoremove(args: '--purge') | |
| raise 'Clean failed' unless Apt.clean | |
| end | |
| # Ubuntu's language-pack-en-base calls this internally, since this is | |
| # unavailable on Debian, call it manually. | |
| locale_tag = "#{ENV.fetch('LANG')} UTF-8" | |
| File.open('/etc/locale.gen', 'a+') do |f| | |
| f.puts(locale_tag) unless f.any? { |l| l.start_with?(locale_tag) } | |
| end | |
| sh '/usr/sbin/locale-gen --keep-existing --no-purge --lang en' | |
| sh "update-locale LANG=#{ENV.fetch('LANG')}" | |
| # Prevent xapian from slowing down the test. | |
| # Install a fake package to prevent it from installing and doing anything. | |
| # This does render it non-functional but since we do not require the database | |
| # anyway this is the apparently only way we can make sure that it doesn't | |
| # create its stupid database. The CI hosts have really bad IO performance | |
| # making a full index take more than half an hour. | |
| install_fake_pkg('apt-xapian-index') | |
| uname = 'jenkins' | |
| uid = 100_000 | |
| gname = 'jenkins' | |
| gid = 120 | |
| group_exist = false | |
| Etc.group do |group| | |
| if group.name == gname | |
| group_exist = true | |
| break | |
| end | |
| end | |
| user_exist = false | |
| Etc.passwd do |user| | |
| if user.name == uname | |
| user_exist = true | |
| break | |
| end | |
| end | |
| sh "addgroup --system --gid #{gid} #{gname}" unless group_exist | |
| unless user_exist | |
| sh "adduser --system --home #{home} --uid #{uid} --ingroup #{gname}" \ | |
| " --disabled-password #{uname}" | |
| end | |
| # Add the new jenkins user the sudoers so we can run as jenkins and elevate | |
| # if and when necessary. | |
| File.open("/etc/sudoers.d/#{uid}-#{uname}", 'w', 0o440) do |f| | |
| f.puts('jenkins ALL=(ALL) NOPASSWD: ALL') | |
| end | |
| # Add a custom version_id in os-release for DCI | |
| custom_version_id | |
| # Ultimate clean up | |
| # Semi big logs | |
| File.write('/var/log/lastlog', '') | |
| File.write('/var/log/faillog', '') | |
| File.write('/var/log/dpkg.log', '') | |
| File.write('/var/log/apt/term.log', '') | |
| cleanup_rubies | |
| end | |
| desc 'Upgrade to newer ruby if required' | |
| task :align_ruby do | |
| FileUtils.rm_rf('/tmp/kitchen') # Instead of messing with pulls, just clone. | |
| sh format('git clone --depth 1 %s %s', | |
| 'https://github.com/blue-systems/pangea-kitchen.git', | |
| '/tmp/kitchen') | |
| Dir.chdir('/tmp/kitchen') do | |
| # ruby_build checks our version against the pangea version and if necessary | |
| # installs a ruby in /usr/local which is more suitable than what we have. | |
| # If this comes back !0 and we are meant to be aligned already this means | |
| # the previous alignment failed, abort when this happens. | |
| if !system('./ruby_build.sh') && ENV['ALIGN_RUBY_EXEC'] | |
| raise 'It seems rake was re-executed after a ruby version alignment,' \ | |
| ' but we still found and unsuitable ruby version being used!' | |
| end | |
| end | |
| case $?.exitstatus | |
| when 0 # installed version is fine, we are happy. | |
| FileUtils.rm_rf('/tmp/kitchen') | |
| next | |
| when 1 # a new version was installed, we'll re-exec ourself. | |
| sh 'gem install rake' | |
| ENV['ALIGN_RUBY_EXEC'] = 'true' | |
| # Reload ourself via new rake | |
| exec('rake', *ARGV) | |
| else # installer crashed or other unexpected error. | |
| raise 'Error while aligning ruby version through pangea-kitchen' | |
| end | |
| end |