From 3867d74f7addf4829208096ba748bf0cf356446e Mon Sep 17 00:00:00 2001 From: Pavel Raiskup Date: Feb 19 2018 13:26:45 +0000 Subject: [PATCH 1/3] [frontend] swap arguments in custom webhook and document --- diff --git a/frontend/coprs_frontend/coprs/templates/coprs/detail/settings/webhooks.html b/frontend/coprs_frontend/coprs/templates/coprs/detail/settings/webhooks.html index b520779..9ffeec4 100644 --- a/frontend/coprs_frontend/coprs/templates/coprs/detail/settings/webhooks.html +++ b/frontend/coprs_frontend/coprs/templates/coprs/detail/settings/webhooks.html @@ -65,6 +65,20 @@
  • Select to trigger on Repository Push.
  • Click the Save button.
  • + +

    Custom webhook

    +
    + {{ custom_url }} +
    +

    How to use it:

    +

    Use the GitLab/GitHub steps above (when needed), or simply

    +

    +

    + $ curl -X POST {{ custom_url }} +
    + Note that the package of name 'PACKAGE_NAME' must exist within this + project, and that the 'POST' http method must be specified. +

    diff --git a/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_general.py b/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_general.py index 75e095b..2763f92 100644 --- a/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_general.py +++ b/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_general.py @@ -422,10 +422,15 @@ def render_copr_webhooks(copr): copr.id, copr.webhook_secret) + custom_url = "https://{}/webhooks/custom/{}/{}/".format( + app.config["PUBLIC_COPR_HOSTNAME"], + copr.id, + copr.webhook_secret) + "/" + return flask.render_template( "coprs/detail/settings/webhooks.html", copr=copr, bitbucket_url=bitbucket_url, github_url=github_url, - gitlab_url=gitlab_url) + gitlab_url=gitlab_url, custom_url=custom_url) @coprs_ns.route("/g///webhooks/") diff --git a/frontend/coprs_frontend/coprs/views/webhooks_ns/webhooks_general.py b/frontend/coprs_frontend/coprs/views/webhooks_ns/webhooks_general.py index 3e9eec1..3c9b417 100644 --- a/frontend/coprs_frontend/coprs/views/webhooks_ns/webhooks_general.py +++ b/frontend/coprs_frontend/coprs/views/webhooks_ns/webhooks_general.py @@ -226,8 +226,8 @@ class HookContentStorage(object): shutil.rmtree(self.tmp) -@webhooks_ns.route("/custom///", methods=["POST"]) -@webhooks_ns.route("/custom////", methods=["POST"]) +@webhooks_ns.route("/custom///", methods=["POST"]) +@webhooks_ns.route("/custom////", methods=["POST"]) @copr_id_and_uuid_required @package_name_required @skip_invalid_calls diff --git a/frontend/coprs_frontend/tests/test_webhooks.py b/frontend/coprs_frontend/tests/test_webhooks.py index c0908e9..d7580f5 100644 --- a/frontend/coprs_frontend/tests/test_webhooks.py +++ b/frontend/coprs_frontend/tests/test_webhooks.py @@ -6,7 +6,7 @@ from tests.coprs_test_case import CoprsTestCase class TestCustomWebhook(CoprsTestCase): def custom_post(self, data, token, copr_id, package_name=None): - url = "/webhooks/custom/{uuid}/{copr_id}/" + url = "/webhooks/custom/{copr_id}/{uuid}/" url = url.format(uuid=token, copr_id=copr_id) if package_name: url = "{0}{1}/".format(url, package_name) From 35278e9040b28f3c27fe2fea509af279b8bda4c0 Mon Sep 17 00:00:00 2001 From: Pavel Raiskup Date: Feb 19 2018 13:26:45 +0000 Subject: [PATCH 2/3] [cli][doc][frontend][python][rpmbuild] new custom source method Fixes: 185 --- diff --git a/cli/copr_cli/main.py b/cli/copr_cli/main.py index 6a0de33..7c37045 100644 --- a/cli/copr_cli/main.py +++ b/cli/copr_cli/main.py @@ -270,6 +270,21 @@ class Commands(object): return self.process_build(args, self.client.create_new_build_rubygems, data) @requires_api_auth + def action_build_custom(self, args): + """ + Method called when 'buildcustom' has been selected by the user. + + :param args: argparse arguments provided by the user + """ + data = { + 'script': ''.join(args.script.readlines()), + } + for arg in ['script_chroot', 'script_builddeps', + 'script_resultdir']: + data[arg] = getattr(args, arg) + return self.process_build(args, self.client.create_new_build_custom, data) + + @requires_api_auth def action_build_distgit(self, args): """ Method called when the 'buildfedpkg' action has been selected by the user. @@ -556,6 +571,23 @@ class Commands(object): result = self.client.edit_package_rubygems(ownername=ownername, projectname=projectname, **data) print(result.message) + @requires_api_auth + def action_add_or_edit_package_custom(self, args): + ownername, projectname = parse_name(args.copr) + data = { + "package_name": args.name, + "script": ''.join(args.script.readlines()), + "script_chroot": args.script_chroot, + "script_builddeps": args.script_builddeps, + "script_resultdir": args.script_resultdir, + "webhook_rebuild": ON_OFF_MAP[args.webhook_rebuild], + } + if args.create: + result = self.client.add_package_custom(ownername=ownername, projectname=projectname, **data) + else: + result = self.client.edit_package_custom(ownername=ownername, projectname=projectname, **data) + print(result.message) + def action_list_packages(self, args): ownername, projectname = parse_name(args.copr) data = { @@ -810,6 +842,22 @@ def setup_parser(): parser_distgit_args_parent.add_argument("--branch", metavar="BRANCH", dest="branch", help="Specify branch to be used") + parser_custom_args_parent = argparse.ArgumentParser(add_help=False) + parser_custom_args_parent.add_argument( + '--script', required=True, + type=argparse.FileType('r'), + help='text file (script) to be used to prepare the sources') + parser_custom_args_parent.add_argument( + '--script-chroot', + help='mock chroot to build sources for the SRPM in') + parser_custom_args_parent.add_argument( + '--script-builddeps', + help='space separated list of packages needed to build the sources') + parser_custom_args_parent.add_argument( + '--script-resultdir', + help='where SCRIPT generates the result, relatively to script\'s ' + '$PWD (defaults to \'.\')') + ######################################################### ### Build options ### ######################################################### @@ -846,6 +894,13 @@ def setup_parser(): help="Build gem from rubygems.org to a specified copr") parser_build_rubygems.set_defaults(func="action_build_rubygems") + # create the parser for the "buildcustom" command + parser_build_custom = subparsers.add_parser( + "buildcustom", + parents=[parser_custom_args_parent, parser_build_parent], + help="Build packages from SRPM generated by custom script") + parser_build_custom.set_defaults(func="action_build_custom") + # create the parser for the "buildfedpkg" command parser_build_distgit = subparsers.add_parser("buildfedpkg", parents=[parser_distgit_args_parent, parser_build_parent], help="DEPRECATED. Use SCM source type instead.") @@ -991,6 +1046,22 @@ def setup_parser(): parents=[parser_rubygems_args_parent, parser_add_or_edit_package_parent]) parser_edit_package_rubygems.set_defaults(func="action_add_or_edit_package_rubygems", create=False) + # Custom build method - edit/create package + parser_add_package_custom = subparsers.add_parser( + "add-package-custom", + help="Creates a new package where sources are built by custom script", + parents=[parser_custom_args_parent, parser_add_or_edit_package_parent]) + parser_add_package_custom.set_defaults( + func="action_add_or_edit_package_custom", + create=True) + parser_edit_package_custom = subparsers.add_parser( + "edit-package-custom", + help="Edits an existing Custom package", + parents=[parser_custom_args_parent, parser_add_or_edit_package_parent]) + parser_edit_package_custom.set_defaults( + func="action_add_or_edit_package_custom", + create=False) + # package listing parser_list_packages = subparsers.add_parser("list-packages", help="Returns list of packages in the given copr") diff --git a/doc/custom_source_method.rst b/doc/custom_source_method.rst new file mode 100644 index 0000000..bdd3113 --- /dev/null +++ b/doc/custom_source_method.rst @@ -0,0 +1,130 @@ +.. _custom_source_method: + +Coustom source method +===================== + +Build sources (for SRPM) by user-defined script. + +The idea behind the script is simple, when the script is run - it's only +mandatory output is specfile, plus optionally any other file needed to +successfully build a source RPM from that spec file (usually tarball(s), +patches, etc.). By default we expect that the script generates the files in +current working directory (resultdir='.'). + +Having turing-complete powers and Internet access - the script can do basically +anything to get all the source pieces together. The only limitation is that it +is executed under non-privileged user (the script is executed in mock chroot, +under 'mockbuild' user). This brings one major obstacle that you can not +install any RPM packages from within the script; if you *need* to have some +packages pre-installed, you need to specify them "declaratively" as a list of +(srpm)build-dependencies. The reasons for this design are that (a) it is easier +and and safer to develop scripts which don't require admin access, (b) it is +convenient to write "portable" scripts (even though the script is executed in +rpm-based mock chroot, the script itself can be easily written/tested on e.g. +Gentoo) and (c) it follows the usual workflows of maintainers (install packages +under root, and work the rest of the day as non-root) and mock workflow. + + +Required configuration for custom method +---------------------------------------- + +Basically you only have to specify **script** and **chroot** parameter. + +- **script** - scipt file content; written in any scripting language, but pay + attention to specify shebang properly, e.g. `#! /bin/sh` for (posix) shell + scripts. Also note that the interpreter might not be available by default, + you might have to request its installation via **builddeps** argument). + +- **chroot** - (mock) chroot where the script is executed. By default, the + `fedora-latest-x86_64` chroot is used, which represents the latest stable + or `branched `_ Fedora + version available in Copr at the time of the build request (e.g. + `fedora-27-x86_64` when `fedora-rawhide-x86_64` represents Fedora 28). + + +Optional parameters +------------------- + +- **builddeps** - space-separated list of packages which are pre-installed into + the build **chroot** (before the script is executed). + +- **resultdir** - where the **script** generates its output. By default, it is + assumed to be current working directory. + + +Webhook support +--------------- + +The only useful webhook for custom source method is the custom web-hook. +Because unlike other methods, custom method implementation doesn't itself pay +attention to webhook payload (json data used e.g. by GitLab to indicate what +type of event triggered the webhok call) nor there's any particular "clone url". + +With custom webhook, the payload parsing/analysis is left to the **script** (in +other words it is user's responsibility). For that purpose custom webhook +handler dumps the webhook payload (if any) into file `$PWD/hook_payload` file +(from the **script** POV). + +For example, if GitLab's *merge-request* event from *contributor/project.git* to +*owner/repo.git* "calls" the custom webhook for package *foo*, the **script** +has to parse the `hook_payload` file to detect *the fact* that +*contributor/project.git* should be cloned (instead of *owner/repo.git*) to +generate the sources. + +Since this all is in user's hands, it is not technically incorrect to have empty +hook payload, e.g. it is valid to call `curl -X POST ` to +trigger the custom source build method. + + +Examples +-------- + +- Trivial example (only spec file):: + + $ cat script + #! /bin/sh -x + curl https://praiskup.fedorapeople.org/quick-package.spec -O + + $ copr add-package-custom PROJECT \ + --name quick-package \ + --script script + + $ copr build-package --name quick-package PROJECT # trigger the build + + +- Simple example with Python package with git submodules and in-tree sources:: + + $ cat script + #! /bin/sh + + mkdir -p results + resultdir=$(readlink -f results) + + set -x # verbose output + set -e # fail the whole script if some command fails + + # obtain the source code + git clone https://github.com/praiskup/resalloc --recursive --depth 1 + cd resalloc + + # 1. generate source tarball into resultdir + python setup.py sdist -d "$resultdir" + + # 2. copy the spec file into resultdir, change the release number so each build + # has unique name-version-release triplet + cd rpm + release='~'$(date +"%Y%m%d_%H%M%S") + sed "s/\(^Release:[[:space:]]*[[:digit:]]\+\)/\1$release/" resalloc.spec \ + > "$resultdir"/resalloc.spec + + # 3. copy other sources + cp *.service "$resultdir" + + $ copr add-package-custom PROJECT \ + --name resalloc \ + --script script \ + --script-resultdir results \ + --script-builddeps 'git' \ + --script-chroot fedora-rawhide-x86_64 + + $ copr build-package --name resalloc PROJECT # trigger the build diff --git a/doc/user_documentation.rst b/doc/user_documentation.rst index 8a163c7..7b84824 100644 --- a/doc/user_documentation.rst +++ b/doc/user_documentation.rst @@ -115,6 +115,14 @@ Similarly to PyPI source type, this allows building gems from ``_. +Custom (script) +^^^^^^^^^^^^^^^ + +This source type uses a user-defined script to generate sources (which are later +used to create SRPM). For more info, have a look at +:ref:`custom_source_method`. + + GitHub Webhooks --------------- diff --git a/frontend/coprs_frontend/coprs/filters.py b/frontend/coprs_frontend/coprs/filters.py index e0a7d17..f891f98 100644 --- a/frontend/coprs_frontend/coprs/filters.py +++ b/frontend/coprs_frontend/coprs/filters.py @@ -220,6 +220,7 @@ def build_source_description(state): "scm": "Build from an SCM repository", "pypi": "Build from PyPI", "rubygems": "Build from RubyGems", + "custom": "Custom build method", } return description_map.get(state, "") diff --git a/frontend/coprs_frontend/coprs/forms.py b/frontend/coprs_frontend/coprs/forms.py index 1cd6092..85ef6d2 100644 --- a/frontend/coprs_frontend/coprs/forms.py +++ b/frontend/coprs_frontend/coprs/forms.py @@ -41,6 +41,8 @@ def get_package_form_cls_by_source_type_text(source_type_text): return PackageFormTito # deprecated elif source_type_text == 'mock_scm': return PackageFormMock # deprecated + elif source_type_text == "custom": + return PackageFormCustom else: raise exceptions.UnknownSourceTypeException("Invalid source type") @@ -574,6 +576,60 @@ class PackageFormDistGit(BasePackageForm): }) +class PackageFormCustom(BasePackageForm): + script = wtforms.TextAreaField( + "Script", + validators=[ + wtforms.validators.DataRequired(), + wtforms.validators.Length( + max=4096, + message="Maximum script size is 4kB"), + ], + ) + + builddeps = wtforms.StringField( + "Build dependencies", + validators=[wtforms.validators.Optional()]) + + chroot = wtforms.SelectField( + 'Mock chroot', + choices=[], + default='fedora-latest-x86_64', + ) + + resultdir = wtforms.StringField( + "Result directory", + validators=[wtforms.validators.Optional()]) + + def __init__(self, *args, **kwargs): + super(PackageFormCustom, self).__init__(*args, **kwargs) + chroot_objects = models.MockChroot.query.filter(models.MockChroot.is_active).all() + + chroots = [c.name for c in chroot_objects] + chroots.sort() + chroots = [(name, name) for name in chroots] + + arches = set() + for ch in chroot_objects: + if ch.os_release == 'fedora': + arches.add(ch.arch) + + self.chroot.choices = [] + if arches: + self.chroot.choices += [('fedora-latest-' + l, 'fedora-latest-' + l) for l in arches] + + self.chroot.choices += chroots + + @property + def source_json(self): + return json.dumps({ + "script": self.script.data, + "chroot": self.chroot.data, + "builddeps": self.builddeps.data, + "resultdir": self.resultdir.data, + }) + + class RebuildAllPackagesFormFactory(object): def __new__(cls, active_chroots, package_names): form_cls = BaseBuildFormFactory(active_chroots, FlaskForm) @@ -677,6 +733,11 @@ class BuildFormUploadFactory(object): return form +class BuildFormCustomFactory(object): + def __new__(cls, active_chroots): + return BaseBuildFormFactory(active_chroots, PackageFormCustom) + + class BuildFormUrlFactory(object): def __new__(cls, active_chroots): form = BaseBuildFormFactory(active_chroots, FlaskForm) diff --git a/frontend/coprs_frontend/coprs/helpers.py b/frontend/coprs_frontend/coprs/helpers.py index ffb0606..a60b078 100644 --- a/frontend/coprs_frontend/coprs/helpers.py +++ b/frontend/coprs_frontend/coprs/helpers.py @@ -114,6 +114,7 @@ class BuildSourceEnum(with_metaclass(EnumType, object)): "pypi": 5, # package_name, version, python_versions "rubygems": 6, # gem_name "scm": 8, # type, clone_url, committish, subdirectory, spec, srpm_build_method + "custom": 9, # user-provided script to build sources } # The same enum is also in distgit's helpers.py diff --git a/frontend/coprs_frontend/coprs/logic/builds_logic.py b/frontend/coprs_frontend/coprs/logic/builds_logic.py index 6df1930..573cd73 100644 --- a/frontend/coprs_frontend/coprs/logic/builds_logic.py +++ b/frontend/coprs_frontend/coprs/logic/builds_logic.py @@ -483,6 +483,31 @@ GROUP BY return cls.create_new(user, copr, source_type, source_json, chroot_names, **build_options) @classmethod + def create_new_from_custom(cls, user, copr, + script, script_chroot=None, script_builddeps=None, + script_resultdir=None, chroot_names=None, **kwargs): + """ + :type user: models.User + :type copr: models.Copr + :type script: str + :type script_chroot: str + :type script_builddeps: str + :type script_resultdir: str + :type chroot_names: List[str] + :rtype: models.Build + """ + source_type = helpers.BuildSourceEnum("custom") + source_dict = { + 'script': script, + 'chroot': script_chroot, + 'builddeps': script_builddeps, + 'resultdir': script_resultdir, + } + + return cls.create_new(user, copr, source_type, json.dumps(source_dict), + chroot_names, **kwargs) + + @classmethod def create_new_from_upload(cls, user, copr, f_uploader, orig_filename, chroot_names=None, **build_options): """ diff --git a/frontend/coprs_frontend/coprs/models.py b/frontend/coprs_frontend/coprs/models.py index 8b60976..1c4cfa2 100644 --- a/frontend/coprs_frontend/coprs/models.py +++ b/frontend/coprs_frontend/coprs/models.py @@ -505,6 +505,17 @@ class Build(db.Model, helpers.Serializer): db.Index('build_order', "is_background", "id"), db.Index('build_filter', "source_type", "canceled")) + def __init__(self, *args, **kwargs): + if kwargs.get('source_type') == helpers.BuildSourceEnum("custom"): + source_dict = json.loads(kwargs['source_json']) + if 'fedora-latest' in source_dict['chroot']: + arch = source_dict['chroot'].split('-')[2] + source_dict['chroot'] = \ + MockChroot.latest_fedora_branched_chroot(arch=arch).name + kwargs['source_json'] = json.dumps(source_dict) + + super(Build, self).__init__(*args, **kwargs) + id = db.Column(db.Integer, primary_key=True) # single url to the source rpm, should not contain " ", "\n", "\t" pkgs = db.Column(db.Text) @@ -834,6 +845,16 @@ class MockChroot(db.Model, helpers.Serializer): distgit_branch = db.relationship("DistGitBranch", backref=db.backref("chroots")) + @classmethod + def latest_fedora_branched_chroot(cls, arch='x86_64'): + return (cls.query + .filter(cls.is_active == True) + .filter(cls.os_release == 'fedora') + .filter(cls.os_version != 'rawhide') + .filter(cls.arch == arch) + .order_by(cls.os_version.desc()) + .first()) + @property def name(self): """ diff --git a/frontend/coprs_frontend/coprs/templates/coprs/detail/_builds_forms.html b/frontend/coprs_frontend/coprs/templates/coprs/detail/_builds_forms.html index 5f97215..1525997 100644 --- a/frontend/coprs_frontend/coprs/templates/coprs/detail/_builds_forms.html +++ b/frontend/coprs_frontend/coprs/templates/coprs/detail/_builds_forms.html @@ -1,4 +1,5 @@ {% from "_helpers.html" import render_field, render_form_errors, copr_url, render_pypi_python_versions_field, render_additional_build_options, render_srpm_build_method_box %} +{% from "coprs/detail/_method_forms.html" import copr_method_form_fileds_custom %} {# This file contains forms for the "New Build" action @@ -156,6 +157,14 @@ {% endmacro %} +{% macro copr_build_form_custom(form, view, copr) %} + {{ copr_build_form_begin(form, view, copr) }} + {{ source_description('Provide custom script to build sources.')}} + {{ copr_method_form_fileds_custom(form) }} + {{ copr_build_form_end(form, view, copr) }} +{% endmacro %} + + {% macro copr_build_form_rebuild(form, view, copr, build) %} {{ copr_build_form_begin(form, view, copr, build, hide_panels=True) }} {{ copr_build_form_end(form, view, copr, hide_panels=True) }} diff --git a/frontend/coprs_frontend/coprs/templates/coprs/detail/_method_forms.html b/frontend/coprs_frontend/coprs/templates/coprs/detail/_method_forms.html new file mode 100644 index 0000000..f621d72 --- /dev/null +++ b/frontend/coprs_frontend/coprs/templates/coprs/detail/_method_forms.html @@ -0,0 +1,22 @@ +{% from "_helpers.html" import render_field %} +{% macro copr_method_form_fileds_custom(form) %} + {{ render_field( + form.script, + rows=5, + style='font-family: monospace;', + placeholder="""#! /bin/sh -x +curl https://example.com/package.spec -O +curl https://example.com/tarball.tar.gz -O +""", + info="write a script that generates spec and sources... (Internet ON, non-root UID)" + )}} + {{ render_field( + form.chroot, + placeholder="fedora-latest-x86_64", + info="what chroot to run the script in") }} + {{ render_field(form.builddeps, placeholder="Optional - space-separated list of packages", + info="packages that the script requires for its execution" ) }} + {{ render_field(form.resultdir, placeholder="Optional - directory where SCRIPT generates sources", + info="path relative to the script's current + working directory") }} +{% endmacro %} diff --git a/frontend/coprs_frontend/coprs/templates/coprs/detail/_package_forms.html b/frontend/coprs_frontend/coprs/templates/coprs/detail/_package_forms.html index cb3c2cc..9e98c48 100644 --- a/frontend/coprs_frontend/coprs/templates/coprs/detail/_package_forms.html +++ b/frontend/coprs_frontend/coprs/templates/coprs/detail/_package_forms.html @@ -1,4 +1,5 @@ {% from "_helpers.html" import render_field, render_form_errors, copr_url, render_pypi_python_versions_field, render_additional_build_options, render_srpm_build_method_box %} +{% from "coprs/detail/_method_forms.html" import copr_method_form_fileds_custom %} {% macro copr_package_form_begin(form, view, copr, package) %} {{ render_form_errors(form) }} @@ -86,6 +87,15 @@ {{ copr_package_form_end(form, package, 'rubygems') }} {% endmacro %} + +{% macro copr_package_form_custom(form, view, copr, package) %} + {{ copr_package_form_begin(form, view, copr, package) }} + {{ copr_method_form_fileds_custom(form) }} + {{ render_webhook_rebuild(form) }} + {{ copr_package_form_end(form, package, 'custom') }} +{% endmacro %} + + {% macro copr_package_form_scm(form, view, copr, package) %} {{ copr_package_form_begin(form, view, copr, package) }} diff --git a/frontend/coprs_frontend/coprs/templates/coprs/detail/_package_helpers.html b/frontend/coprs_frontend/coprs/templates/coprs/detail/_package_helpers.html index de988c9..4927596 100644 --- a/frontend/coprs_frontend/coprs/templates/coprs/detail/_package_helpers.html +++ b/frontend/coprs_frontend/coprs/templates/coprs/detail/_package_helpers.html @@ -3,6 +3,7 @@ copr_package_form_scm, copr_package_form_pypi, copr_package_form_rubygems, + copr_package_form_custom, with context %} @@ -20,6 +21,7 @@ with context %} {{ nav_element("scm", "SCM", copr_url(view, copr, source_type_text="scm", **kwargs)) }} {{ nav_element("pypi", "PyPI", copr_url(view, copr, source_type_text="pypi", **kwargs)) }} {{ nav_element("rubygems", "RubyGems", copr_url(view, copr, source_type_text="rubygems", **kwargs)) }} + {{ nav_element("custom", "Custom", copr_url(view, copr, source_type_text="custom", **kwargs)) }} {% endmacro %} @@ -33,6 +35,9 @@ with context %} {% elif source_type_text == "rubygems" %} {{ copr_package_form_rubygems(form_rubygems, view, copr, package) }} + {% elif source_type_text == "custom" %} + {{ copr_package_form_custom(form_custom, view, copr, package) }} + {% else %}

    Wrong source type

    {% endif %} diff --git a/frontend/coprs_frontend/coprs/templates/coprs/detail/add_build.html b/frontend/coprs_frontend/coprs/templates/coprs/detail/add_build.html index 4e0d39b..9dfa42f 100644 --- a/frontend/coprs_frontend/coprs/templates/coprs/detail/add_build.html +++ b/frontend/coprs_frontend/coprs/templates/coprs/detail/add_build.html @@ -38,6 +38,7 @@ {{ nav_element("scm", "SCM", copr_url('coprs_ns.copr_add_build_scm', copr)) }} {{ nav_element("pypi", "PyPI", copr_url('coprs_ns.copr_add_build_pypi', copr)) }} {{ nav_element("rubygems", "RubyGems", copr_url('coprs_ns.copr_add_build_rubygems', copr)) }} + {{ nav_element("custom", "Custom", copr_url('coprs_ns.copr_add_build_custom', copr)) }} diff --git a/frontend/coprs_frontend/coprs/templates/coprs/detail/add_build/custom.html b/frontend/coprs_frontend/coprs/templates/coprs/detail/add_build/custom.html new file mode 100644 index 0000000..e800e0d --- /dev/null +++ b/frontend/coprs_frontend/coprs/templates/coprs/detail/add_build/custom.html @@ -0,0 +1,11 @@ +{% extends "coprs/detail/add_build.html" %} + +{% from "coprs/detail/_builds_forms.html" import copr_build_form_custom with context %} + +{% set add_build_tab = "custom" %} + +{% block build_form %} + +{{ copr_build_form_custom(form, view, copr) }} + +{% endblock %} diff --git a/frontend/coprs_frontend/coprs/views/api_ns/api_general.py b/frontend/coprs_frontend/coprs/views/api_ns/api_general.py index de8f84b..f1684ed 100755 --- a/frontend/coprs_frontend/coprs/views/api_ns/api_general.py +++ b/frontend/coprs_frontend/coprs/views/api_ns/api_general.py @@ -483,6 +483,25 @@ def copr_new_build_rubygems(copr): return process_creating_new_build(copr, form, create_new_build) +@api_ns.route("/coprs///new_build_custom/", methods=["POST"]) +@api_login_required +@api_req_with_copr +def copr_new_build_custom(copr): + form = forms.BuildFormCustomFactory(copr.active_chroots)(csrf_enabled=False) + def create_new_build(): + return BuildsLogic.create_new_from_custom( + flask.g.user, + copr, + form.script.data, + form.chroot.data, + form.builddeps.data, + form.resultdir.data, + chroot_names=form.selected_chroots, + background=form.background.data, + ) + return process_creating_new_build(copr, form, create_new_build) + + @api_ns.route("/coprs///new_build_scm/", methods=["POST"]) @api_login_required @api_req_with_copr diff --git a/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_builds.py b/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_builds.py index 5b70700..e1cda26 100644 --- a/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_builds.py +++ b/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_builds.py @@ -5,6 +5,7 @@ import os import shutil import tempfile +from functools import wraps from werkzeug import secure_filename from coprs import app @@ -351,6 +352,52 @@ def process_new_build_rubygems(copr, add_view, url_on_success): form = forms.BuildFormRubyGemsFactory(copr.active_chroots)() return process_new_build(copr, form, factory, render_add_build_rubygems, add_view, url_on_success) +############################### Custom builds ############################### + +@coprs_ns.route("/g///new_build_custom/", methods=["POST"]) +@coprs_ns.route("///new_build_custom/", methods=["POST"]) +@login_required +@req_with_copr +def copr_new_build_custom(copr): + """ Handle the build request and redirect back. """ + + # TODO: parametric decorator for this view && url_on_success + view = 'coprs_ns.copr_new_build_custom' + url_on_success = helpers.copr_url('coprs_ns.copr_add_build_custom', copr) + + def factory(**build_options): + BuildsLogic.create_new_from_custom( + flask.g.user, + copr, + form.script.data, + form.chroot.data, + form.builddeps.data, + form.resultdir.data, + chroot_names=form.selected_chroots, + **build_options + ) + + form = forms.BuildFormCustomFactory(copr.active_chroots)() + + return process_new_build(copr, form, factory, render_add_build_custom, + view, url_on_success) + + + +@coprs_ns.route("/g///add_build_custom/") +@coprs_ns.route("///add_build_custom/") +@login_required +@req_with_copr +def copr_add_build_custom(copr, form=None): + return render_add_build_custom(copr, form, + 'coprs_ns.copr_new_build_custom') + +def render_add_build_custom(copr, form, view, package=None): + if not form: + form = forms.BuildFormCustomFactory(copr.active_chroots)() + return flask.render_template("coprs/detail/add_build/custom.html", + copr=copr, form=form, view=view) + ################################ Upload builds ################################ diff --git a/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_packages.py b/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_packages.py index 01d1672..45690bd 100644 --- a/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_packages.py +++ b/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_packages.py @@ -8,7 +8,7 @@ from coprs import forms from coprs import helpers from coprs.models import Package, Build from coprs.views.coprs_ns import coprs_ns -from coprs.views.coprs_ns.coprs_builds import render_add_build_scm, render_add_build_pypi +from coprs.views.coprs_ns.coprs_builds import render_add_build_scm, render_add_build_pypi, render_add_build_custom from coprs.views.misc import login_required, page_not_found, req_with_copr, req_with_copr from coprs.logic.complex_logic import ComplexLogic from coprs.logic.packages_logic import PackagesLogic @@ -107,6 +107,10 @@ def copr_rebuild_package(copr, package_name): form = forms.BuildFormPyPIFactory f = render_add_build_pypi view_suffix = "_pypi" + elif package.source_type_text == "custom": + form = forms.BuildFormCustomFactory + f = render_add_build_custom + view_suffix = "_custom" else: flask.flash("Package {} has not the default source which is required for rebuild. Please configure some source" .format(package_name, copr.full_name)) @@ -127,6 +131,7 @@ def copr_add_package(copr, source_type_text="scm", **kwargs): "scm": forms.PackageFormScm(), "pypi": forms.PackageFormPyPI(), "rubygems": forms.PackageFormRubyGems(), + "custom": forms.PackageFormCustom(), } if "form" in kwargs: @@ -135,7 +140,8 @@ def copr_add_package(copr, source_type_text="scm", **kwargs): return flask.render_template("coprs/detail/add_package.html", copr=copr, package=None, source_type_text=source_type_text, view="coprs_ns.copr_new_package", form_scm=form["scm"], form_pypi=form["pypi"], - form_rubygems=form["rubygems"]) + form_rubygems=form["rubygems"], + form_custom=form['custom']) @coprs_ns.route("///package/new/", methods=["POST"]) @@ -167,6 +173,7 @@ def copr_edit_package(copr, package_name, source_type_text=None, **kwargs): "scm": forms.PackageFormScm, "pypi": forms.PackageFormPyPI, "rubygems": forms.PackageFormRubyGems, + "custom": forms.PackageFormCustom, } form = {k: v(formdata=None) for k, v in form_classes.items()} @@ -178,7 +185,8 @@ def copr_edit_package(copr, package_name, source_type_text=None, **kwargs): return flask.render_template("coprs/detail/edit_package.html", package=package, copr=copr, source_type_text=source_type_text, view="coprs_ns.copr_edit_package", form_scm=form["scm"], form_pypi=form["pypi"], - form_rubygems=form["rubygems"]) + form_rubygems=form["rubygems"], + form_custom=form['custom']) @coprs_ns.route("///package//edit/", methods=["POST"]) diff --git a/python/copr/client/client.py b/python/copr/client/client.py index 22cc04a..9f888e6 100644 --- a/python/copr/client/client.py +++ b/python/copr/client/client.py @@ -69,6 +69,7 @@ SOURCE_TYPE_MOCK_SCM = 'mock_scm' SOURCE_TYPE_PYPI = 'pypi' SOURCE_TYPE_RUBYGEMS = 'rubygems' SOURCE_TYPE_SCM = 'scm' +SOURCE_TYPE_CUSTOM = 'custom' class CoprClient(UnicodeMixin): """ Main interface to the copr service @@ -588,6 +589,47 @@ class CoprClient(UnicodeMixin): return self.process_creating_new_build(projectname, data, api_endpoint, username, chroots, background=background) + + def create_new_build_custom(self, projectname, + script, script_chroot=None, script_builddeps=None, + script_resultdir=None, + username=None, timeout=None, memory=None, chroots=None, + background=False, progress_callback=None): + """ Creates new build with Custom source build method. + + :param projectname: name of Copr project (without user namespace) + :param script: script to execute to generate sources + :param script_chroot: [optional] what chroot to use to generate + sources (defaults to fedora-latest-x86_64) + :param script_builddeps: [optional] list of script's dependencies + :param script_resultdir: [optional] where script generates results + (relative to cwd) + :param username: [optional] use alternative username + :param timeout: [optional] build timeout + :param memory: [optional] amount of required memory for build process + :param chroots: [optional] build only with given chroots + :param background: [optional] mark the build as a background job. + :param progress_callback: [optional] a function that received a + MultipartEncoderMonitor instance for each chunck of uploaded data + + :return: :py:class:`~.responses.CoprResponse` with additional fields: + + - **builds_list**: list of :py:class:`~.responses.BuildWrapper` + """ + data = { + "memory_reqs": memory, + "timeout": timeout, + "script": script, + "chroot": script_chroot, + "builddeps": script_builddeps, + "resultdir": script_resultdir, + } + + api_endpoint = "new_build_custom" + return self.process_creating_new_build(projectname, data, api_endpoint, username, + chroots, background=background) + + def create_new_build_distgit(self, projectname, clone_url, branch=None, username=None, timeout=None, memory=None, chroots=None, background=False, progress_callback=None): """ Creates new build from a dist-git repository @@ -818,6 +860,46 @@ class CoprClient(UnicodeMixin): }) return response + def edit_package_custom(self, package_name, projectname, + script, script_chroot=None, script_builddeps=None, + script_resultdir=None, + ownername=None, webhook_rebuild=None): + + request_url = self.get_package_edit_url(ownername, projectname, + package_name, SOURCE_TYPE_CUSTOM) + + data = { + "package_name": package_name, + "script": script, + "builddeps": script_builddeps, + "resultdir": script_resultdir, + "chroot": script_chroot, + } + if webhook_rebuild != None: + data['webhook_rebuild'] = 'y' if webhook_rebuild else '' + + response = self.process_package_action(request_url, ownername, projectname, data) + return response + + def add_package_custom(self, package_name, projectname, + script, script_chroot=None, script_builddeps=None, + script_resultdir=None, + ownername=None, webhook_rebuild=None): + + request_url = self.get_package_add_url(ownername, projectname, + SOURCE_TYPE_CUSTOM) + response = self.process_package_action(request_url, ownername, + projectname, data={ + "package_name": package_name, + "script": script, + "builddeps": script_builddeps, + "resultdir": script_resultdir, + "chroot": script_chroot, + "webhook_rebuild": 'y' if webhook_rebuild else '', + }, + ) + return response + def process_package_action(self, request_url, ownername, projectname, data, fetch_functor=None): if not ownername: ownername = self.username diff --git a/rpmbuild/bin/copr-sources-custom b/rpmbuild/bin/copr-sources-custom new file mode 100755 index 0000000..38df51c --- /dev/null +++ b/rpmbuild/bin/copr-sources-custom @@ -0,0 +1,107 @@ +#! /bin/env python + +import os +import subprocess +import argparse +import logging +import pipes + +DEFAULT_WORKDIR = '/builddir/copr-sources-custom' + +logging.basicConfig(level=logging.DEBUG) +log = logging.getLogger(__name__) + +description = """ +Generate spec + patches + tarballs using custom script in mock chroot. +""".strip() + +parser = argparse.ArgumentParser( + description = description +) +parser.add_argument( + "-r", "--mock-config", "--root", + dest='config', + required=True, + help="mock config reference (alternative to '-r/--root' option in mock)" +) +parser.add_argument( + "--script", + required=True, + type=argparse.FileType('r'), + help="file to be copyied into mock chroot and executed" +) +parser.add_argument( + "--hook-payload-file", + type=argparse.FileType('r'), + help="file with 'webhook payload content', it is going to be copied " + "into working directory as 'hook_payload' file", +) +parser.add_argument( + "--builddeps", + help="space separated list of build dependencies (packages)" +) +parser.add_argument( + "--resultdir", + default=".", + help="directory were the SCRIPT generates the sources within mock chroot" +) +parser.add_argument( + "--workdir", + default=DEFAULT_WORKDIR, + help="execute the SCRIPT from with CWD=WORKDIR (within mock chroot)" +) + + +def run_cmd(command, **kwargs): + log.info("running command: " + ' '.join([pipes.quote(x) for x in command])) + return subprocess.check_call(command, **kwargs) + + +if __name__ == "__main__": + args = parser.parse_args() + + user = 'mockbuild' + + # Where the script is run from within mock chroot. + workdir = os.path.normpath(args.workdir) + + # Where the script's result will be copied from, by default + # equal to workdir. + resultdir = os.path.normpath(os.path.join(workdir, args.resultdir)) + + log.info("Working in '{0}'".format(workdir)) + log.info("Results should be created in '{0}'".format(resultdir)) + + mock = ['mock', '-r', args.config] + + run_cmd(mock + ['--init']) + + if args.builddeps: + pkgs = args.builddeps.split() + run_cmd(mock + ['--install'] + pkgs) + + run_cmd(mock + ['--shell', 'cat - > /script'], stdin=args.script) + + setup_cmd = 'set -ex; chmod a+x /script;' + + prep_dir_template = ' rm -rf {d}; mkdir -p {d}; chown {user} {d};' + setup_cmd += prep_dir_template.format( + user=user, + d=pipes.quote(workdir), + ) + + run_cmd(mock + ['--chroot', setup_cmd]) + + if args.hook_payload_file: + payload_file_inner = "{0}/hook_payload".format(pipes.quote(workdir)) + run_cmd(mock + ['--shell', 'cat - > ' + payload_file_inner], + stdin=args.hook_payload_file) + run_cmd(mock + ['--shell', 'chmod a+r ' + payload_file_inner]) + + cmd = 'set -xe ; cd {workdir} ; {env} /script'.format( + workdir=pipes.quote(workdir), + env='COPR_RESULTDIR=' + pipes.quote(resultdir), + ) + cmd = pipes.quote(cmd) + + run_cmd(mock + ['--chroot', 'su - {0} -c {1}'.format(user, cmd)]) diff --git a/rpmbuild/copr-rpmbuild.spec b/rpmbuild/copr-rpmbuild.spec index 2826523..1364ec0 100644 --- a/rpmbuild/copr-rpmbuild.spec +++ b/rpmbuild/copr-rpmbuild.spec @@ -59,6 +59,7 @@ install -m 644 make_srpm_mock.cfg %{buildroot}%{_sysconfdir}/copr-rpmbuild/make_ install -d %{buildroot}%{_mandir}/man1 install -p -m 644 man/copr-rpmbuild.1 %{buildroot}/%{_mandir}/man1/ +install -p -m 755 bin/copr-sources-custom %buildroot%_bindir %py3_install @@ -68,6 +69,7 @@ install -p -m 644 man/copr-rpmbuild.1 %{buildroot}/%{_mandir}/man1/ %{python3_sitelib}/* %{_bindir}/copr-rpmbuild +%{_bindir}/copr-sources-custom %{_mandir}/man1/copr-rpmbuild.1* %dir %attr(0775, root, mock) %{_sharedstatedir}/copr-rpmbuild diff --git a/rpmbuild/copr_rpmbuild/helpers.py b/rpmbuild/copr_rpmbuild/helpers.py index bd11c10..2100ecc 100644 --- a/rpmbuild/copr_rpmbuild/helpers.py +++ b/rpmbuild/copr_rpmbuild/helpers.py @@ -23,6 +23,7 @@ class SourceType: PYPI = 5 RUBYGEMS = 6 SCM = 8 + CUSTOM = 9 def cmd_debug(result): @@ -189,3 +190,26 @@ def extract_srpm(srpm_path, destination): subprocess.check_output(cmd, shell=True) finally: os.chdir(cwd) + + +def build_srpm(srcdir, destdir): + cmd = [ + 'rpmbuild', '-bs', + '--define', '_sourcedir ' + srcdir, + '--define', '_rpmdir ' + srcdir, + '--define', '_builddir ' + srcdir, + '--define', '_specdir ' + srcdir, + '--define', '_srcrpmdir ' + destdir, + ] + + specfiles = glob.glob(os.path.join(srcdir, '*.spec')) + if len(specfiles) == 0: + raise RuntimeError("no spec file available") + + if len(specfiles) > 1: + raise RuntimeError("too many specfiles: {0}".format( + ', '.join(specfiles) + )) + + cmd += [specfiles[0]] + run_cmd(cmd) diff --git a/rpmbuild/copr_rpmbuild/providers/__init__.py b/rpmbuild/copr_rpmbuild/providers/__init__.py index d830da7..47682ba 100644 --- a/rpmbuild/copr_rpmbuild/providers/__init__.py +++ b/rpmbuild/copr_rpmbuild/providers/__init__.py @@ -3,6 +3,7 @@ from .rubygems import RubyGemsProvider from .pypi import PyPIProvider from .spec import SpecUrlProvider from .scm import ScmProvider +from .custom import CustomProvider __all__ = [RubyGemsProvider, PyPIProvider, @@ -17,6 +18,7 @@ def factory(source_type): SourceType.RUBYGEMS: RubyGemsProvider, SourceType.PYPI: PyPIProvider, SourceType.SCM: ScmProvider, + SourceType.CUSTOM: CustomProvider, }[source_type] except KeyError: raise RuntimeError("No provider associated with this source type") diff --git a/rpmbuild/copr_rpmbuild/providers/custom.py b/rpmbuild/copr_rpmbuild/providers/custom.py new file mode 100644 index 0000000..66cca58 --- /dev/null +++ b/rpmbuild/copr_rpmbuild/providers/custom.py @@ -0,0 +1,94 @@ +import os +import json +import logging +import shutil +import tempfile +import subprocess +import glob +import requests + +from copr_rpmbuild import helpers +from .base import Provider + + +log = logging.getLogger("__main__") + + +class CustomProvider(Provider): + chroot = 'fedora-rawhide-x86_64' + builddeps = None + file_script = None + inner_resultdir = None + inner_workdir = '/workdir' + hook_payload_url = None + + workdir = None + + def __init__(self, source_json, outdir, config): + super(CustomProvider, self).__init__(source_json, outdir, config) + + self.outdir = outdir + self.chroot = source_json.get('chroot') + self.inner_resultdir = source_json.get('resultdir') + self.builddeps = source_json.get('builddeps') + + if 'hook_data' in source_json: + self.hook_payload_url = "{server}/tmp/{tmp}/hook_payload".format( + server=config.get("main", "frontend_url"), + tmp=source_json['tmp'], + ) + + self.workdir = outdir + self.file_script = os.path.join(self.workdir, 'script') + with open(self.file_script, 'w') as script: + script.write(source_json['script']) + + + def produce_srpm(self): + mock_config_file = os.path.join(self.outdir, 'mock-config.cfg') + + with open(mock_config_file, 'w') as f: + # Enable network. + f.write("include('/etc/mock/{0}.cfg')\n".format(self.chroot)) + f.write("config_opts['rpmbuild_networking'] = True\n") + f.write("config_opts['use_host_resolv'] = True\n") + + cmd = [ + 'unbuffer', + 'copr-sources-custom', + '--workdir', self.inner_workdir, + '--mock-config', mock_config_file, + '--script', self.file_script, + ] + if self.builddeps: + cmd += ['--builddeps', self.builddeps] + + if self.hook_payload_url: + chunk_size = 1024 + hook_payload_file = os.path.join(self.outdir, 'hook_payload') + response = requests.get(self.hook_payload_url, stream=True) + response.raise_for_status() + + with open(hook_payload_file, 'wb') as payload_file: + for chunk in response.iter_content(chunk_size): + payload_file.write(chunk) + + cmd += ['--hook-payload-file', hook_payload_file] + + inner_resultdir = self.inner_workdir + if self.inner_resultdir: + # User wishes to re-define resultdir. + cmd += ['--resultdir', self.inner_resultdir] + inner_resultdir = os.path.normpath(os.path.join( + self.inner_workdir, self.inner_resultdir)) + + # prepare the sources + helpers.run_cmd(cmd) + + # copy the sources out into workdir + mock = ['mock', '-r', mock_config_file] + + srpm_srcdir = os.path.join(self.workdir, 'srcdir') + helpers.run_cmd(mock + ['--copyout', inner_resultdir, srpm_srcdir]) + helpers.build_srpm(srpm_srcdir, self.outdir) + shutil.rmtree(srpm_srcdir) From 176e161baf290811758c473d4c2fce40af0ae640 Mon Sep 17 00:00:00 2001 From: Jakub Kadlčík Date: Feb 19 2018 13:26:45 +0000 Subject: [PATCH 3/3] [beaker-tests] test the new custom method --- diff --git a/beaker-tests/Sanity/copr-cli-basic-operations/files/quick-package.sh b/beaker-tests/Sanity/copr-cli-basic-operations/files/quick-package.sh new file mode 100755 index 0000000..ce8ed62 --- /dev/null +++ b/beaker-tests/Sanity/copr-cli-basic-operations/files/quick-package.sh @@ -0,0 +1,49 @@ +#! /bin/sh -x + +set -e + +generate_specfile() +{ + test -n "$DESTDIR" && mkdir -p "$DESTDIR" + + test -n "$BUILDDEPS" && { + for i in $BUILDDEPS; do + rpm -q $i + done + } + + if ${HOOK_PAYLOAD-false}; then + test -f hook_payload + test "$(cat hook_payload)" = "{\"a\": \"b\"}" + else + ! test -f hook_payload + fi + +cat > "${DESTDIR-.}"/quick-package.spec <<\EOF +Name: quick-package +Version: 0 +Release: 0%{?dist} +Summary: dummy package +License: GPL +URL: http://example.com/ + +%{!?_pkgdocdir: %global _pkgdocdir %{_docdir}/%{name}-%{version}} + +%description +nothing + + +%install +mkdir -p $RPM_BUILD_ROOT/%{_pkgdocdir} +echo "this does nothing" > $RPM_BUILD_ROOT/%{_pkgdocdir}/README + + +%files +%doc %{_pkgdocdir}/README + + +%changelog +* Thu Jun 05 2014 Pavel Raiskup - 0-1 +- does nothing! +EOF +} diff --git a/beaker-tests/Sanity/copr-cli-basic-operations/runtest-custom-method.sh b/beaker-tests/Sanity/copr-cli-basic-operations/runtest-custom-method.sh new file mode 100755 index 0000000..8598396 --- /dev/null +++ b/beaker-tests/Sanity/copr-cli-basic-operations/runtest-custom-method.sh @@ -0,0 +1,216 @@ +#!/bin/bash + +. /usr/bin/rhts-environment.sh || exit 1 +. /usr/share/beakerlib/beakerlib.sh || exit 1 + +export RESULTDIR=`mktemp -d` + +export TESTPATH="$( builtin cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +export IN=$TESTPATH/action-tasks.json +export OUT=$TESTPATH/action-results.out.json + +if [[ ! $FRONTEND_URL ]]; then + FRONTEND_URL="http://copr-fe-dev.cloud.fedoraproject.org" +fi + + +NAME_VAR="TEST$(date +%s)" + + +parse_build_id() +{ + local id + id=$(grep 'Created builds:' "$rlRun_LOG" | sed 's/.* //') + test -n "$id" || return 1 + export BUILD_ID=$id +} + +cleanup_resultdir () +( + rm -rf "$RESULTDIR/*" +) + +check_resultdir () +( + set -e + cd "$RESULTDIR/fedora-rawhide-x86_64" + # @FIXME + #test -f "$RESULTDIR"/script + for i in $FILES; do + echo "checking that $i exists in resultdir" + test -f "$i" + done + + NV=$1 + + echo "checking that only one srpm exists" + set -- *.src.rpm + test 1 -eq "$#" + + echo "checking that srpm version is fine" + case $1 in + $NV*.src.rpm) ;; # OK + *) false ;; + esac +) + +check_http_status () +{ + grep "HTTP/1.1 $1" "$rlRun_LOG" +} + +quick_package_script () +{ + cp "$TESTPATH/files/quick-package.sh" script + echo "$1" >> script +} + + +rlJournalStart + rlPhaseStartTest Test + rlRun "export WORKDIR=\`mktemp -d\`" + rlRun "test -n \"\$WORKDIR\"" + + rlRun 'cd "$WORKDIR"' + rlRun 'echo workdir: $WORKDIR' + + + rlLogInfo "Create the project $PROJECT" + PROJECT=custom-1-$NAME_VAR + rlRun 'copr-cli create "$PROJECT" --chroot fedora-rawhide-x86_64' + + + rlLogInfo "Test add-package && build" + rlRun 'cleanup_resultdir' + rlRun 'quick_package_script generate_specfile' + rlRun 'copr add-package-custom "$PROJECT" \ + --name quick-package \ + --script script \ + --script-chroot fedora-rawhide-x86_64' + rlRun -s 'copr build-package "$PROJECT" --name quick-package --nowait' + rlRun 'parse_build_id' + rlRun 'copr watch-build $BUILD_ID' + rlRun 'copr download-build $BUILD_ID --dest $RESULTDIR' + rlRun 'FILES="success" check_resultdir quick-package-0-0' + + + rlLogInfo "Test edit-package && --resultdir" + rlRun 'cleanup_resultdir' + rlRun 'quick_package_script "DESTDIR=rrr generate_specfile"' + rlRun 'copr edit-package-custom "$PROJECT" \ + --name quick-package \ + --script script \ + --script-chroot fedora-rawhide-x86_64' + rlRun -s 'copr build-package "$PROJECT" --name quick-package --nowait' + rlRun 'parse_build_id' + # Should fail, there's no spec file in expected resultdir=. + rlRun 'copr watch-build $BUILD_ID' 4 + + rlRun 'copr edit-package-custom "$PROJECT" \ + --name quick-package \ + --script script \ + --script-resultdir rrr \ + --script-chroot fedora-rawhide-x86_64' + rlRun -s 'copr build-package "$PROJECT" --name quick-package --nowait' + rlRun 'parse_build_id' + rlRun 'copr watch-build $BUILD_ID' + rlRun 'copr download-build $BUILD_ID --dest $RESULTDIR' + rlRun 'FILES="success" check_resultdir quick-package-0-0' + + + rlLogInfo "Test that builddeps get propagated" + builddeps="automake autoconf spax" + rlRun 'cleanup_resultdir' + rlRun 'quick_package_script "BUILDDEPS=xxx generate_specfile"' + rlRun 'copr edit-package-custom "$PROJECT" \ + --name quick-package \ + --script script \ + --script-builddeps "$builddeps" \ + --script-chroot fedora-rawhide-x86_64' + rlRun -s 'copr build-package "$PROJECT" --name quick-package --nowait' + rlRun 'parse_build_id' + # Invalid BUILDDEPS value, should fail + rlRun 'copr watch-build $BUILD_ID' 4 + + rlRun 'quick_package_script "BUILDDEPS=\"$builddeps\" generate_specfile"' + rlRun 'copr edit-package-custom "$PROJECT" \ + --name quick-package \ + --script script \ + --script-builddeps "$builddeps" \ + --script-chroot fedora-rawhide-x86_64' + rlRun -s 'copr build-package "$PROJECT" --name quick-package --nowait' + rlRun 'parse_build_id' + # Invalid BUILDDEPS value, should fail + rlRun 'copr watch-build $BUILD_ID' + rlRun 'copr download-build $BUILD_ID --dest $RESULTDIR' + rlRun 'FILES="success" check_resultdir quick-package-0-0' + + + rlLogInfo "check that hook_payload get's created" + rlRun 'cleanup_resultdir' + rlRun 'quick_package_script "HOOK_PAYLOAD=: generate_specfile"' + rlRun 'copr edit-package-custom "$PROJECT" \ + --name quick-package \ + --script script \ + --script-chroot fedora-rawhide-x86_64 \ + --webhook-rebuild on' + rlRun -s 'copr build-package "$PROJECT" --name quick-package --nowait' + rlRun 'parse_build_id' + rlLogInfo "Still should fail, since this build is not triggered by webhook." + rlRun 'copr watch-build $BUILD_ID' 4 + + trigger_url="$FRONTEND_URL/webhooks/custom/$copr_id/webhook_secret/quick-package/" + rlRun -s 'curl -I "$trigger_url"' 0 # GET can't work + rlRun 'check_http_status 405' + + content_type_option=' -H "Content-Type: application/json"' + data_option=' --data '\''{"a": "b"}'\' + + rlLogInfo "full cmd would be: curl -X POST $content_type_option $data_option $trigger_url" + rlRun "build_id=\$(curl -X POST $data_option \"$trigger_url\")" 0 + rlLogInfo "Still fails since the POST data are not json" + rlRun 'copr watch-build $BUILD_ID' 4 + + rlLogInfo "Still fails since the POST data are not json" + rlRun "build_id=\$(curl -X POST $content_type_option $data_option \"$trigger_url\")" 0 + rlLogInfo "Should succeed finally" + # @FIXME + # rlRun 'copr watch-build $build_id' + # rlRun 'copr download-build $build_id --dest $RESULTDIR' + # rlRun 'FILES="success" check_resultdir quick-package-0-0' + + + rlLogInfo "basic buildcustom command, with fedora-latest-x86_64 chroot (default)" + rlRun 'cleanup_resultdir' + rlRun 'quick_package_script "generate_specfile"' + rlRun -s "copr buildcustom $PROJECT --script script --nowait" + rlRun 'parse_build_id' + rlRun 'copr watch-build $BUILD_ID' + rlRun 'copr download-build $BUILD_ID --dest $RESULTDIR' + rlRun 'FILES="success" check_resultdir quick-package-0-0' + + + rlLogInfo "buildcustom with --builddeps" + builddeps='postgresql-devel' + rlRun 'cleanup_resultdir' + rlRun "quick_package_script 'BUILDDEPS=\"$builddeps\" generate_specfile'" + rlRun -s "copr buildcustom $PROJECT --script script --script-builddeps \"$builddeps\" --nowait" + rlRun 'parse_build_id' + rlRun 'copr watch-build $BUILD_ID' + rlRun 'copr download-build $BUILD_ID --dest $RESULTDIR' + rlRun 'FILES="success" check_resultdir quick-package-0-0' + + + rlLogInfo "buildcustom with --builddeps and --resultdir" + destdir=abc + rlRun 'cleanup_resultdir' + rlRun "quick_package_script 'BUILDDEPS=\"$builddeps\" DESTDIR=$destdir generate_specfile'" + rlRun -s "copr buildcustom $PROJECT --script script --script-resultdir=$destdir --script-builddeps \"$builddeps\" --nowait" + rlRun 'parse_build_id' + rlRun 'copr watch-build $BUILD_ID' + rlRun 'copr download-build $BUILD_ID --dest $RESULTDIR' + rlRun 'FILES="success" check_resultdir quick-package-0-0' + + rlPhaseEnd +rlJournalEnd