Skip to content

Commit cea0777

Browse files
committedJun 22, 2017
Check the status of landing jobs (bug 1375042)
Renamed Transplant to Landing in API, models, db schema. Current API: - /landings POST - land the revision GET - get a list by revision and/or status - /landings/{id} GET - get a specific landing
1 parent 4b7a931 commit cea0777

20 files changed

+446
-59
lines changed
 

‎.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ __pycache__
22
/*.egg-info/
33
/.cache/
44
/docker-compose.override.yml
5+
*.pyc

‎README.md

+9
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,12 @@ Server: Werkzeug/0.12.1 Python/3.5.3
5353

5454
Start a development server and expose its ports as documented above, and visit
5555
`http://localhost:8000/ui/` in your browser to view the API documentation.
56+
57+
## Testing
58+
59+
We're using `pytest` with `pytest-flask`. All tests are placed in `./tests/`
60+
To run the tests please call
61+
62+
```bash
63+
$ invoke test
64+
```

‎dev-requirements.txt

-1
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,3 @@ py==1.4.33 \
2727
--hash=sha256:1f9a981438f2acc20470b301a07a496375641f902320f70e31916fe3377385a9
2828
requests-mock==1.3.0 \
2929
--hash=sha256:23edd6f7926aa13b88bf79cb467632ba2dd5a253034e9f41563f60ed305620c7
30-

‎docker-compose.yml

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ services:
1818
- PHABRICATOR_URL=https://mozphab.dev.mozaws.net
1919
- PHABRICATOR_UNPRIVILEGED_API_KEY=api-123456789
2020
- TRANSPLANT_URL=https://stub.transplant.example.com
21+
- DATABASE_URL=sqlite:////sqlite.db
2122
py3-linter:
2223
build:
2324
context: ./

‎docker/Dockerfile-dev

+3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44

55
FROM python:3.5-alpine
66

7+
RUN apk --update --no-cache add \
8+
sqlite
79
ADD requirements.txt /requirements.txt
10+
ADD docker/db/schema.db /sqlite.db
811
RUN pip install --no-cache -r /requirements.txt
912
ADD dev-requirements.txt /dev-requirements.txt
1013
RUN pip install --no-cache -r /dev-requirements.txt

‎docker/Dockerfile-prod

+3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ FROM python:3.5-alpine
77
RUN addgroup -g 1001 app && \
88
adduser -D -u 1001 -G app -s /usr/sbin/nologin app
99

10+
RUN apk --update --no-cache add \
11+
sqlite > /dev/null 2>&1
12+
1013
COPY requirements.txt /requirements.txt
1114
RUN pip install --no-cache -r /requirements.txt
1215

‎docker/db/schema.db

12 KB
Binary file not shown.

‎landoapi/api/landings.py

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# This Source Code Form is subject to the terms of the Mozilla Public
2+
# License, v. 2.0. If a copy of the MPL was not distributed with this
3+
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
4+
"""
5+
Transplant API
6+
See the OpenAPI Specification for this API in the spec/swagger.yml file.
7+
"""
8+
from connexion import problem
9+
from flask import request
10+
from landoapi.models.landing import (
11+
Landing,
12+
LandingNotCreatedException,
13+
LandingNotFoundException,
14+
RevisionNotFoundException,
15+
)
16+
17+
18+
def land(data, api_key=None):
19+
""" API endpoint at /revisions/{id}/transplants to land revision. """
20+
# get revision_id from body
21+
revision_id = data['revision_id']
22+
try:
23+
landing = Landing.create(revision_id, api_key)
24+
except RevisionNotFoundException:
25+
# We could not find a matching revision.
26+
return problem(
27+
404,
28+
'Revision not found',
29+
'The requested revision does not exist',
30+
type='https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404'
31+
)
32+
except LandingNotCreatedException:
33+
# We could not find a matching revision.
34+
return problem(
35+
502,
36+
'Landing not created',
37+
'The requested revision does exist, but landing failed.'
38+
'Please retry your request at a later time.',
39+
type='https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/502'
40+
)
41+
42+
return {'id': landing.id}, 202
43+
44+
45+
def get_list(revision_id=None, status=None):
46+
""" API endpoint at /landings to return all Landing objects related to a
47+
Revision or of specific status.
48+
"""
49+
kwargs = {}
50+
if revision_id:
51+
kwargs['revision_id'] = revision_id
52+
53+
if status:
54+
kwargs['status'] = status
55+
56+
landings = Landing.query.filter_by(**kwargs).all()
57+
return list(map(lambda l: l.serialize(), landings)), 200
58+
59+
60+
def get(landing_id):
61+
""" API endpoint at /landings/{landing_id} to return stored Landing.
62+
"""
63+
try:
64+
landing = Landing.get(landing_id)
65+
except LandingNotFoundException:
66+
return problem(
67+
404,
68+
'Landing not found',
69+
'The requested Landing does not exist',
70+
type='https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404'
71+
)
72+
73+
return landing.serialize(), 200

‎landoapi/api/revisions.py

+4-30
Original file line numberDiff line numberDiff line change
@@ -7,28 +7,13 @@
77
"""
88
from connexion import problem
99
from landoapi.phabricator_client import PhabricatorClient
10-
from landoapi.transplant_client import TransplantClient
1110

1211

1312
def get(revision_id, api_key=None):
14-
""" API endpoint at /revisions/{id} to get revision data. """
15-
phab = PhabricatorClient(api_key)
16-
revision = phab.get_revision(id=revision_id)
13+
""" Gets revision from Phabricator.
1714
18-
if not revision:
19-
# We could not find a matching revision.
20-
return problem(
21-
404,
22-
'Revision not found',
23-
'The requested revision does not exist',
24-
type='https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404'
25-
)
26-
27-
return _format_revision(phab, revision, include_parents=True), 200
28-
29-
30-
def land(revision_id, api_key=None):
31-
""" API endpoint at /revisions/{id}/transplants to land revision. """
15+
Returns None or revision.
16+
"""
3217
phab = PhabricatorClient(api_key)
3318
revision = phab.get_revision(id=revision_id)
3419

@@ -41,18 +26,7 @@ def land(revision_id, api_key=None):
4126
type='https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404'
4227
)
4328

44-
revision = _format_revision(phab, revision, include_parents=True)
45-
46-
trans = TransplantClient()
47-
<<<<<<< HEAD
48-
id = trans.land('ldap_username@example.com', revision)
49-
=======
50-
id = trans.land(
51-
'ldap_username@example.com', revision['repo']['url'], 'patch',
52-
'destination', 'push_bookmark', 'http://pingback.url'
53-
)
54-
>>>>>>> Land revision to a stub Transplant API (bug 1372538)
55-
return {}, 202
29+
return _format_revision(phab, revision, include_parents=True), 200
5630

5731

5832
def _format_revision(

‎landoapi/app.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import connexion
88
from connexion.resolver import RestyResolver
99
from landoapi.dockerflow import dockerflow
10+
from landoapi.models.storage import db
1011

1112

1213
def create_app(version_path):
@@ -17,8 +18,13 @@ def create_app(version_path):
1718
# Get the Flask app being wrapped by the Connexion app.
1819
flask_app = app.app
1920
flask_app.config['VERSION_PATH'] = version_path
20-
flask_app.register_blueprint(dockerflow)
21+
flask_app.config.setdefault(
22+
'SQLALCHEMY_DATABASE_URI',
23+
os.environ.get('DATABASE_URL', 'sqlite:////sqlite.db')
24+
)
2125

26+
flask_app.register_blueprint(dockerflow)
27+
db.init_app(flask_app)
2228
return app
2329

2430

‎landoapi/models/__init__.py

Whitespace-only changes.

‎landoapi/models/landing.py

+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# This Source Code Form is subject to the terms of the Mozilla Public
2+
# License, v. 2.0. If a copy of the MPL was not distributed with this
3+
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
4+
from landoapi.models.storage import db
5+
from landoapi.phabricator_client import PhabricatorClient
6+
from landoapi.transplant_client import TransplantClient
7+
8+
TRANSPLANT_JOB_STARTED = 'started'
9+
TRANSPLANT_JOB_FINISHED = 'finished'
10+
11+
12+
def _get_revision(revision_id, api_key=None):
13+
""" Gets revision from Phabricator.
14+
15+
Returns None or revision.
16+
"""
17+
phab = PhabricatorClient(api_key)
18+
revision = phab.get_revision(id=revision_id)
19+
if not revision:
20+
return None
21+
22+
raw_repo = phab.get_repo(revision['repositoryPHID'])
23+
return {
24+
'id': int(revision['id']),
25+
'phid': revision['phid'],
26+
'repo_url': raw_repo['uri'],
27+
'title': revision['title'],
28+
'url': revision['uri'],
29+
'date_created': int(revision['dateCreated']),
30+
'date_modified': int(revision['dateModified']),
31+
'status': int(revision['status']),
32+
'status_name': revision['statusName'],
33+
'summary': revision['summary'],
34+
'test_plan': revision['testPlan'],
35+
}
36+
37+
38+
class Landing(db.Model):
39+
__tablename__ = "landings"
40+
41+
id = db.Column(db.Integer, primary_key=True)
42+
request_id = db.Column(db.Integer, unique=True)
43+
revision_id = db.Column(db.String(30))
44+
status = db.Column(db.Integer)
45+
46+
def __init__(
47+
self, request_id=None, revision_id=None, status=TRANSPLANT_JOB_STARTED
48+
):
49+
self.request_id = request_id
50+
self.revision_id = revision_id
51+
self.status = status
52+
53+
@classmethod
54+
def create(cls, revision_id, phabricator_api_key=None, save=True):
55+
""" Land revision and create a Transplant item in storage. """
56+
revision = _get_revision(revision_id, phabricator_api_key)
57+
if not revision:
58+
raise RevisionNotFoundException(revision_id)
59+
60+
trans = TransplantClient()
61+
request_id = trans.land(
62+
'ldap_username@example.com', revision['repo_url']
63+
)
64+
if not request_id:
65+
raise LandingNotCreatedException
66+
67+
landing = cls(
68+
revision_id=revision_id,
69+
request_id=request_id,
70+
status=TRANSPLANT_JOB_STARTED
71+
)
72+
if save:
73+
landing.save(create=True)
74+
75+
return landing
76+
77+
@classmethod
78+
def get(cls, landing_id):
79+
""" Get Landing object from storage. """
80+
landing = cls.query.get(landing_id)
81+
if not landing:
82+
raise LandingNotFoundException()
83+
84+
return landing
85+
86+
def save(self, create=False):
87+
""" Save objects in storage. """
88+
if create:
89+
db.session.add(self)
90+
91+
db.session.commit()
92+
return self
93+
94+
def __repr__(self):
95+
return '<Landing: %s>' % self.id
96+
97+
def serialize(self):
98+
""" Serialize to JSON compatible dictionary. """
99+
return {
100+
'id': self.id,
101+
'revision_id': self.revision_id,
102+
'request_id': self.request_id,
103+
'status': self.status
104+
}
105+
106+
107+
class LandingNotCreatedException(Exception):
108+
""" Transplant service failed to land a revision. """
109+
pass
110+
111+
112+
class LandingNotFoundException(Exception):
113+
""" No specific Landing was found in database. """
114+
pass
115+
116+
117+
class RevisionNotFoundException(Exception):
118+
""" Phabricator returned 404 for a given revision id. """
119+
120+
def __init__(self, revision_id):
121+
super().__init__()
122+
self.revision_id = revision_id

‎landoapi/models/storage.py

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# This Source Code Form is subject to the terms of the Mozilla Public
2+
# License, v. 2.0. If a copy of the MPL was not distributed with this
3+
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
4+
from flask_sqlalchemy import SQLAlchemy
5+
6+
db = SQLAlchemy()

0 commit comments

Comments
 (0)
Failed to load comments.