zuul/zuul/driver/github/githubtrigger.py

276 lines
11 KiB
Python

# Copyright 2015 Hewlett-Packard Development Company, L.P.
# Copyright 2023 Acme Gating, LLC
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
import voluptuous as vs
from zuul.trigger import BaseTrigger
from zuul.driver.github.githubmodel import GithubEventFilter
from zuul.driver.github import githubsource
from zuul.driver.util import scalar_or_list, to_list, make_regex, ZUUL_REGEX
from zuul.configloader import DeprecationWarning
class GithubUnlabelDeprecation(DeprecationWarning):
zuul_error_name = 'Github unlabel Deprecation'
zuul_error_message = """The 'unlabel' trigger attribute
is deprecated. Use 'label' instead."""
class GithubEventListDeprecation(DeprecationWarning):
zuul_error_name = 'Github event list Deprecation'
zuul_error_message = """Specifying the 'event' trigger attribute
as a list is deprecated. Use a single item instead."""
class GithubRequireStatusDeprecation(DeprecationWarning):
zuul_error_name = 'Github require-status Deprecation'
zuul_error_message = """The 'require-status' trigger attribute
is deprecated. Use 'require' instead."""
class GithubRequestedActionDeprecation(DeprecationWarning):
zuul_error_name = 'Github requested action Deprecation'
zuul_error_message = """The 'requested' value for the 'action'
trigger attribute is deprecated. Use 'rerequested' instead."""
class GithubTriggerConfigurationWarning(DeprecationWarning):
zuul_error_name = 'Github Trigger Configuration Warning'
class GithubTrigger(BaseTrigger):
name = 'github'
log = logging.getLogger("zuul.trigger.GithubTrigger")
def getEventFilters(self, connection_name, trigger_config,
parse_context):
efilters = []
pcontext = parse_context
new_schema = getNewSchema()
for trigger in to_list(trigger_config):
# Deprecated in 8.3.0 but warning added in 10.0
if 'require-status' in trigger:
with pcontext.confAttr(trigger, 'require-status'):
pcontext.accumulator.addError(
GithubRequireStatusDeprecation())
# Deprecated with warning in 10.0
if 'unlabel' in trigger:
with pcontext.confAttr(trigger, 'unlabel'):
pcontext.accumulator.addError(
GithubUnlabelDeprecation())
# Deprecated with warning in 10.0
if isinstance(trigger.get('event', None), list):
with pcontext.confAttr(trigger, 'event'):
pcontext.accumulator.addError(
GithubEventListDeprecation())
try:
new_schema(trigger)
except vs.Invalid as e:
pcontext.accumulator.addError(
GithubTriggerConfigurationWarning(
"The trigger configuration as supplied "
f"has an error: {e}"))
with pcontext.confAttr(trigger, 'event') as attr:
types = [make_regex(x, pcontext)
for x in to_list(attr)]
with pcontext.confAttr(trigger, 'branch') as attr:
branches = [make_regex(x, pcontext)
for x in to_list(attr)]
with pcontext.confAttr(trigger, 'ref') as attr:
refs = [make_regex(x, pcontext)
for x in to_list(attr)]
with pcontext.confAttr(trigger, 'comment') as attr:
comments = [make_regex(x, pcontext)
for x in to_list(attr)]
# This is a compatibility layer to map the action 'requested' back
# to the original action 'rerequested'.
# TODO: Remove after zuul 5.0
# Note the original backwards compat handling for this did
# not allow 'requested' as a list, so we don't do that
# here either.
if trigger.get('action') == 'requested':
trigger['action'] = 'rerequested'
with pcontext.confAttr(trigger, 'action'):
pcontext.accumulator.addError(
GithubRequestedActionDeprecation())
f = GithubEventFilter(
connection_name=connection_name,
trigger=self,
types=types,
actions=to_list(trigger.get('action')),
branches=branches,
refs=refs,
comments=comments,
check_runs=to_list(trigger.get('check')),
labels=to_list(trigger.get('label')),
unlabels=to_list(trigger.get('unlabel')),
states=to_list(trigger.get('state')),
statuses=to_list(trigger.get('status')),
required_statuses=to_list(trigger.get('require-status')),
require=trigger.get('require'),
reject=trigger.get('reject'),
)
efilters.append(f)
return efilters
def onPullRequest(self, payload):
pass
def getNewSchema():
# For now, this is only used to raise deprecation errors if the
# syntax does not match. This was added in 10.0. When we're
# ready to enforce this, rename this method getSchema.
base_schema = vs.Schema({
vs.Required('event'): vs.Any('pull_request',
'pull_request_review',
'push',
'check_run'),
'require': githubsource.getRequireSchema(),
'reject': githubsource.getRejectSchema(),
})
# Pull request
pull_request_base_schema = base_schema.extend({
vs.Required('event'): 'pull_request',
'branch': scalar_or_list(vs.Any(ZUUL_REGEX, str)),
})
pull_request_default_schema = pull_request_base_schema.extend({
'action': scalar_or_list(vs.Any(
'opened', 'changed', 'closed', 'reopened')),
})
pull_request_comment_schema = pull_request_base_schema.extend({
vs.Required('action'): 'comment',
'comment': scalar_or_list(vs.Any(ZUUL_REGEX, str)),
})
pull_request_labeled_schema = pull_request_base_schema.extend({
vs.Required('action'): scalar_or_list(vs.Any('labeled', 'unlabeled')),
'label': scalar_or_list(str),
})
pull_request_status_schema = pull_request_base_schema.extend({
vs.Required('action'): 'status',
'status': scalar_or_list(str),
})
# Pull request review
pull_request_review_base_schema = base_schema.extend({
vs.Required('event'): 'pull_request_review',
'branch': scalar_or_list(vs.Any(ZUUL_REGEX, str)),
})
# The docs for this are at:
# https://docs.github.com/en/webhooks/webhook-events-and-payloads?actionType=dismissed#pull_request_review
# They specify "dismissed, approved, changes_requested"
# But we have also experimentally seen "commented"
# The graphql docs are at:
# https://docs.github.com/en/enterprise-server@3.12/graphql/reference/enums#pullrequestreviewstate
# And they additionally specify "commented" and "pending".
# It's unclear whether "pending" can show up in the webhook (it
# may only appear in a graphql query of a user's own unsaved
# reviews) but since we've seen the extra "commented" value, let's
# be generous and accept "pending" as well.
pull_request_review_schema = pull_request_review_base_schema.extend({
'action': scalar_or_list(vs.Any('submitted', 'dismissed')),
'state': scalar_or_list(vs.Any(
'approved', 'commented', 'changes_requested',
'dismissed', 'pending')),
})
# Push
push_schema = base_schema.extend({
vs.Required('event'): 'push',
'ref': scalar_or_list(vs.Any(ZUUL_REGEX, str)),
})
# Check run
check_run_schema = base_schema.extend({
vs.Required('event'): 'check_run',
# The 'requested' action is deprecated, but we have a separate,
# more specific deprecation for this.
'action': scalar_or_list(vs.Any('rerequested', 'completed')),
'check': scalar_or_list(str),
})
init_schema = base_schema.extend({}, extra=vs.ALLOW_EXTRA)
def validate(data):
# This method could be a simple vs.Any() across all the
# possibilities, but sometimes the error messages are less
# intuitive (e.g., indicating that the action is wrong rather
# than that a user has added an attribute that is incompatible
# with the action). Instead, we assume the user got the event
# and/or action right first, then apply the appropriate schema
# for what they selected.
event = data.get('event')
# TODO: run this unconditionally after
# GithubEventListDeprecation is complete
if isinstance(event, str):
# First, make sure the common items are correct.
init_schema(data)
action = data.get('action')
if event == 'pull_request':
if action == 'comment':
pull_request_comment_schema(data)
elif action in ('labeled', 'unlabeled',):
pull_request_labeled_schema(data)
elif action == 'status':
pull_request_status_schema(data)
else:
pull_request_default_schema(data)
elif event == 'pull_request_review':
pull_request_review_schema(data)
elif event == 'push':
push_schema(data)
elif event == 'check_run':
check_run_schema(data)
return validate
def getSchema():
old_schema = vs.Schema({
vs.Required('event'):
scalar_or_list(vs.Any('pull_request',
'pull_request_review',
'push',
'check_run')),
'action': scalar_or_list(str),
'branch': scalar_or_list(vs.Any(ZUUL_REGEX, str)),
'ref': scalar_or_list(vs.Any(ZUUL_REGEX, str)),
'comment': scalar_or_list(vs.Any(ZUUL_REGEX, str)),
'label': scalar_or_list(str),
'unlabel': scalar_or_list(str),
'state': scalar_or_list(str),
'require-status': scalar_or_list(str),
'require': githubsource.getRequireSchema(),
'reject': githubsource.getRejectSchema(),
'status': scalar_or_list(str),
'check': scalar_or_list(str),
})
return old_schema