#531 Allow usage of the side tags repo
Merged 4 years ago by vmaljulin. Opened 4 years ago by vmaljulin.
vmaljulin/greenwave FACTORY-5691  into  master

file modified
+1
@@ -10,6 +10,7 @@ 

  

  WORKDIR /src

  RUN dnf -y install \

+     git-core \

      python3-dogpile-cache \

      python3-fedmsg \

      python3-flask \

file modified
+1
@@ -5,6 +5,7 @@ 

      set -e

  

      dnf -y install \

+         git-core \

          postgresql-server \

          postgresql-contrib \

          python3-gunicorn \

@@ -52,7 +52,7 @@ 

  

  .. _tolerate-invalid-gating-yaml:

  

- Tolerate an invalid gating.yaml file

+ Tolerate an invalid remote rule file

  ------------------------------------

  

  A gating.yaml file is considered invalid if it has an invalid syntax (yaml
@@ -67,18 +67,17 @@ 

  ``testcase`` equal to ``invalid-gating-yaml``. It is not necessary to have

  a result in Resultsdb for this testcase.

  

- The side effect is that all the policies defined in the gating.yaml

+ The side effect is that all the policies defined in the remote rule

  file will be completely ignored by Greenwave.

  

  

  .. _missing-gating-yaml:

  

- Missing gating.yaml file

+ Missing remote rule file

  ------------------------

  

- Missing gating.yaml file (i.e. not present in dist-git repo of the tested

- package in the required revision) is just skipped and not treated as

- unsatisfied requirement by default. To change this, ``required`` boolean

+ Missing remote rule file (i.e. not present in the configured repo) is just skipped

+ and not treated as unsatisfied requirement by default. To change this, ``required`` boolean

  attribute of ``RemoteRule`` must be set to ``true``.

  

  .. code-block:: yaml
@@ -91,8 +90,7 @@ 

     rules:

       - !RemoteRule {required: true}

  

- For such policy, if gating.yaml is missing, could result in the following

- decision.

+ For such policy, missing remote rule file could result in the following decision.

  

  .. code-block:: json

  
@@ -118,7 +116,7 @@ 

  If you want to add some additional policies, you can follow this

  tutorial.

  

- We need to write the gating.yaml file. The one for this example will

+ We need to write a remote rule file. The one for this example will

  be this one:

  

  ::
@@ -131,7 +129,7 @@ 

          rules:

            - !PassingTestCaseRule {test_case_name: dist.depcheck}

  

- *NB*. It is not possible to insert a RemoteRule inside a gating.yaml file.

+ *NB*. It is not possible to insert a RemoteRule inside a remote rule file.

  This will provoke an error.

  

  You need now to push the new file (or the changes) in your dist-git
@@ -145,8 +143,8 @@ 

  Now you can find in the link of the build in Koji the nvr of the build.

  Example: ``python-ansi2html-1.1.1-114.fc28``

  

- In case of a misconfigured gating.yaml you would need to repeate the

- build. To avoid this it is possible to validate the gating.yaml file

+ In case of a misconfigured remote rule you would need to repeate the

+ build. To avoid this it is possible to validate the remote rule file

  before starting the build.

  To do that you can use this command (in this example we are using the

  Fedora Greenwave instance in production):
@@ -195,28 +193,26 @@ 

  decision will change and all the requirements will be satisfied (if

  everything was configured in the correct way).

  

- If your gating.yaml file will be misconfigured, Greenwave will reply

- that the gating.yaml file is wrong. If you just want to skip this check

+ If your remote rule file is misconfigured, Greenwave will reply

+ that the remote rule file is wrong. If you just want to skip this check

  without build again, just look at the previous section in this page.

  

  

  .. _fetching-gating-yaml:

  

- How is gating.yaml file retrieved?

- ----------------------------------

+ How is the remote rule file being retrieved?

+ --------------------------------------------

  

- The "gating.yaml" file is downloaded from a dist-git repository based on the

- source URL of a specific build in Koji.

- 

- The file is fetched from specific git commit (the revision is part of the

- build's source URL).

+ The remote rule file (usually called ``gating.yaml``) is downloaded

+ from a repository based on the source URL of a specific build in Koji.

+ Different URLs can be set for different subject types.

  

  More specifically, Greenwave first gets the build data ``koji call getBuild

  $NVR``. Then it parses URL in "source" field to get namespace ("rpms" or

  "containers" etc.), the git commit and package name (or rather the git

  repository name).

  

- The "gating.yaml" URL is constructed based on ``DIST_GIT_URL_TEMPLATE``

+ For HTTP method, the remote rule URL is constructed based on ``HTTP_URL_TEMPLATE``

  specified in Greenwave configuration. The URL template is something like::

  

-     {DIST_GIT_BASE_URL}/{pkg_namespace}/{pkg_name}/raw/{rev}/f/gating.yaml

+     http://example.com/{pkg_namespace}{pkg_name}/raw/{rev}/f/gating.yaml

file modified
+29 -9
@@ -249,19 +249,39 @@ 

  

  

  Once the code is pushed, Greenwave will start to check if there is a

- gating.yaml file in your dist-git repo. If you didn't configure any

- gating.yaml file nothing will change.

+ remote rule file in your repo. If you didn't configure any remote rule file

+ nothing will change.

  

- Greenwave will check if a gating.yaml exists, if it does, it pulls it

+ Greenwave will check if a remote rule file exists, if it does, it pulls it

  down, loads it, and uses it to additionally evaluate the subject of the

  decision.

  

- Greenwave requires these configuration parameters ``KOJI_BASE_URL``,

- ``DIST_GIT_BASE_URL`` and ``DIST_GIT_URL_TEMPLATE``. Here's the default

- for the Fedora instance:

+ Greenwave requires these configuration parameters ``KOJI_BASE_URL`` and

+ ``REMOTE_RULE_POLICIES``.

+ 

+ ``REMOTE_RULE_POLICIES`` is a map, where the key is the subject type. There could be

+ a default pattern "*" used when no subject type matched. Old parameter ``DIST_GIT_URL_TEMPLATE``

+ is used if there is no default subject type, but please note that it is obsolete

+ and should not be used in new configurations. Each subject should contain a map of parameters

+ depending on a retrieval mechanism.

+ 

+ Greenwave has two mechanisms to retrieve the remote rule file: ``git archive`` is

+ using for side tags rules and using a git front-end. ``GIT_URL`` and ``GIT_PATH_TEMPLATE``

+ should be set for ``git archive`` mechanism, ``HTTP_URL_TEMPLATE``

+ should be set if you are going to use a git front-end.

+ 

+ Below is an example configuration where ``git archive`` is being used for "brew-build-group"

+ subject type and HTTP is being used for other:

  

  .. code-block:: console

  

-    DIST_GIT_BASE_URL = 'https://src.fedoraproject.org/'

-    DIST_GIT_URL_TEMPLATE = '{DIST_GIT_BASE_URL}{pkg_namespace}/{pkg_name}/raw/{rev}/f/gating.yaml'

-    KOJI_BASE_URL = 'https://koji.fedoraproject.org/kojihub'

+     REMOTE_RULE_POLICIES = {

+         'brew-build-group': {

+             'GIT_URL': 'git@gitlab.cee.redhat.com:devops/greenwave-policies/side-tags.git',

+             'GIT_PATH_TEMPLATE': '{pkg_namespace}/{pkg_name}.yaml'

+         },

+         '*': {

+             'HTTP_URL_TEMPLATE': 'https://src.fedoraproject.org/{pkg_namespace}/{pkg_name}/raw/{rev}/f/gating.yaml'

+         }

+     }

+     KOJI_BASE_URL = 'https://koji.fedoraproject.org/kojihub'

file modified
+20 -11
@@ -1,6 +1,7 @@ 

  # SPDX-License-Identifier: GPL-2.0+

  

  import logging

+ 

  from flask import Flask

  from greenwave.api_v1 import api

  from greenwave.monitor import monitor_api
@@ -16,12 +17,20 @@ 

  

  

  def _can_use_remote_rule(config):

-     # Ensure that the required config settings are set

-     return (bool(

-         config.get('DIST_GIT_BASE_URL') and

-         config.get('DIST_GIT_URL_TEMPLATE') and

-         config.get('KOJI_BASE_URL'))

-     )

+     if not config.get('KOJI_BASE_URL'):

+         return False

+ 

+     if config.get('DIST_GIT_URL_TEMPLATE'):

+         return True

+ 

+     if config.get('REMOTE_RULE_POLICIES'):

+         return all(

+             (conf_item.get('GIT_URL') and conf_item.get('GIT_PATH_TEMPLATE')) or

+             conf_item.get('HTTP_URL_TEMPLATE')

+             for conf_item in config.get('REMOTE_RULE_POLICIES').values()

+         )

+ 

+     return False

  

  

  def _has_remote_rule(policies):
@@ -50,10 +59,10 @@ 

  

      if not _can_use_remote_rule(app.config) and _has_remote_rule(app.config['policies']):

          raise RuntimeError(

-             "If you want to apply a RemoteRule"

-             " you need to configure 'DIST_GIT_BASE_URL', "

-             "'DIST_GIT_URL_TEMPLATE' and KOJI_BASE_URL in "

-             "your configuration."

+             'If you want to apply a RemoteRule, you must have "KOJI_BASE_URL" and '

+             '"DIST_GIT_URL_TEMPLATE" or "REMOTE_RULE_POLICIES" or both set in your configuration. '

+             'Each field in "REMOTE_RULE_POLICIES" map have to contain either '

+             '"GIT_URL"/"GIT_PATH_TEMPLATE" or "HTTP_URL_TEMPLATE".'

          )

  

      # register error handlers
@@ -82,4 +91,4 @@ 

  

      Returns a 200 response if the application is alive and able to serve requests.

      """

-     return ('Health check OK', 200, [('Content-Type', 'text/plain')])

+     return 'Health check OK', 200, [('Content-Type', 'text/plain')]

file modified
+17 -2
@@ -23,8 +23,23 @@ 

      WAIVERDB_API_URL = 'https://waiverdb.fedoraproject.org/api/v1.0'

  

      # Remote rule configuration

-     DIST_GIT_BASE_URL = 'https://src.fedoraproject.org/'

-     DIST_GIT_URL_TEMPLATE = '{DIST_GIT_BASE_URL}{pkg_namespace}/{pkg_name}/raw/{rev}/f/gating.yaml'

+     # NOTE: DIST_GIT_URL_TEMPLATE is obsolete and used here only for

+     # backward compatibility. They maybe removed in future versions. Use REMOTE_RULE_POLICIES['*']

+     # instead

+     DIST_GIT_URL_TEMPLATE = \

+         'https://src.fedoraproject.org/{pkg_namespace}{pkg_name}/raw/{rev}/f/gating.yaml'

+     REMOTE_RULE_POLICIES = {

+         'brew-build-group': {

+             'GIT_URL': 'git@gitlab.cee.redhat.com:devops/greenwave-policies/side-tags.git',

+             'GIT_PATH_TEMPLATE': '{pkg_namespace}/{pkg_name}.yaml'

+         },

+         '*': {

+             'HTTP_URL_TEMPLATE':

+                 'https://src.fedoraproject.org/{pkg_namespace}{pkg_name}/raw/{rev}/f/gating.yaml'

+         }

+     }

+     REMOTE_RULE_GIT_TIMEOUT = 30

+     REMOTE_RULE_GIT_MAX_RETRY = 3

      KOJI_BASE_URL = 'https://koji.fedoraproject.org/kojihub'

      # Options for outbound HTTP requests made by python-requests

      REQUESTS_TIMEOUT = (6.1, 15)

file modified
+22 -6
@@ -213,7 +213,7 @@ 

          return TestResultPassed(self.test_case_name, self.result_id)

  

  

- class InvalidGatingYaml(RuleNotSatisfied):

+ class InvalidRemoteRuleYaml(RuleNotSatisfied):

      """

      Remote policy parsing failed.

      """
@@ -236,7 +236,7 @@ 

          return None

  

  

- class MissingGatingYaml(RuleNotSatisfied):

+ class MissingRemoteRuleYaml(RuleNotSatisfied):

      """

      Remote policy not found in remote repository.

      """
@@ -433,6 +433,14 @@ 

          'required': SafeYAMLBool(optional=True, default=False),

      }

  

+     def _get_config_urls(self, rr_config, subject):

+         if subject in rr_config:

+             return rr_config[subject]

+         if '*' in rr_config:

+             return rr_config['*']

+         raise RuntimeError(f'Cannot use a remote rule for {subject} subject '

+                            f'as it has not been configured')

+ 

      def _get_sub_policies(self, policy, subject):

          if not subject.supports_remote_rule:

              return []
@@ -447,10 +455,18 @@ 

  

          # if the element is actually a container and not a pkg there will be a "-container"

          # string at the end of the "pkg_name" and it will not match with the one in the

-         # gating.yaml URL

+         # remote rule file URL

          if pkg_namespace == 'containers':

              pkg_name = re.sub('-container$', '', pkg_name)

-         response = greenwave.resources.retrieve_yaml_remote_rule(rev, pkg_name, pkg_namespace)

+         rr_policies_conf = current_app.config.get('REMOTE_RULE_POLICIES')

+         if not rr_policies_conf or '*' not in rr_policies_conf:

+             rr_policies_conf['*'] = {

+                 'HTTP_URL_TEMPLATE': current_app.config['DIST_GIT_URL_TEMPLATE']

+             }

+         cur_subject_config = self._get_config_urls(rr_policies_conf, policy.subject_type)

+         response = greenwave.resources.retrieve_yaml_remote_rule(

+             rev, pkg_name, pkg_namespace, cur_subject_config

+         )

  

          if response is None:

              # greenwave extension file not found
@@ -479,12 +495,12 @@ 

              policies = self._get_sub_policies(policy, subject)

          except SafeYAMLError as e:

              return [

-                 InvalidGatingYaml(subject, 'invalid-gating-yaml', str(e))

+                 InvalidRemoteRuleYaml(subject, 'invalid-gating-yaml', str(e))

              ]

  

          if policies is None:

              if self.required:

-                 return [MissingGatingYaml(subject)]

+                 return [MissingRemoteRuleYaml(subject)]

              return []

  

          answers = []

file modified
+65 -12
@@ -8,6 +8,9 @@ 

  

  import logging

  import re

+ from io import BytesIO

+ import tarfile

+ import subprocess

  import socket

  

  from urllib.parse import urlparse
@@ -47,6 +50,7 @@ 

      """

      Retrieves results from cache or ResultsDB.

      """

+ 

      def __init__(self, **args):

          super().__init__(**args)

          self.cache = {}
@@ -92,6 +96,7 @@ 

      """

      Retrieves waivers from WaiverDB.

      """

+ 

      def _retrieve_all(self, filters):

          if self.since:

              for filter_ in filters:
@@ -138,8 +143,8 @@ 

      if not source:

          raise NoSourceException(

              'Failed to retrieve SCM URL from Koji build "{}" at "{}" '

-             '(expected SCM URL in "source" attribute)'

-             .format(nvr, koji_url))

+             '(expected SCM URL in "source" attribute)'.format(nvr, koji_url)

+         )

  

      url = urlparse(source)

  
@@ -153,8 +158,8 @@ 

      if not rev:

          raise BadGateway(

              'Failed to parse SCM URL "{}" from Koji build "{}" at "{}" '

-             '(missing URL fragment with SCM revision information)'

-             .format(source, nvr, koji_url))

+             '(missing URL fragment with SCM revision information)'.format(source, nvr, koji_url)

+         )

  

      pkg_name = url.path.split('/')[-1]

      pkg_name = re.sub(r'\.git$', '', pkg_name)
@@ -162,29 +167,77 @@ 

  

  

  @cached

- def retrieve_yaml_remote_rule(rev, pkg_name, pkg_namespace):

-     """ Retrieve cached gating.yaml content for a given rev from the dist-git web UI. """

+ def retrieve_yaml_remote_rule(rev, pkg_name, pkg_namespace, rr_config):

+     """ Retrieve a remote rule file content from the given repo"""

+     if rr_config.get('GIT_URL') and rr_config.get('GIT_PATH_TEMPLATE'):

+         return _retrieve_yaml_remote_rule_git_archive(

+             pkg_name, pkg_namespace, rr_config['GIT_URL'], rr_config['GIT_PATH_TEMPLATE']

+         )

+     else:

+         return _retrieve_yaml_remote_rule_web(

+             rev, pkg_name, pkg_namespace, rr_config['HTTP_URL_TEMPLATE']

+         )

+ 

+ 

+ _retrieve_remote_rule_error = 'Error occurred while retrieving a remote rule file from the repo.'

+ 

+ 

+ def _retrieve_yaml_remote_rule_web(rev, pkg_name, pkg_namespace, url_template):

+     """ Retrieve a remote rule file content from the git web UI. """

      data = {

-         "DIST_GIT_BASE_URL": (current_app.config['DIST_GIT_BASE_URL'].rstrip('/') +

-                               ('/' if pkg_namespace else '')),

-         "pkg_namespace": pkg_namespace,

+         "pkg_namespace": pkg_namespace + ('/' if pkg_namespace else ''),

          "pkg_name": pkg_name,

          "rev": rev

      }

-     url = current_app.config['DIST_GIT_URL_TEMPLATE'].format(**data)

+     url = url_template.format(**data)

      response = requests_session.request('HEAD', url)

      if response.status_code == 404:

          return None

  

      if response.status_code != 200:

-         raise BadGateway('Error occurred looking for gating.yaml file in the dist-git repo.')

+         raise BadGateway(_retrieve_remote_rule_error)

  

-     # gating.yaml found...

+     # remote rule file found...

      response = requests_session.request('GET', url)

      response.raise_for_status()

      return response.content

  

  

+ def _retrieve_yaml_remote_rule_git_archive(pkg_name, pkg_namespace, git_url, path_template):

+     """ Retrieve  a remote rule file content from a git repo using git archive. """

+     git_path = path_template.format(pkg_name=pkg_name, pkg_namespace=pkg_namespace)

+     cmd = ['git', 'archive', f'--remote={git_url}', 'master', git_path]

+     # Retry thrice if TimeoutExpired exception is raised

+     MAX_RETRY = current_app.config.get('REMOTE_RULE_GIT_MAX_RETRY', 3)

+     git_archive = None

+     for _ in range(MAX_RETRY):

+         try:

+             git_archive = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

+             output, error_output = git_archive.communicate(

+                 timeout=current_app.config.get('REMOTE_RULE_GIT_TIMEOUT', 30)

+             )

+             break

+         except subprocess.TimeoutExpired:

+             if git_archive:

+                 git_archive.kill()

+             continue

+ 

+     if git_archive.returncode != 0:

+         error_output = error_output.decode('utf-8')

+         if 'path not found' in error_output:

+             return None

+ 

+         cmd_str = ' '.join(cmd)

+         log.error('The following exception occurred while running "%s": %s', cmd_str, error_output)

+         raise BadGateway(_retrieve_remote_rule_error)

+ 

+     # Convert the output to a file-like object with BytesIO, then tar can read it

+     # in memory rather than writing it to a file first

+     remote_rule_archive = tarfile.open(fileobj=BytesIO(output))

+     remote_rule_content = remote_rule_archive.extractfile(git_path).read().decode('utf-8')

+     return remote_rule_content

+ 

+ 

  # NOTE - not cached.

  def retrieve_decision(greenwave_url, data):

      response = requests_session.post(greenwave_url, json=data)

@@ -29,7 +29,8 @@ 

      mock_load_policies.return_value = policies

  

      config = TestingConfig()

-     config.DIST_GIT_BASE_URL = ''

+     config.DIST_GIT_URL_TEMPLATE = ''

+     config.REMOTE_RULE_POLICIES = {}

  

      expected_error = 'If you want to apply a RemoteRule'

  
@@ -37,23 +38,40 @@ 

          create_app(config)

  

  

+ def test_can_use_remote_rule_http_fallback():

+     """ Test that _can_use_remote_rule verifies the configuration properly if HTTP is used. """

+     config = {

+         'KOJI_BASE_URL': 'https://koji.domain.local/kojihub',

+         'DIST_GIT_URL_TEMPLATE':

+             'https://dist-git.domain.local/{pkg_namespace}{pkg_name}/raw/{rev}/f/gating.yaml'

+     }

+     assert _can_use_remote_rule(config) is True

+ 

+ 

  def test_can_use_remote_rule_http():

      """ Test that _can_use_remote_rule verifies the configuration properly if HTTP is used. """

      config = {

-         'DIST_GIT_BASE_URL': 'https://dist-git.domain.local',

          'KOJI_BASE_URL': 'https://koji.domain.local/kojihub',

-         'DIST_GIT_URL_TEMPLATE': ('{DIST_GIT_BASE_URL}{pkg_namespace}/{pkg_name}/raw/{rev}/f/'

-                                   'gating.yaml')

+         'REMOTE_RULE_POLICIES': {

+             '*': {

+                 'HTTP_URL_TEMPLATE': 'https://src.fedoraproject.org/{pkg_namespace}{pkg_name}/'

+                                      'raw/{rev}/f/gating.yaml'

+             }

+         }

      }

      assert _can_use_remote_rule(config) is True

  

  

  @pytest.mark.parametrize('config', (

      {

-         'DIST_GIT_BASE_URL': 'https://dist-git.domain.local',

+         'REMOTE_RULE_POLICIES': {

+             'brew-build-group': {

+                 'GIT_URL': 'git@gitlab.cee.redhat.com:devops/greenwave-policies/side-tags.git',

+                 'GIT_PATH_TEMPLATE': '{pkg_namespace}/{pkg_name}.yaml'

+             }

+         },

      },

      {

-         'DIST_GIT_BASE_URL': 'https://dist-git.domain.local',

          'KOJI_BASE_URL': 'https://koji.domain.local/kojihub'

      }

  ))

@@ -17,8 +17,8 @@ 

      TestResultMissing,

      TestResultFailed,

      TestResultPassed,

-     InvalidGatingYaml,

-     MissingGatingYaml,

+     InvalidRemoteRuleYaml,

+     MissingRemoteRuleYaml,

      OnDemandPolicy

  )

  from greenwave.resources import ResultsRetriever
@@ -550,7 +550,7 @@ 

                      results = DummyResultsRetriever()

                      decision = policy.check('fedora-26', subject, results)

                      assert len(decision) == 1

-                     assert isinstance(decision[0], InvalidGatingYaml)

+                     assert isinstance(decision[0], InvalidRemoteRuleYaml)

                      assert decision[0].is_satisfied is False

  

  
@@ -640,7 +640,7 @@ 

                  results = DummyResultsRetriever()

                  decision = policy.check('fedora-rawhide', subject, results)

                  assert len(decision) == 1

-                 assert isinstance(decision[0], MissingGatingYaml)

+                 assert isinstance(decision[0], MissingRemoteRuleYaml)

                  assert not decision[0].is_satisfied

                  assert decision[0].subject.identifier == subject.identifier

  
@@ -1109,7 +1109,7 @@ 

                  results = DummyResultsRetriever()

                  decision = policy.check('fedora-26', subject, results)

                  assert len(decision) == 1

-                 assert isinstance(decision[0], MissingGatingYaml)

+                 assert isinstance(decision[0], MissingRemoteRuleYaml)

                  assert not decision[0].is_satisfied

                  assert decision[0].subject.identifier == subject.identifier

  

@@ -1,5 +1,7 @@ 

  # SPDX-License-Identifier: GPL-2.0+

  

+ import subprocess

+ import io

  import socket

  from requests.exceptions import ConnectionError, HTTPError

  
@@ -120,7 +122,9 @@ 

              response = mock.MagicMock()

              response.status_code = 404

              session.request.return_value = response

-             retrieve_yaml_remote_rule("deadbeaf", "pkg", "")

+             retrieve_yaml_remote_rule(

+                 "deadbeaf", "pkg", "", app.config['REMOTE_RULE_POLICIES']['*']

+             )

  

              expected_call = mock.call(

                  'HEAD', 'https://src.fedoraproject.org/pkg/raw/deadbeaf/f/gating.yaml')
@@ -138,7 +142,9 @@ 

              ]

  

              with pytest.raises(HTTPError) as excinfo:

-                 retrieve_yaml_remote_rule("deadbeaf", "pkg", "")

+                 retrieve_yaml_remote_rule(

+                     "deadbeaf", "pkg", "", app.config['REMOTE_RULE_POLICIES']['*']

+                 )

  

              assert str(excinfo.value) == (

                  '502 Server Error: Something went terribly wrong... for url: '
@@ -146,6 +152,69 @@ 

              )

  

  

+ @mock.patch('tarfile.open')

+ @mock.patch('subprocess.Popen')

+ def test_retrieve_yaml_remote_rule_git_archive(mock_subp, mock_tar):

+     # Make the git archive call return bytes

+     mock_subp.return_value.communicate.return_value = (b'tar file', '')

+     mock_subp.return_value.returncode = 0

+     # Make the tar archive, based on the return value of git archive, return a file-like

+     # object representing the gating.yaml file

+     mock_tar.return_value.extractfile.return_value = io.BytesIO(b'some gating yaml file')

+ 

+     app = greenwave.app_factory.create_app()

+     rr_config = {

+         'GIT_URL': 'git://dist-git.domain.local/abc/abc.git',

+         'GIT_PATH_TEMPLATE': '{pkg_namespace}/{pkg_name}.yaml'

+     }

+     with app.app_context():

+         gating_yaml = retrieve_yaml_remote_rule('abcdef', 'python-requests', 'rpms', rr_config)

+ 

+     assert gating_yaml == 'some gating yaml file'

+     expected_cmd = [

+         'git', 'archive', '--remote=git://dist-git.domain.local/abc/abc.git',

+         'master', 'rpms/python-requests.yaml']

+     mock_subp.assert_called_once_with(expected_cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE)

+     tar_file = mock_tar.call_args[1]['fileobj'].read()

+     assert tar_file == b'tar file'

+     mock_tar.return_value.extractfile.assert_called_once_with('rpms/python-requests.yaml')

+ 

+ 

+ @mock.patch('subprocess.Popen')

+ def test_retrieve_yaml_remote_rule_git_archive_no_file(mock_subp):

+     # Make the git archive command return an error saying the file isn't in the repo

+     mock_subp.return_value.communicate.return_value = \

+         (None, b'remote: fatal: path not found: xxx.yaml')

+     mock_subp.return_value.returncode = 1

+ 

+     app = greenwave.app_factory.create_app()

+     rr_config = {

+         'GIT_URL': 'git://dist-git.domain.local/abc/abc.git',

+         'GIT_PATH_TEMPLATE': '{pkg_namespace}/{pkg_name}.yaml'

+     }

+     with app.app_context():

+         gating_yaml = retrieve_yaml_remote_rule('master', 'python-requests', 'rpms', rr_config)

+ 

+     assert gating_yaml is None

+ 

+ 

+ @mock.patch('subprocess.Popen')

+ def test_retrieve_yaml_remote_rule_git_archive_error(mock_subp):

+     # Make the git archive command return an error

+     mock_subp.return_value.communicate.return_value = (None, b'remote: fatal: some error')

+     mock_subp.return_value.returncode = 1

+ 

+     app = greenwave.app_factory.create_app()

+     rr_config = {

+         'GIT_URL': 'git://dist-git.domain.local/abc/abc.git',

+         'GIT_PATH_TEMPLATE': '{pkg_namespace}/{pkg_name}.yaml'

+     }

+     expected_error = 'Error occurred while retrieving a remote rule file from the repo.'

+     with pytest.raises(BadGateway, match=expected_error):

+         with app.app_context():

+             retrieve_yaml_remote_rule('master', 'python-requests', 'rpms', rr_config)

+ 

+ 

  @mock.patch('greenwave.resources.xmlrpc.client.ServerProxy')

  def test_retrieve_scm_from_koji_build_socket_error(mock_xmlrpc_client):

      mock_auth_server = mock_xmlrpc_client.return_value

@@ -6,7 +6,7 @@ 

      TestResultFailed,

      TestResultMissing,

      TestResultMissingWaived,

-     InvalidGatingYaml,

+     InvalidRemoteRuleYaml,

  )

  

  from greenwave.subjects.subject import Subject
@@ -23,7 +23,7 @@ 

      testSubject, 'test', None)

  testResultMissingWaived = TestResultMissingWaived(

      testSubject, 'test', None)

- testInvalidGatingYaml = InvalidGatingYaml(

+ testInvalidGatingYaml = InvalidRemoteRuleYaml(

      testSubject, 'test', 'Missing !Policy tag')

  

  

@@ -1,7 +1,7 @@ 

  # SPDX-License-Identifier: GPL-2.0+

  

  from greenwave.policies import (

-     InvalidGatingYaml,

+     InvalidRemoteRuleYaml,

      TestResultMissing,

      TestResultFailed,

  )
@@ -79,7 +79,7 @@ 

  

  def test_waive_invalid_gatin_yaml():

      answers = [

-         InvalidGatingYaml(

+         InvalidRemoteRuleYaml(

              subject=test_subject(),

              test_case_name='invalid-gating-yaml',

              details='',

JIRA: FACTORY-5691
This also partially reverts commit be53a78.

Signed-off-by: Valerij Maljulin vmaljuli@redhat.com

1 new commit added

  • fixup! Allow usage of the side tags repo
4 years ago

I believe we want to use different URL for the side-tag artifact types ("brew-build-group" internally) but still keep the current dist-git URL for other artifact types.

MAX_RETRY and timeout should be in configuration.

Join with single space - it will be easier to copy the command.

Is there other way to find out if the file does not exist? Checking error string is not a good idea.

You did good changing the name, but we really need to remember to change the conf both internally and in fedora.

Yeah, that's true. Can we maybe keep DIST_GIT_BASE_URL and DIST_GIT_URL_TEMPLATE and rename the REMOTE_RULE_GIT_BASE_URL and REMOTE_RULE_GIT_URL_TEMPLATE to be more specific about the group artifacts? Maybe just GROUP_REMOTE_RULE_GIT_BASE_URL and GROUP_REMOTE_RULE_GIT_URL_TEMPLATE.

And then when we'll work on FACTORY-5058 we'll have to use DIST_GIT_* when people need to retrieve "standard" remote rules and GROUP_REMOTE_RULE_GIT_* when people need to retrieve group remote rules...

Maybe this shouldn't mention dist-git, but just say "remote repo".

Optional: this comment is correct. But the user might not know that for rules hosted on dist-git we use http and for rules for groups we use git archive. Maybe you could keep it like that and add in parenthesis this info (if they are hosted on dist-git or if they are about group rules).

I think it would be ideally something generic like:

# Maps artifact type to remote rule base URL.
REMOTE_RULE_GIT_BASE_URLS = {
    "brew-build-group": "https://gitlab.cee.redhat.com/devops/greenwave-policies/side-tags.git",
}
# Fallback URL for artifact types not in REMOTE_RULE_GIT_BASE_URLS.
REMOTE_RULE_GIT_BASE_URL_FALLBACK = "https://src.fedoraproject.org/"

I think I'd prefer to have in that repo only:
'{pkg_namespace}/{pkg_name}.yaml'
...having "_gating" seems redundant to me since this new repo is made only for these policies and most likely it won't contain other kind of files in there.

1 new commit added

  • fixup! Allow usage of the side tags repo
4 years ago

3 new commits added

  • fixup! Allow usage of the side tags repo
  • fixup! Allow usage of the side tags repo
  • Allow usage of the side tags repo
4 years ago

Remove this function, just use the retrieve_yaml_remote_rule_*() directly -- having a boolean flag as argument, as in this case, makes the code hard to read (especially in caller).

It's not clear from the docstring nor the function name that this function is specifically for side-tags.

Also, it will be difficult to add support for any new artifact type to use another git repo (in the greenwave-policies GitLab group).

Why not use a mapping for the configuration as I mentioned before (REMOTE_RULE_GIT_BASE_URLS)?

Could you squash the commits in only one?

1 new commit added

  • fixup! fixup! Allow usage of the side tags repo
4 years ago

Could you squash the commits in only one?

Of course, I'll squash it, once I got '+1'

Minor: this _retrieve_gating_yaml_error is used also here, but with the git-archive approach you shouldn't use dist-git.
So maybe we can change _retrieve_gating_yaml_error to be just something like: "Error occurred looking for the remote rules file in the repo."

It looks good, just a really minor comment. +1
@lholecek do you wanna check it too one last time?

Changing the str.format() parameter names also breaks the previous configuration.

Can we just assume git is installed and drop this? The function is too complex even without it.

This should now depend on the artifact/subject type.

Changing the str.format() parameter names also breaks the previous configuration.

no it doesn't (see replace in policies.py)

This should now depend on the artifact/subject type.

It's just a check if everything is configured properly, which doesn't care about the subject type.

rebased onto 5e118b795ad0586056b117cdcbc97c08e658641a

4 years ago

This should now depend on the artifact/subject type.

It's just a check if everything is configured properly, which doesn't care about the subject type.

If user wants the URL for particular subject type, they don't necessarily require URL * -- i.e. with current code, it won't work if the URL is defined for the given subject type but undefined for *.

I don't see patterns being useful in the config dict -- it makes things much more complicated.

Is this useful by itself? Maybe just expand it in HTTP_URL_TEMPLATE below?

If user wants the URL for particular subject type, they don't necessarily require URL * -- i.e. with current code, it won't work if the URL is defined for the given subject type but undefined for *.

Why they shouldn't use something by default? For example if there is only one subject type it could be used as '*'...

1 new commit added

  • fixup! Allow usage of the side tags repo
4 years ago

Is this useful by itself? Maybe just expand it in HTTP_URL_TEMPLATE below?

Merged URL template with a base URL

If user wants the URL for particular subject type, they don't necessarily require URL * -- i.e. with current code, it won't work if the URL is defined for the given subject type but undefined for *.

Why they shouldn't use something by default? For example if there is only one subject type it could be used as '*'...

Why are they required to use "*" pattern? Why not simply drop the wildcard support and fallback to DIST_GIT_* options:

DIST_GIT_BASE_URL = 'https://src.fedoraproject.org/'
DIST_GIT_URL_TEMPLATE = '{DIST_GIT_BASE_URL}{pkg_namespace}/{pkg_name}/raw/{rev}/f/gating.yaml'
KOJI_BASE_URL = 'https://koji.fedoraproject.org/kojihub'
REMOTE_RULE_POLICIES = {
    'brew-build-group': {
        'GIT_URL': 'git@gitlab.cee.redhat.com:devops/greenwave-policies/side-tags.git',
        'GIT_PATH_TEMPLATE': '{pkg_namespace}/{pkg_name}.yaml'
    },
}

In subject type configuration, supports_remote_rule is False by default.

The new subject types for side-tags either need to be defined in conf/subject_types/ or there needs to be additional logic when getting supports_remote_rule property?

2 new commits added

  • fixup! Allow usage of the side tags repo
  • Allow usage of the side tags repo
4 years ago

"*" is no longer mandatory

Oh, why was invalid-gating-yaml renamed? This potentially breaks the functionality for someone.

Looks like this branch is redundant now.

There could be a default pattern "*".

You can remove this sentence.

I'm not a fan of deprecating the old configuration - I wouldn't bother removing it in near future.

Also using patterns in the new config dict can lead to some unexpected behavior because someone may define brew-build-group before brew* but in Python 3.6 the order of values in the dict is undefined. Also, the example I gave is pretty useless, I really cannot think about what I would use the patterns for.

Let's wait for Giulia to review ... and we can talk about it later.

In the new repo the file with the policies will not be called "gating.yaml" anymore, so I think that's the reason behind it. But yeah, it would break the functionality... I would just leave "invalid-gating-yaml" as Lukas pointed out just for this one.

I think we should it keep it backward compatible. We could maintain also the old configuration without deprecating it, because it doesn't really harm in keeping it. It's not a lot of more code involved... So I don't mind in keeping it and don't deprecate it. We'll probably never change it in Fedora, so I think it's fine to keep it.

This needs to be expanded because supports_remote_rule is False by default (unless you create custom subject type in conf/subject_types/).

rebased onto 8467ea86b43f9d47a234f12e00f963ee33188fd2

4 years ago

rebased onto 54c835961b73470f7d87de096c67703f3303a439

4 years ago

rebased onto f823134

4 years ago

Pull-Request has been merged by vmaljulin

4 years ago