Blob Blame History Raw
#!/usr/bin/python3

# Copyright 2013 Red Hat Inc., Durham, North Carolina.
# All Rights Reserved.
#
# 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) any later version.
#
# 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, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Authors:
#      Martin Preisler <mpreisle@redhat.com>
#      Vratislav Podzimek <vpodzime@redhat.com>

import locale
# We shall always behave the same, regardless of locale
locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')


import argparse
import string
import os.path
import shutil
import datetime
import tempfile
import subprocess
import sys

class CannotContinueError(Exception):
    """Exception class for cases where processing cannot continue."""

    pass

if subprocess.call(["rpmbuild", "--version"], stdout = sys.stdout, stderr = sys.stderr) != 0:
    sys.stderr.write("Could not execute `rpmbuild --version`. "
        "Please make sure it's installed (often packaged as 'rpm-build').\n")
    sys.exit(1)

def get_rpmbuild_paths():
    sources = subprocess.check_output(["rpm", "--eval", "%{_sourcedir}"]).strip().decode('utf-8')

    if not os.path.exists(sources):
        if subprocess.call(["rpmdev-setuptree"], stdout = sys.stdout, stderr = sys.stderr) != 0:
            raise CannotContinueError("Failed to setup rpmbuild tree. Please make sure you have rpmdev-setuptree "
                "installed, or set it up manually. The SOURCES directory (%%{_sourcedir}) was expected at "
                "'%s'" % (sources))

    rpm = subprocess.check_output(["rpm", "--eval", "%{_rpmdir}"]).strip().decode('utf-8')
    srpm = subprocess.check_output(["rpm", "--eval", "%{_srcrpmdir}"]).strip().decode('utf-8')

    if not os.path.exists(rpm) or not os.path.exists(srpm):
        sys.stderr.write("The SOURCES rpmbuild directory exists but RPM or SRPM do not. Please make "
            "sure your rpmbuild tree is setup correctly. Delete it and it will be created automatically.\n")

    return sources, rpm, srpm

def copy_sources_to_rpmbuild(rpmbuild_sources_path, files):
    for f in files:
        shutil.copyfile(f.name, "%s/%s" % (rpmbuild_sources_path, os.path.basename(f.name)))

def make_sources_list(files):
    ret = ""

    for (i, f) in enumerate(files):
        ret += "Source%i: %s\n" % (i, os.path.basename(f.name))

    return ret

def make_installer(scap_location, files):
    prepper = ""
    installer = ""
    installed_files = ""

    for (i, f) in enumerate(files):
        prepper += "cp %%SOURCE%i .\n" % (i)

        installer += "cp %s $RPM_BUILD_ROOT/%s/%%{name}/\n" % (os.path.basename(f.name), scap_location)
        installed_files += "%s/%%{name}/%s\n" % (scap_location, os.path.basename(f.name))

    return prepper, installer, installed_files

def create_spec(template_path, name,
        version, release, summary, license,
        scap_location, files,
        target_file):

    spec_template = """
# __ prefixed varibles in the form of ${__*} will get replaced by Python's string.Template

Name:           ${__package_name}
Version:        ${__package_version}
Release:        ${__package_release}
Summary:        ${__package_summary}
License:        ${__package_license}

${__package_sources}

BuildArch:      noarch

#BuildRequires:  openscap-utils >= ${__package_openscap_version}
#Requires:       openscap-utils >= ${__package_openscap_version}

%description
This package was generated by scap-as-rpm.

%prep
${__package_prepper}

%build

%install
mkdir -p $RPM_BUILD_ROOT/${__package_scap_location}/%{name}
${__package_installer}

%files
${__package_installed_files}

%changelog
* ${__changelog_date} save-as-rpm - ${__package_version}-${__package_release}
- Autogenerated
"""

    template = string.Template(spec_template)

    sources_list = make_sources_list(files)
    prepper, installer, installed_files = make_installer(scap_location, files)

    spec_source = template.safe_substitute(
        __package_name = name,

        __package_version = version,
        __package_release = release,
        __package_summary = summary,
        __package_license = license,

        __package_scap_location = scap_location,

        __package_sources = sources_list,
        __package_openscap_version = "0.9.12", # FIXME
        __package_prepper = prepper,
        __package_installer = installer,
        __package_installed_files = installed_files,
        __changelog_date = datetime.date.today().strftime("%a %b %d %Y")
    )

    target_file.write(spec_source)

