From 23537db256d288d692bb79eb8c49e963baaa7ce3 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Aug 23 2017 20:24:32 +0000 Subject: [PATCH 1/3] Create a custom FedoraAtomicCi rule The issue is that bodhi always provides an NVR but if the package is not being considered by the Fedora Atomic CI pipeline, its corresponding git repository isn't being clone and its `original_spec_nvr` value not included in the `pipeline.package.ignore` fedmsg message. In other words, relying on `pipeline.package.ignore` messages when querying info about a particular NVR is never going to work. With this rule we are specifying a list of packages of interest (to be filled) and a test_case_name. Every package that is part of the list of interest must satisfy this test case, all the others are considered satisfying the rule. Signed-off-by: Pierre-Yves Chibon --- diff --git a/conf/policies/fedora.yaml b/conf/policies/fedora.yaml index 53b5e28..3d6644b 100644 --- a/conf/policies/fedora.yaml +++ b/conf/policies/fedora.yaml @@ -8,4 +8,16 @@ decision_context: bodhi_update_push_stable rules: - !PassingTestCaseRule {test_case_name: dist.abicheck} - !PassingTestCaseRule {test_case_name: dist.rpmdeplint} - - !PassingTestCaseRule {test_case_name: dist.upgradepath} \ No newline at end of file + - !PassingTestCaseRule {test_case_name: dist.upgradepath} +# Fedora Atomic CI pipeline +# http://fedoraproject.org/wiki/CI +--- !Policy +id: "atomic_ci_pipeline_results" +product_versions: + - fedora-26 +decision_context: bodhi_update_push_stable +rules: + - !FedoraAtomicCi { + test_case_name: org.centos.prod.ci.pipeline.complete, + repos: ['kernel', 'rpm-ostree'], + } diff --git a/greenwave/policies.py b/greenwave/policies.py index b95fe03..441a26c 100644 --- a/greenwave/policies.py +++ b/greenwave/policies.py @@ -159,6 +159,47 @@ class PassingTestCaseRule(Rule): return "%s(test_case_name=%r)" % (self.__class__.__name__, self.test_case_name) +class FedoraAtomicCi(Rule): + """ + This rule requires that the value of the specified field is part of a + specified list. + """ + yaml_tag = u'!FedoraAtomicCi' + yaml_loader = yaml.SafeLoader + + def __init__(self, test_case_name, repos): + self.test_case_name = test_case_name + self.repos = repos + + def check(self, item, results, waivers): + """ Check that the request satisfies the requirement of the Fedora + Atomic CI pipeline. + + If the request (item) corresponds to a request for Fedora Atomic CI + results request and if it is the case, check that the package for + which this request is, is in the allowed list. + If it is, then proceed as usual using the specified test_case_name. + If the package is not in the list, then consider this requirement + moot and satisfied. + + """ + + if 'original_spec_nvr' not in item: + return RuleSatisfied() + + nvr = item['original_spec_nvr'] + pkg_name = nvr.rsplit('-', 2)[0] + if pkg_name not in self.repos: + return RuleSatisfied() + + rule = PassingTestCaseRule(test_case_name=self.test_case_name) + return rule.check(item, results, waivers) + + def __repr__(self): + return "%s(test_case_name=%s, repos=%r)" % ( + self.__class__.__name__, self.test_case_name, self.repos) + + class Policy(yaml.YAMLObject): yaml_tag = u'!Policy' yaml_loader = yaml.SafeLoader diff --git a/greenwave/tests/test_policies.py b/greenwave/tests/test_policies.py index 7af3b48..61ef292 100644 --- a/greenwave/tests/test_policies.py +++ b/greenwave/tests/test_policies.py @@ -2,7 +2,13 @@ # SPDX-License-Identifier: GPL-2.0+ from greenwave.app_factory import create_app -from greenwave.policies import summarize_answers, RuleSatisfied, TestResultMissing, TestResultFailed +from greenwave.policies import ( + summarize_answers, + RuleSatisfied, + TestResultMissing, + TestResultFailed, + FedoraAtomicCi, +) def test_summarize_answers(): @@ -23,7 +29,9 @@ def test_load_policies(): app = create_app('greenwave.config.TestingConfig') assert len(app.config['policies']) > 0 assert any(policy.id == '1' for policy in app.config['policies']) - assert any(policy.decision_context == 'errata_newfile_to_qe' for policy in - app.config['policies']) - assert any(rule.test_case_name == 'dist.rpmdiff.analysis.abi_symbols' for policy in - app.config['policies'] for rule in policy.rules) + assert any(policy.decision_context == 'errata_newfile_to_qe' + for policy in app.config['policies']) + assert any(getattr(rule, 'test_case_name', None) == 'dist.rpmdiff.analysis.abi_symbols' + for policy in app.config['policies'] for rule in policy.rules) + assert any(isinstance(rule, FedoraAtomicCi) + for policy in app.config['policies'] for rule in policy.rules) From bb2445fd056b57b6f64680f1ea8e8e0b5ea27fcf Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Aug 24 2017 16:49:44 +0000 Subject: [PATCH 2/3] Set an Access-Control-Allow-Origin header to the /decision endpoint This header is added if the configuration file contains a CORS_URL and add supports AJAX support for this endpoint. Relates https://pagure.io/greenwave/issue/65 Signed-off-by: Pierre-Yves Chibon --- diff --git a/greenwave/api_v1.py b/greenwave/api_v1.py index 650bf17..fb8d285 100644 --- a/greenwave/api_v1.py +++ b/greenwave/api_v1.py @@ -4,12 +4,20 @@ import requests from flask import Blueprint, request, current_app, jsonify from werkzeug.exceptions import BadRequest, NotFound, UnsupportedMediaType from greenwave.policies import summarize_answers +from greenwave.utils import insert_headers api = (Blueprint('api_v1', __name__)) requests_session = requests.Session() +@api.route('/decision', methods=['OPTIONS']) +def make_decision_options(): + """ Handles the OPTIONS requests to the /decision endpoint. """ + resp = current_app.make_default_options_response() + + return insert_headers(resp) + @api.route('/decision', methods=['POST']) def make_decision(): """ @@ -130,4 +138,7 @@ def make_decision(): 'unsatisfied_requirements': [answer.to_json() for answer in answers if not answer.is_satisfied], } - return jsonify(res), 200 + resp = jsonify(res) + resp = insert_headers(resp) + resp.status_code = 200 + return resp diff --git a/greenwave/utils.py b/greenwave/utils.py index 6ef54b7..91c1869 100644 --- a/greenwave/utils.py +++ b/greenwave/utils.py @@ -3,7 +3,8 @@ import os import glob import yaml -from flask import jsonify, current_app +from functools import wraps +from flask import jsonify, current_app, make_response, request from flask.config import Config from werkzeug.exceptions import HTTPException @@ -25,6 +26,9 @@ def json_error(error): current_app.logger.exception('Returning 500 to user.') response = jsonify(message=str(error.message)) response.status_code = 500 + + response = insert_headers(response) + return response @@ -70,3 +74,15 @@ def load_policies(policies_dir): for policy_pathname in policy_pathnames: policies.extend(yaml.safe_load_all(open(policy_pathname, 'r'))) return policies + + +def insert_headers(response): + """ Insert the CORS headers for the give reponse if there are any + configured for the application. + """ + if current_app.config.get('CORS_URL'): + response.headers['Access-Control-Allow-Origin'] = \ + current_app.config['CORS_URL'] + response.headers['Access-Control-Allow-Headers'] = 'Content-Type' + response.headers['Access-Control-Allow-Method'] = 'POST, OPTIONS' + return response From 4ecceb2e04685d28630a8b9272af469332736caa Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Aug 24 2017 16:51:35 +0000 Subject: [PATCH 3/3] New /version API endpoint This API endpoint simply returns the version of the running greenwave instance. This is most helpful when deploying a new greenwave to see if it made it to production/staging or not. Signed-off-by: Pierre-Yves Chibon --- diff --git a/greenwave/api_v1.py b/greenwave/api_v1.py index fb8d285..d5637e7 100644 --- a/greenwave/api_v1.py +++ b/greenwave/api_v1.py @@ -3,6 +3,7 @@ import requests from flask import Blueprint, request, current_app, jsonify from werkzeug.exceptions import BadRequest, NotFound, UnsupportedMediaType +from greenwave import __version__ from greenwave.policies import summarize_answers from greenwave.utils import insert_headers @@ -11,6 +12,15 @@ api = (Blueprint('api_v1', __name__)) requests_session = requests.Session() +@api.route('/version', methods=['GET']) +def version(): + """ Returns the current running version. """ + resp = jsonify({'version': __version__}) + resp = insert_headers(resp) + resp.status_code = 200 + return resp + + @api.route('/decision', methods=['OPTIONS']) def make_decision_options(): """ Handles the OPTIONS requests to the /decision endpoint. """ @@ -18,6 +28,7 @@ def make_decision_options(): return insert_headers(resp) + @api.route('/decision', methods=['POST']) def make_decision(): """