summaryrefslogtreecommitdiff
path: root/zuul/source/__init__.py
blob: c2487af88f684fae3d3a4e9d1a597738767c355f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# Copyright 2014 Rackspace Australia
#
# 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 abc
import time

from zuul import model


class BaseSource(object, metaclass=abc.ABCMeta):
    """Base class for sources.

    A source class gives methods for fetching and updating changes. Each
    pipeline must have (only) one source. It is the canonical provider of the
    change to be tested.

    Defines the exact public methods that must be supplied."""

    def __init__(self, driver, connection, canonical_hostname, config=None):
        self.driver = driver
        self.connection = connection
        self.canonical_hostname = canonical_hostname
        self.config = config or {}

    @abc.abstractmethod
    def getRefSha(self, project, ref):
        """Return a sha for a given project ref."""

    @abc.abstractmethod
    def isMerged(self, change, head=None):
        """Determine if change is merged.

        If head is provided the change is checked if it is at head."""

    @abc.abstractmethod
    def canMerge(self, change, allow_needs, event=None, allow_refresh=False):
        """Determine if change can merge.

        change: The change to check for mergeability
        allow_needs: The statuses/votes that are allowed to be missing on a
                     change, typically the votes the pipeline would set itself
                     before attempting to merge
        event: event information for log annotation
        allow_refresh: Allow refreshing of cached volatile data that cannot be
                       reliably kept up to date using events.
        """

    def postConfig(self):
        """Called after configuration has been processed."""

    @abc.abstractmethod
    def getChangeKey(self, event):
        """Get a ChangeKey from a ChangeManagementEvent or TriggerEvent"""

    @abc.abstractmethod
    def getChange(self, change_key, refresh=False, event=None):
        """Get the change represented by a change_key

        This method is called very frequently, and should generally
        return quickly.  The connection is expected to cache change
        objects and automatically update them as related events are
        received.

        The event is optional, and if present may be used to annotate
        log entries and supply additional information about the change
        if a refresh is necessary.

        If the change key does not correspond to this source, return
        None.

        """

    @abc.abstractmethod
    def getChangeByURL(self, url, event):
        """Get the change corresponding to the supplied URL.

        The URL may may not correspond to this source; if it doesn't,
        or there is no change at that URL, return None.

        """

    def getChangeByURLWithRetry(self, url, event):
        for x in range(3):
            # We retry this as we are unlikely to be able to report back
            # failures if our source is broken, but if we can get the
            # info on subsequent requests we can continue to do the
            # requested job work.
            try:
                return self.getChangeByURL(url, event)
            except Exception:
                # Note that if the change isn't found dep is None.
                # We do not raise in that case and do not need to handle it
                # here.
                retry = x != 2 and " Retrying" or ""
                self.log.exception("Failed to retrieve dependency %s.%s",
                                   url, retry)
                if retry:
                    time.sleep(1)
                else:
                    raise

    @abc.abstractmethod
    def getChangesDependingOn(self, change, projects, tenant):
        """Return changes which depend on changes at the supplied URIs.

        Search this source for changes which depend on the supplied
        change.  Generally the Change.uris attribute should be used to
        perform the search, as it contains a list of URLs without the
        scheme which represent a single change

        If the projects argument is None, search across all known
        projects.  If it is supplied, the search may optionally be
        restricted to only those projects.

        The tenant argument can be used by the source to limit the
        search scope.
        """

    def getChangesByTopic(self, topic):
        """Return changes in the same topic.

        This should return changes under the same topic, as well as
        changes under the same topic of any git-dependent changes,
        recursively.

        This is only implemented by the Gerrit driver, however if
        other systems have a similar "topic" functionality, it could
        be added to other drivers.
        """

        return []

    @abc.abstractmethod
    def getProjectOpenChanges(self, project):
        """Get the open changes for a project."""

    def getProjectDefaultMergeMode(self, project):
        """Return the default merge mode for this project.

        If users do not specify the merge mode for a project, this
        mode will be used.  It may be a driver-specific default,
        or the driver may use data from the remote system to provide
        a project-specific default.
        """
        return 'merge'

    @abc.abstractmethod
    def getGitUrl(self, project):
        """Get the git url for a project."""

    def getRetryTimeout(self, project):
        """Get the retry timeout for a project in seconds.

        This is used by the mergers to potentially increase the number
        of git fetch retries before giving up.  Return None to use the
        default.
        """
        return None

    @abc.abstractmethod
    def getProject(self, name):
        """Get a project."""

    @abc.abstractmethod
    def getProjectBranches(self, project, tenant, min_ltime=-1):
        """Get branches for a project

        This method is called very frequently, and should generally
        return quickly.  The connection is expected to cache branch
        lists for all projects queried, and further, to automatically
        clear or update that cache when it observes branch creation or
        deletion events.

        """

    def getProjectMergeModes(self, project, tenant, min_ltime=-1):
        """Get supported merge modes for a project

        This method is called very frequently, and should generally
        return quickly.  The connection is expected to cache merge
        modes for all projects queried.

        The default implementation indicates that all merge modes are
        supported.

        """

        return model.ALL_MERGE_MODES

    @abc.abstractmethod
    def getProjectBranchCacheLtime(self):
        """Return the current ltime of the project branch cache."""

    @abc.abstractmethod
    def getRequireFilters(self, config):
        """Return a list of ChangeFilters for the scheduler to match against.
        """

    @abc.abstractmethod
    def getRejectFilters(self, config):
        """Return a list of ChangeFilters for the scheduler to match against.
        """

    def setChangeAttributes(self, change, **attrs):
        """"Set the provided attributes on the given change.

        This method must be used when modifying attributes of a change
        outside the driver context. The driver needs to make sure that
        the change is also reflected in the cache in Zookeeper.
        """
        # TODO (swestphahl): Remove this workaround after all drivers
        # have been converted to use a Zookeeper backed changed cache.
        for name, value in attrs.items():
            setattr(change, name, value)