def main():
    parser = argparse.ArgumentParser(
        description = "Takes given SCAP input(s) and makes an RPM package that contains them. "
                      "The result RPM can be installed using # yum install ./package-name-1-1.rpm "
                      "which will put the contents into /usr/share/xml/scap. No dependency on openscap "
                      "or scap-workbench is enforced in the output package so you can use any "
                      "SCAP-capable scanner to evaluate the content.")

    # we choose name automatically if its missing
    parser.add_argument("--pkg-name", dest = "pkg_name", default = None,
            help = "Name of the RPM package, if none is provided the basename of the first SCAP input is used. Ex.: xyz-security-guide")
    parser.add_argument("--pkg-version", dest = "pkg_version", default = "1")
    parser.add_argument("--pkg-release", dest = "pkg_release", default = "1")
    parser.add_argument("--pkg-summary", dest = "pkg_summary", default = "stub",
        help = "Optional short description of the package.")
    parser.add_argument("--pkg-license", dest = "pkg_license", default = "Unknown",
        help = "Short name of the license that you want to publish the package under. Ex.: GPLv2+, BSD, ...")

    parser.add_argument("--pkg-scap-location", dest = "pkg_scap_location", default = "%{_datadir}/xml/scap",
        help = "Folder where SCAP files are supposed to be installed. Each package will have its own folder "
               "inside this folder. RPM variables can be used and will be expanded as usual. It is "
               "recommended to keep the default settings.")

    parser.add_argument("--rpm-destination", dest = "rpm_destination", default = ".",
        help = "The folder (absolute or relative to CWD) where the result RPM shall be saved.")
    parser.add_argument("--srpm-destination", dest = "srpm_destination", default = None,
        help = "The folder (absolute or relative to CWD) where the result SRPM shall be saved.")
    parser.add_argument("files", metavar = "FILE", nargs = "+",
        help = "List of files that should be put into the result package. "
               "These should be SCAP XML files but such requirement is not enforced.")

    args = parser.parse_args()

    if not args.rpm_destination:
        print("--rpm-destination has to be specified")
        parser.print_help()
        return 1

    if not os.path.isdir(args.rpm_destination):
        print("'%s' does not seem like a directory or isn't accessible!" % args.rpm_destination)
        parser.print_help()
        return 1

    if args.srpm_destination is not None and not os.path.isdir(args.srpm_destination):
        print("'%s' does not seem like a directory or isn't accessible!" % args.srpm_destination)
        parser.print_help()
        return 1

    for fpath in args.files:
        if not os.path.exists(fpath):
            print("File '%s' does not exist or isn't accessible!" % fpath)
            parser.print_help()
            return 1

    args.files = [open(fpath, "r") for fpath in args.files]

    rpmbuild_sources_path, rpmbuild_rpm_path, rpmbuild_srpm_path = get_rpmbuild_paths()

    copy_sources_to_rpmbuild(rpmbuild_sources_path, args.files)

    name = args.pkg_name
    if name is None:
        name, _ = os.path.splitext(os.path.basename(args.files[0].name))

    temp_dir = tempfile.mkdtemp()
    spec_file = open("%s/%s.spec" % (temp_dir, name), "w")
    try:
        create_spec("templates/package.spec",
            name,
            args.pkg_version, args.pkg_release, args.pkg_summary, args.pkg_license,
            args.pkg_scap_location, args.files,
            spec_file)
        spec_file_path = spec_file.name
        spec_file.close()

        ret = subprocess.call(["rpmbuild", "-ba", spec_file_path], stdout = sys.stdout)
        if ret != 0:
            raise CannotContinueError("Failed to build RPM and SRPM for %s" % spec_file_path)

    finally:
        shutil.rmtree(temp_dir)

    rpm_basename = "%s-%s-%s.noarch.rpm" % (name, args.pkg_version, args.pkg_release)
    srpm_basename = "%s-%s-%s.src.rpm" % (name, args.pkg_version, args.pkg_release)

    shutil.copy(os.path.join(rpmbuild_rpm_path, "noarch", rpm_basename), os.path.join(args.rpm_destination, rpm_basename))
    if args.srpm_destination is not None:
        shutil.copy(os.path.join(rpmbuild_srpm_path, srpm_basename), os.path.join(args.srpm_destination, srpm_basename))

    print("")
    print("Resulting RPM:\t'%s'" % (os.path.join(args.rpm_destination, rpm_basename)))
    if args.srpm_destination is not None:
        print("Resulting SRPM:\t'%s'" % (os.path.join(args.srpm_destination, srpm_basename)))
    print("")
    print("Finished!")

    return 0

if __name__ == "__main__":
    try:
        ret = main()
        sys.exit(ret)
    except CannotContinueError as e:
        sys.stderr.write("%s\n" % e.message)
        sys.exit(1)