{"action":"created","comment":{"url":"https://api.github.com/repos/projectatomic/osbs-client/pulls/comments/174870266","pull_request_review_id":104308006,"id":174870266,"diff_hunk":"@@ -0,0 +1,591 @@\n+\"\"\"\n+Copyright (c) 2018 Red Hat, Inc\n+All rights reserved.\n+\n+This software may be modified and distributed under the terms\n+of the BSD license. See the LICENSE file for details.\n+\"\"\"\n+from __future__ import print_function, absolute_import, unicode_literals\n+\n+import logging\n+import os\n+import json\n+import copy\n+\n+from osbs.constants import (BUILD_TYPE_ORCHESTRATOR, ISOLATED_RELEASE_FORMAT)\n+from osbs.exceptions import OsbsException, OsbsValidationException\n+from osbs.utils import Labels\n+\n+logger = logging.getLogger(__name__)\n+\n+\n+class PluginsTemplate(object):\n+    def __init__(self, build_json_dir, template_path):\n+        self._template = None\n+        self._build_json_dir = build_json_dir\n+        self._template_path = template_path\n+\n+    @property\n+    def template(self):\n+        if self._template is None:\n+            path = os.path.join(self._build_json_dir, self._template_path)\n+            logger.debug(\"loading template from path %s\", path)\n+            try:\n+                with open(path, \"r\") as fp:\n+                    self._template = json.load(fp)\n+            except (IOError, OSError) as ex:\n+                raise OsbsException(\"Can't open template '%s': %s\" %\n+                                    (path, repr(ex)))\n+        return self._template\n+\n+    def remove_plugin(self, plugin_type, plugin_name):\n+        \"\"\"\n+        if config contains plugin, remove it\n+        \"\"\"\n+        for p in self.template[plugin_type]:\n+            if p.get('name') == plugin_name:\n+                self.template[plugin_type].remove(p)\n+                break\n+\n+    def add_plugin(self, plugin_type, plugin_name, args_dict):\n+        \"\"\"\n+        if config has plugin, override it, else add it\n+        \"\"\"\n+        plugin_modified = False\n+\n+        for plugin in self.template[plugin_type]:\n+            if plugin['name'] == plugin_name:\n+                plugin['args'] = args_dict\n+                plugin_modified = True\n+\n+        if not plugin_modified:\n+            self.template[plugin_type].append({\"name\": plugin_name, \"args\": args_dict})\n+\n+    def get_plugin_conf(self, plugin_type, plugin_name):\n+        \"\"\"\n+        Return the configuration for a plugin.\n+\n+        Raises KeyError if there are no plugins of that type.\n+        Raises IndexError if the named plugin is not listed.\n+        \"\"\"\n+        match = [x for x in self.template[plugin_type] if x.get('name') == plugin_name]\n+        return match[0]\n+\n+    def has_plugin_conf(self, plugin_type, plugin_name):\n+        \"\"\"\n+        Check whether a plugin is configured.\n+        \"\"\"\n+        try:\n+            self.get_plugin_conf(plugin_type, plugin_name)\n+            return True\n+        except (KeyError, IndexError):\n+            return False\n+\n+    def _get_plugin_conf_or_fail(self, plugin_type, plugin_name):\n+        try:\n+            conf = self.get_plugin_conf(plugin_type, plugin_name)\n+        except KeyError:\n+            raise RuntimeError(\"Invalid template: plugin type '%s' misses\" % plugin_type)\n+        except IndexError:\n+            raise RuntimeError(\"no such plugin in template: \\\"%s\\\"\" % plugin_name)\n+        return conf\n+\n+    def set_param(self, param, value):\n+        self.template[param] = value\n+\n+    def merge_plugin_arg(self, plugin_type, plugin_name, arg_key, arg_dict):\n+        plugin_conf = self._get_plugin_conf_or_fail(plugin_type, plugin_name)\n+\n+        # Values supplied by the caller override those from the template JSON\n+        template_value = plugin_conf['args'].get(arg_key, {})\n+        if not isinstance(template_value, dict):\n+            template_value = {}\n+\n+        value = copy.deepcopy(template_value)\n+        value.update(arg_dict)\n+        plugin_conf['args'][arg_key] = value\n+\n+    def set_plugin_arg(self, plugin_type, plugin_name, arg_key, arg_value):\n+        plugin_conf = self._get_plugin_conf_or_fail(plugin_type, plugin_name)\n+        plugin_conf.setdefault(\"args\", {})\n+        plugin_conf['args'][arg_key] = arg_value\n+\n+    def set_plugin_arg_valid(self, phase, plugin, name, value):\n+        if value is not None:\n+            self.set_plugin_arg(phase, plugin, name, value)\n+            return True\n+        return False\n+\n+    def remove_arg(self, arg, optional_value=None):\n+        self.template['user_params'].pop(arg, optional_value)\n+\n+    def to_json(self):\n+        return json.dumps(self.template)\n+\n+\n+class PluginsConfiguration(object):\n+    def __init__(self, user_params):\n+        # Figure out inner template to use from user_params:\n+        self.user_params = user_params\n+\n+        #    <build_type>_inner:<arrangement_version>.json\n+        arrangement_version = self.user_params.arrangement_version.value\n+        build_type = self.user_params.build_type.value\n+        pt_path = '{0}_inner:{1}.json'.format(build_type, arrangement_version)\n+        self.pt = PluginsTemplate(self.user_params.build_json_dir.value, pt_path)\n+\n+    def has_tag_suffixes_placeholder(self):\n+        phase = 'postbuild_plugins'\n+        plugin = 'tag_from_config'\n+        if not self.pt.has_plugin_conf(phase, plugin):\n+            return False\n+\n+        placeholder = '{{TAG_SUFFIXES}}'\n+        plugin_conf = self.pt.get_plugin_conf('postbuild_plugins', 'tag_from_config')\n+        return plugin_conf.get('args', {}).get('tag_suffixes') == placeholder\n+\n+    def adjust_for_scratch(self):\n+        \"\"\"\n+        Remove certain plugins in order to handle the \"scratch build\"\n+        scenario. Scratch builds must not affect subsequent builds,\n+        and should not be imported into Koji.\n+        \"\"\"\n+        if self.user_params.scratch.value:\n+            self.pt.remove_arg('triggers', None)\n+\n+            remove_plugins = [\n+                (\"prebuild_plugins\", \"koji_parent\"),\n+                (\"postbuild_plugins\", \"compress\"),  # required only to make an archive for Koji\n+                (\"postbuild_plugins\", \"pulp_pull\"),  # required only to make an archive for Koji\n+                (\"postbuild_plugins\", \"koji_upload\"),\n+                (\"postbuild_plugins\", \"fetch_worker_metadata\"),\n+                (\"postbuild_plugins\", \"compare_components\"),\n+                (\"postbuild_plugins\", \"import_image\"),\n+                (\"exit_plugins\", \"koji_promote\"),\n+                (\"exit_plugins\", \"koji_import\"),\n+                (\"exit_plugins\", \"koji_tag_build\"),\n+                (\"exit_plugins\", \"remove_worker_metadata\"),\n+                (\"exit_plugins\", \"import_image\"),\n+            ]\n+\n+            if not self.has_tag_suffixes_placeholder():\n+                remove_plugins.append((\"postbuild_plugins\", \"tag_from_config\"))\n+\n+            for when, which in remove_plugins:\n+                logger.info(\"removing %s from scratch build request\", which)\n+                self.pt.remove_plugin(when, which)\n+\n+            if self.pt.has_plugin_conf('postbuild_plugins', 'tag_by_labels'):\n+                self.pt.set_plugin_arg('postbuild_plugins', 'tag_by_labels',\n+                                       'unique_tag_only', True)\n+\n+            self.set_label('scratch', 'true')\n+\n+    def adjust_for_isolated(self, release):\n+        if not self.isolated:\n+            return\n+\n+        self.pt.remove_arg('triggers', None)\n+\n+        if not release.value:\n+            raise OsbsValidationException('The release parameter is required for isolated builds.')\n+\n+        if not ISOLATED_RELEASE_FORMAT.match(release.value):\n+            raise OsbsValidationException(\n+                'For isolated builds, the release value must be in the format: {0}'\n+                .format(ISOLATED_RELEASE_FORMAT.pattern))\n+\n+        self.set_label('isolated', 'true')\n+        self.set_label('isolated-release', release.value)\n+\n+    def adjust_for_custom_base_image(self):\n+        \"\"\"\n+        Disable plugins to handle builds depending on whether\n+        or not this is a build from a custom base image.\n+        \"\"\"\n+        plugins = []\n+        if self.is_custom_base_image():\n+            # Plugins irrelevant to building base images.\n+            plugins.append((\"prebuild_plugins\", \"pull_base_image\"))\n+            plugins.append((\"prebuild_plugins\", \"koji_parent\"))\n+            plugins.append((\"prebuild_plugins\", \"inject_parent_image\"))\n+            msg = \"removing %s from custom image build request\"\n+\n+        else:\n+            # Plugins not needed for building non base images.\n+            plugins.append((\"prebuild_plugins\", \"add_filesystem\"))\n+            msg = \"removing %s from non custom image build request\"\n+\n+        for when, which in plugins:\n+            logger.info(msg, which)\n+            self.pt.emove_plugin(when, which)\n+\n+    def adjust_for_repo_info(self):\n+        if not self._repo_info:\n+            logger.warning('repo info not set')\n+            return\n+\n+        if not self._repo_info.configuration.is_autorebuild_enabled():\n+            logger.info('autorebuild is disabled in repo configuration, removing triggers')\n+            self.pt.remove_arg('triggers', None)\n+\n+        else:\n+            labels = Labels(self._repo_info.dockerfile_parser.labels)\n+            try:\n+                labels.get_name_and_value(Labels.LABEL_TYPE_RELEASE)\n+            except KeyError:\n+                # As expected, release label not set in Dockerfile\n+                pass\n+            else:\n+                raise RuntimeError('when autorebuild is enabled in repo configuration, '\n+                                   '\"release\" label must not be set in Dockerfile')\n+\n+    def render_add_filesystem(self):\n+        phase = 'prebuild_plugins'\n+        plugin = 'add_filesystem'\n+\n+        if self.pt.has_plugin_conf(phase, plugin):\n+            self.pt.set_plugin_arg_valid(phase, plugin, 'repos',\n+                                         self.user_params.yum_repourls.value)\n+            self.pt.set_plugin_arg_valid(phase, plugin, 'from_task_id',\n+                                         self.user_params.filesystem_koji_task_id.value)\n+\n+    def render_add_labels_in_dockerfile(self):\n+        phase = 'prebuild_plugins'\n+        plugin = 'add_labels_in_dockerfile'\n+        if not self.pt.has_plugin_conf(phase, plugin):\n+            return\n+\n+        implicit_labels = {}\n+        label_user_params = {\n+            'release': self.user_params.release,\n+        }\n+\n+        for label, user_params in label_user_params.items():\n+            if user_params.value is not None:\n+                implicit_labels[label] = user_params.value\n+\n+        self.pt.merge_plugin_arg(phase, plugin, 'labels', implicit_labels)\n+\n+    def render_add_yum_repo_by_url(self):\n+        if (self.user_params.yum_repourls.value is not None and\n+                self.pt.has_plugin_conf('prebuild_plugins', \"add_yum_repo_by_url\")):\n+            self.pt.set_plugin_arg('prebuild_plugins', \"add_yum_repo_by_url\", \"repourls\",\n+                                   self.user_params.yum_repourls.value)\n+\n+    def render_customizations(self):\n+        \"\"\"\n+        Customize prod_inner for site user_paramsific customizations\n+        \"\"\"\n+\n+        disable_plugins = self.customize_conf.get('disable_plugins', [])\n+        if not disable_plugins:\n+            logger.debug(\"No site-user_paramsific plugins to disable\")\n+        else:\n+            for plugin_dict in disable_plugins:\n+                try:\n+                    self.remove_plugin(\n+                        plugin_dict['plugin_type'],\n+                        plugin_dict['plugin_name']\n+                    )\n+                    logger.debug(\n+                        \"site-user_paramsific plugin disabled -> Type:{0} Name:{1}\".format(\n+                            plugin_dict['plugin_type'],\n+                            plugin_dict['plugin_name']\n+                        )\n+                    )\n+                except KeyError:\n+                    # Malformed config\n+                    logger.debug(\"Invalid custom configuration found for disable_plugins\")\n+\n+        enable_plugins = self.customize_conf.get('enable_plugins', [])\n+        if not enable_plugins:\n+            logger.debug(\"No site-user_paramsific plugins to enable\")\n+        else:\n+            for plugin_dict in enable_plugins:\n+                try:\n+                    self.add_plugin(\n+                        plugin_dict['plugin_type'],\n+                        plugin_dict['plugin_name'],\n+                        plugin_dict['plugin_args']\n+                    )\n+                    logger.debug(\n+                        \"site-user_paramsific plugin enabled -> Type:{0} Name:{1} Args: {2}\".format(\n+                            plugin_dict['plugin_type'],\n+                            plugin_dict['plugin_name'],\n+                            plugin_dict['plugin_args']\n+                        )\n+                    )\n+                except KeyError:\n+                    # Malformed config\n+                    logger.debug(\"Invalid custom configuration found for enable_plugins\")\n+\n+    def render_flatpak_create_dockerfile(self):\n+        phase = 'prebuild_plugins'\n+        plugin = 'flatpak_create_dockerfile'\n+\n+        if self.pt.has_plugin_conf(phase, plugin):\n+            if not self.pt.set_plugin_arg_valid(phase, plugin, 'base_image',\n+                                                self.user_params.flatpak_base_image.value):\n+                self.pt.remove_plugin(phase, plugin)\n+                return\n+\n+    def render_flatpak_create_oci(self):\n+        phase = 'prepublish_plugins'\n+        plugin = 'flatpak_create_oci'\n+\n+        if not self.user_params.flatpak.value:\n+            self.pt.remove_plugin(phase, plugin)\n+            return\n+\n+    def render_koji(self):\n+        \"\"\"\n+        if there is yum repo user_paramsified, don't pick stuff from koji\n+        \"\"\"\n+        phase = 'prebuild_plugins'\n+        plugin = 'koji'\n+        if not self.pt.has_plugin_conf(phase, plugin):\n+            return\n+\n+        if self.user_params.yum_repourls.value:\n+            logger.info(\"removing koji from request \"\n+                        \"because there is yum repo user_paramsified\")\n+            self.pt.remove_plugin(phase, plugin)\n+        elif self.user_params.flatpak.value:\n+            logger.info(\"removing koji from request \"\n+                        \"because this is a Flatpak built from a module\")\n+            self.pt.remove_plugin(phase, plugin)\n+        elif not self.pt.set_plugin_arg_valid(phase, plugin, \"target\",\n+                                              self.user_params.koji_target.value):\n+            logger.info(\"removing koji from request as not user_paramsified\")\n+            self.pt.remove_plugin(phase, plugin)\n+\n+    def render_bump_release(self):\n+        \"\"\"\n+        If the bump_release plugin is present, configure it\n+        \"\"\"\n+        phase = 'prebuild_plugins'\n+        plugin = 'bump_release'\n+        if not self.pt.has_plugin_conf(phase, plugin):\n+            return\n+\n+        if self.user_params.release.value:\n+            logger.info('removing %s from request as release already user_paramsified',\n+                        plugin)\n+            self.pt.remove_plugin(phase, plugin)\n+            return\n+\n+        # For flatpak, we want a name-version-release of\n+        # <name>-<stream>-<module_build_version>.<n>, where the .<n> makes\n+        # sure that the build is unique in Koji\n+        if self.user_params.flatpak.value:\n+            self.pt.set_plugin_arg(phase, plugin, 'append', True)\n+\n+    def render_import_image(self, use_auth=None):\n+        \"\"\"\n+        Configure the import_image plugin\n+        \"\"\"\n+        # import_image is a multi-phase plugin\n+        phases = ('postbuild_plugins', 'exit_plugins')","path":"osbs/build/plugins_configuration.py","position":389,"original_position":389,"commit_id":"12f736accd445b86603cae217ce81e22afb7c7e2","original_commit_id":"12f736accd445b86603cae217ce81e22afb7c7e2","user":{"login":"mlangsdorf","id":8007405,"avatar_url":"https://avatars0.githubusercontent.com/u/8007405?v=4","gravatar_id":"","url":"https://api.github.com/users/mlangsdorf","html_url":"https://github.com/mlangsdorf","followers_url":"https://api.github.com/users/mlangsdorf/followers","following_url":"https://api.github.com/users/mlangsdorf/following{/other_user}","gists_url":"https://api.github.com/users/mlangsdorf/gists{/gist_id}","starred_url":"https://api.github.com/users/mlangsdorf/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mlangsdorf/subscriptions","organizations_url":"https://api.github.com/users/mlangsdorf/orgs","repos_url":"https://api.github.com/users/mlangsdorf/repos","events_url":"https://api.github.com/users/mlangsdorf/events{/privacy}","received_events_url":"https://api.github.com/users/mlangsdorf/received_events","type":"User","site_admin":false},"body":"Fixed.","created_at":"2018-03-15T17:40:33Z","updated_at":"2018-03-15T17:40:33Z","html_url":"https://github.com/projectatomic/osbs-client/pull/738#discussion_r174870266","pull_request_url":"https://api.github.com/repos/projectatomic/osbs-client/pulls/738","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/projectatomic/osbs-client/pulls/comments/174870266"},"html":{"href":"https://github.com/projectatomic/osbs-client/pull/738#discussion_r174870266"},"pull_request":{"href":"https://api.github.com/repos/projectatomic/osbs-client/pulls/738"}},"in_reply_to_id":174860238},"pull_request":{"url":"https://api.github.com/repos/projectatomic/osbs-client/pulls/738","id":174386720,"html_url":"https://github.com/projectatomic/osbs-client/pull/738","diff_url":"https://github.com/projectatomic/osbs-client/pull/738.diff","patch_url":"https://github.com/projectatomic/osbs-client/pull/738.patch","issue_url":"https://api.github.com/repos/projectatomic/osbs-client/issues/738","number":738,"state":"open","locked":false,"title":"Arrangement 6: Add support for BuildUserParams, BuildRequestV2, and PluginsConfiguration","user":{"login":"mlangsdorf","id":8007405,"avatar_url":"https://avatars0.githubusercontent.com/u/8007405?v=4","gravatar_id":"","url":"https://api.github.com/users/mlangsdorf","html_url":"https://github.com/mlangsdorf","followers_url":"https://api.github.com/users/mlangsdorf/followers","following_url":"https://api.github.com/users/mlangsdorf/following{/other_user}","gists_url":"https://api.github.com/users/mlangsdorf/gists{/gist_id}","starred_url":"https://api.github.com/users/mlangsdorf/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mlangsdorf/subscriptions","organizations_url":"https://api.github.com/users/mlangsdorf/orgs","repos_url":"https://api.github.com/users/mlangsdorf/repos","events_url":"https://api.github.com/users/mlangsdorf/events{/privacy}","received_events_url":"https://api.github.com/users/mlangsdorf/received_events","type":"User","site_admin":false},"body":"As part of the move to arrangement 6, create PluginsConfiguration, BuildRequestV2 and the\r\nunderlying BuildUserParams.\r\n\r\n(move from PR733 because github keeps stalling out on me).\r\n\r\nCurrently the templates are *_inner:7.json to avoid collisions with the DEFAULT_ARRANGEMENT_VERSION tests.\r\n\r\nStill needs code to handle custom configurations.\r\n\r\nPlease review.\r\n\r\nSigned-off-by: Mark Langsdorf mlangsdo@redhat.com","created_at":"2018-03-12T14:34:54Z","updated_at":"2018-03-15T17:40:33Z","closed_at":null,"merged_at":null,"merge_commit_sha":"ee6649a016d1db1afec35ae61199a494aa22c460","assignee":null,"assignees":[],"requested_reviewers":[{"login":"rcerven","id":23337371,"avatar_url":"https://avatars0.githubusercontent.com/u/23337371?v=4","gravatar_id":"","url":"https://api.github.com/users/rcerven","html_url":"https://github.com/rcerven","followers_url":"https://api.github.com/users/rcerven/followers","following_url":"https://api.github.com/users/rcerven/following{/other_user}","gists_url":"https://api.github.com/users/rcerven/gists{/gist_id}","starred_url":"https://api.github.com/users/rcerven/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rcerven/subscriptions","organizations_url":"https://api.github.com/users/rcerven/orgs","repos_url":"https://api.github.com/users/rcerven/repos","events_url":"https://api.github.com/users/rcerven/events{/privacy}","received_events_url":"https://api.github.com/users/rcerven/received_events","type":"User","site_admin":false},{"login":"bfontecc007","id":29286673,"avatar_url":"https://avatars2.githubusercontent.com/u/29286673?v=4","gravatar_id":"","url":"https://api.github.com/users/bfontecc007","html_url":"https://github.com/bfontecc007","followers_url":"https://api.github.com/users/bfontecc007/followers","following_url":"https://api.github.com/users/bfontecc007/following{/other_user}","gists_url":"https://api.github.com/users/bfontecc007/gists{/gist_id}","starred_url":"https://api.github.com/users/bfontecc007/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bfontecc007/subscriptions","organizations_url":"https://api.github.com/users/bfontecc007/orgs","repos_url":"https://api.github.com/users/bfontecc007/repos","events_url":"https://api.github.com/users/bfontecc007/events{/privacy}","received_events_url":"https://api.github.com/users/bfontecc007/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/projectatomic/osbs-client/pulls/738/commits","review_comments_url":"https://api.github.com/repos/projectatomic/osbs-client/pulls/738/comments","review_comment_url":"https://api.github.com/repos/projectatomic/osbs-client/pulls/comments{/number}","comments_url":"https://api.github.com/repos/projectatomic/osbs-client/issues/738/comments","statuses_url":"https://api.github.com/repos/projectatomic/osbs-client/statuses/12f736accd445b86603cae217ce81e22afb7c7e2","head":{"label":"mlangsdorf:osbs5052-2","ref":"osbs5052-2","sha":"12f736accd445b86603cae217ce81e22afb7c7e2","user":{"login":"mlangsdorf","id":8007405,"avatar_url":"https://avatars0.githubusercontent.com/u/8007405?v=4","gravatar_id":"","url":"https://api.github.com/users/mlangsdorf","html_url":"https://github.com/mlangsdorf","followers_url":"https://api.github.com/users/mlangsdorf/followers","following_url":"https://api.github.com/users/mlangsdorf/following{/other_user}","gists_url":"https://api.github.com/users/mlangsdorf/gists{/gist_id}","starred_url":"https://api.github.com/users/mlangsdorf/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mlangsdorf/subscriptions","organizations_url":"https://api.github.com/users/mlangsdorf/orgs","repos_url":"https://api.github.com/users/mlangsdorf/repos","events_url":"https://api.github.com/users/mlangsdorf/events{/privacy}","received_events_url":"https://api.github.com/users/mlangsdorf/received_events","type":"User","site_admin":false},"repo":{"id":90181508,"name":"osbs-client","full_name":"mlangsdorf/osbs-client","owner":{"login":"mlangsdorf","id":8007405,"avatar_url":"https://avatars0.githubusercontent.com/u/8007405?v=4","gravatar_id":"","url":"https://api.github.com/users/mlangsdorf","html_url":"https://github.com/mlangsdorf","followers_url":"https://api.github.com/users/mlangsdorf/followers","following_url":"https://api.github.com/users/mlangsdorf/following{/other_user}","gists_url":"https://api.github.com/users/mlangsdorf/gists{/gist_id}","starred_url":"https://api.github.com/users/mlangsdorf/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mlangsdorf/subscriptions","organizations_url":"https://api.github.com/users/mlangsdorf/orgs","repos_url":"https://api.github.com/users/mlangsdorf/repos","events_url":"https://api.github.com/users/mlangsdorf/events{/privacy}","received_events_url":"https://api.github.com/users/mlangsdorf/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/mlangsdorf/osbs-client","description":"client for OpenShift Build Service","fork":true,"url":"https://api.github.com/repos/mlangsdorf/osbs-client","forks_url":"https://api.github.com/repos/mlangsdorf/osbs-client/forks","keys_url":"https://api.github.com/repos/mlangsdorf/osbs-client/keys{/key_id}","collaborators_url":"https://api.github.com/repos/mlangsdorf/osbs-client/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/mlangsdorf/osbs-client/teams","hooks_url":"https://api.github.com/repos/mlangsdorf/osbs-client/hooks","issue_events_url":"https://api.github.com/repos/mlangsdorf/osbs-client/issues/events{/number}","events_url":"https://api.github.com/repos/mlangsdorf/osbs-client/events","assignees_url":"https://api.github.com/repos/mlangsdorf/osbs-client/assignees{/user}","branches_url":"https://api.github.com/repos/mlangsdorf/osbs-client/branches{/branch}","tags_url":"https://api.github.com/repos/mlangsdorf/osbs-client/tags","blobs_url":"https://api.github.com/repos/mlangsdorf/osbs-client/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/mlangsdorf/osbs-client/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/mlangsdorf/osbs-client/git/refs{/sha}","trees_url":"https://api.github.com/repos/mlangsdorf/osbs-client/git/trees{/sha}","statuses_url":"https://api.github.com/repos/mlangsdorf/osbs-client/statuses/{sha}","languages_url":"https://api.github.com/repos/mlangsdorf/osbs-client/languages","stargazers_url":"https://api.github.com/repos/mlangsdorf/osbs-client/stargazers","contributors_url":"https://api.github.com/repos/mlangsdorf/osbs-client/contributors","subscribers_url":"https://api.github.com/repos/mlangsdorf/osbs-client/subscribers","subscription_url":"https://api.github.com/repos/mlangsdorf/osbs-client/subscription","commits_url":"https://api.github.com/repos/mlangsdorf/osbs-client/commits{/sha}","git_commits_url":"https://api.github.com/repos/mlangsdorf/osbs-client/git/commits{/sha}","comments_url":"https://api.github.com/repos/mlangsdorf/osbs-client/comments{/number}","issue_comment_url":"https://api.github.com/repos/mlangsdorf/osbs-client/issues/comments{/number}","contents_url":"https://api.github.com/repos/mlangsdorf/osbs-client/contents/{+path}","compare_url":"https://api.github.com/repos/mlangsdorf/osbs-client/compare/{base}...{head}","merges_url":"https://api.github.com/repos/mlangsdorf/osbs-client/merges","archive_url":"https://api.github.com/repos/mlangsdorf/osbs-client/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/mlangsdorf/osbs-client/downloads","issues_url":"https://api.github.com/repos/mlangsdorf/osbs-client/issues{/number}","pulls_url":"https://api.github.com/repos/mlangsdorf/osbs-client/pulls{/number}","milestones_url":"https://api.github.com/repos/mlangsdorf/osbs-client/milestones{/number}","notifications_url":"https://api.github.com/repos/mlangsdorf/osbs-client/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/mlangsdorf/osbs-client/labels{/name}","releases_url":"https://api.github.com/repos/mlangsdorf/osbs-client/releases{/id}","deployments_url":"https://api.github.com/repos/mlangsdorf/osbs-client/deployments","created_at":"2017-05-03T18:36:51Z","updated_at":"2017-05-03T18:36:53Z","pushed_at":"2018-03-13T19:13:09Z","git_url":"git://github.com/mlangsdorf/osbs-client.git","ssh_url":"git@github.com:mlangsdorf/osbs-client.git","clone_url":"https://github.com/mlangsdorf/osbs-client.git","svn_url":"https://github.com/mlangsdorf/osbs-client","homepage":null,"size":2671,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":null,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"projectatomic:master","ref":"master","sha":"15930615161ad97e1b2abc70ea99450c0d0f5520","user":{"login":"projectatomic","id":6852258,"avatar_url":"https://avatars1.githubusercontent.com/u/6852258?v=4","gravatar_id":"","url":"https://api.github.com/users/projectatomic","html_url":"https://github.com/projectatomic","followers_url":"https://api.github.com/users/projectatomic/followers","following_url":"https://api.github.com/users/projectatomic/following{/other_user}","gists_url":"https://api.github.com/users/projectatomic/gists{/gist_id}","starred_url":"https://api.github.com/users/projectatomic/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/projectatomic/subscriptions","organizations_url":"https://api.github.com/users/projectatomic/orgs","repos_url":"https://api.github.com/users/projectatomic/repos","events_url":"https://api.github.com/users/projectatomic/events{/privacy}","received_events_url":"https://api.github.com/users/projectatomic/received_events","type":"Organization","site_admin":false},"repo":{"id":31409916,"name":"osbs-client","full_name":"projectatomic/osbs-client","owner":{"login":"projectatomic","id":6852258,"avatar_url":"https://avatars1.githubusercontent.com/u/6852258?v=4","gravatar_id":"","url":"https://api.github.com/users/projectatomic","html_url":"https://github.com/projectatomic","followers_url":"https://api.github.com/users/projectatomic/followers","following_url":"https://api.github.com/users/projectatomic/following{/other_user}","gists_url":"https://api.github.com/users/projectatomic/gists{/gist_id}","starred_url":"https://api.github.com/users/projectatomic/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/projectatomic/subscriptions","organizations_url":"https://api.github.com/users/projectatomic/orgs","repos_url":"https://api.github.com/users/projectatomic/repos","events_url":"https://api.github.com/users/projectatomic/events{/privacy}","received_events_url":"https://api.github.com/users/projectatomic/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/projectatomic/osbs-client","description":"client for OpenShift Build Service","fork":false,"url":"https://api.github.com/repos/projectatomic/osbs-client","forks_url":"https://api.github.com/repos/projectatomic/osbs-client/forks","keys_url":"https://api.github.com/repos/projectatomic/osbs-client/keys{/key_id}","collaborators_url":"https://api.github.com/repos/projectatomic/osbs-client/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/projectatomic/osbs-client/teams","hooks_url":"https://api.github.com/repos/projectatomic/osbs-client/hooks","issue_events_url":"https://api.github.com/repos/projectatomic/osbs-client/issues/events{/number}","events_url":"https://api.github.com/repos/projectatomic/osbs-client/events","assignees_url":"https://api.github.com/repos/projectatomic/osbs-client/assignees{/user}","branches_url":"https://api.github.com/repos/projectatomic/osbs-client/branches{/branch}","tags_url":"https://api.github.com/repos/projectatomic/osbs-client/tags","blobs_url":"https://api.github.com/repos/projectatomic/osbs-client/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/projectatomic/osbs-client/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/projectatomic/osbs-client/git/refs{/sha}","trees_url":"https://api.github.com/repos/projectatomic/osbs-client/git/trees{/sha}","statuses_url":"https://api.github.com/repos/projectatomic/osbs-client/statuses/{sha}","languages_url":"https://api.github.com/repos/projectatomic/osbs-client/languages","stargazers_url":"https://api.github.com/repos/projectatomic/osbs-client/stargazers","contributors_url":"https://api.github.com/repos/projectatomic/osbs-client/contributors","subscribers_url":"https://api.github.com/repos/projectatomic/osbs-client/subscribers","subscription_url":"https://api.github.com/repos/projectatomic/osbs-client/subscription","commits_url":"https://api.github.com/repos/projectatomic/osbs-client/commits{/sha}","git_commits_url":"https://api.github.com/repos/projectatomic/osbs-client/git/commits{/sha}","comments_url":"https://api.github.com/repos/projectatomic/osbs-client/comments{/number}","issue_comment_url":"https://api.github.com/repos/projectatomic/osbs-client/issues/comments{/number}","contents_url":"https://api.github.com/repos/projectatomic/osbs-client/contents/{+path}","compare_url":"https://api.github.com/repos/projectatomic/osbs-client/compare/{base}...{head}","merges_url":"https://api.github.com/repos/projectatomic/osbs-client/merges","archive_url":"https://api.github.com/repos/projectatomic/osbs-client/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/projectatomic/osbs-client/downloads","issues_url":"https://api.github.com/repos/projectatomic/osbs-client/issues{/number}","pulls_url":"https://api.github.com/repos/projectatomic/osbs-client/pulls{/number}","milestones_url":"https://api.github.com/repos/projectatomic/osbs-client/milestones{/number}","notifications_url":"https://api.github.com/repos/projectatomic/osbs-client/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/projectatomic/osbs-client/labels{/name}","releases_url":"https://api.github.com/repos/projectatomic/osbs-client/releases{/id}","deployments_url":"https://api.github.com/repos/projectatomic/osbs-client/deployments","created_at":"2015-02-27T08:20:07Z","updated_at":"2018-03-13T19:30:32Z","pushed_at":"2018-03-15T13:47:00Z","git_url":"git://github.com/projectatomic/osbs-client.git","ssh_url":"git@github.com:projectatomic/osbs-client.git","clone_url":"https://github.com/projectatomic/osbs-client.git","svn_url":"https://github.com/projectatomic/osbs-client","homepage":null,"size":2702,"stargazers_count":28,"watchers_count":28,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":30,"mirror_url":null,"archived":false,"open_issues_count":35,"license":{"key":"bsd-3-clause","name":"BSD 3-Clause \"New\" or \"Revised\" License","spdx_id":"BSD-3-Clause","url":"https://api.github.com/licenses/bsd-3-clause"},"forks":30,"open_issues":35,"watchers":28,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/projectatomic/osbs-client/pulls/738"},"html":{"href":"https://github.com/projectatomic/osbs-client/pull/738"},"issue":{"href":"https://api.github.com/repos/projectatomic/osbs-client/issues/738"},"comments":{"href":"https://api.github.com/repos/projectatomic/osbs-client/issues/738/comments"},"review_comments":{"href":"https://api.github.com/repos/projectatomic/osbs-client/pulls/738/comments"},"review_comment":{"href":"https://api.github.com/repos/projectatomic/osbs-client/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/projectatomic/osbs-client/pulls/738/commits"},"statuses":{"href":"https://api.github.com/repos/projectatomic/osbs-client/statuses/12f736accd445b86603cae217ce81e22afb7c7e2"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"mlangsdorf"}}