Skip to content

Commit 7881d30

Browse files
authored
Integration Test workflow accept SDK artifact; and Packaging workflow can trigger Integration Test afterwards (#94)
1 parent 56922f9 commit 7881d30

File tree

4 files changed

+451
-8
lines changed

4 files changed

+451
-8
lines changed

.github/workflows/integration_tests.yml

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ on:
3232
default: '0'
3333
required: true
3434
test_packaged_sdk:
35-
description: 'Optional: Packaging run # to build against?'
35+
description: 'Optional: Packaging run # to build against? (sdk_url will be ignored.)'
3636
test_pull_request:
3737
description: 'Optional: Pull request # to build and test? (With optional commit hash, separated by a colon. Specify the FULL hash.)'
3838

@@ -171,18 +171,29 @@ jobs:
171171
shell: bash
172172
run: |
173173
python scripts/gha/restore_secrets.py --passphrase "${{ secrets.TEST_SECRET }}"
174+
- name: Fetch prebuilt packaged SDK from previous run
175+
uses: dawidd6/action-download-artifact@v2
176+
if: ${{ github.event.inputs.test_packaged_sdk != '' }}
177+
with:
178+
name: 'firebase_unity_sdk'
179+
workflow: 'packaging.yml'
180+
run_id: ${{ github.event.inputs.test_packaged_sdk }}
181+
path: firebase_unity_sdk
174182
- name: Build integration tests
175183
timeout-minutes: 220
176184
shell: bash
177185
run: |
178-
# TODO: Download unity SDK first
179-
if [[ -z "${{ github.event.inputs.sdk_url }}" ]];then
180-
sdk_url="https://dl.google.com/firebase/sdk/unity/firebase_unity_sdk_8.5.0.zip"
186+
if [[ -n "${{ github.event.inputs.test_packaged_sdk }}" ]]; then
187+
mv -v firebase_unity_sdk ~/Downloads/
181188
else
182-
sdk_url=${{ github.event.inputs.sdk_url }}
189+
if [[ -z "${{ github.event.inputs.sdk_url }}" ]];then
190+
sdk_url="https://dl.google.com/firebase/sdk/unity/firebase_unity_sdk_8.5.0.zip"
191+
else
192+
sdk_url=${{ github.event.inputs.sdk_url }}
193+
fi
194+
curl ${sdk_url} -o ~/Downloads/firebase_unity_sdk.zip
195+
unzip -q ~/Downloads/firebase_unity_sdk.zip -d ~/Downloads/
183196
fi
184-
curl ${sdk_url} -o ~/Downloads/firebase_unity_sdk.zip
185-
unzip -q ~/Downloads/firebase_unity_sdk.zip -d ~/Downloads/
186197
python scripts/gha/build_testapps.py \
187198
--t ${{ needs.check_and_prepare.outputs.apis }} \
188199
--u $( python scripts/gha/print_matrix_configuration.py -u ${{matrix.unity_version}} -k version ) \

.github/workflows/package.yml

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ on:
88
description: 'CSV of VMs to run on'
99
default: 'ubuntu-latest'
1010
required: true
11+
skipIntegrationTests:
12+
description: 'skip integration tests?'
13+
default: 0
1114

1215
jobs:
1316
package_sdks:
@@ -92,5 +95,41 @@ jobs:
9295
with:
9396
name: firebase_unity_sdk_tgz
9497
path: output_tgz
95-
98+
99+
trigger_integration_tests:
100+
# Trigger the integration_tests workflow.
101+
needs: [package_sdks]
102+
if: (github.event.inputs.skipIntegrationTests == 0 || github.event.inputs.skipIntegrationTests == '') && !cancelled()
103+
runs-on: ubuntu-latest
104+
steps:
105+
- name: Checkout repo
106+
uses: actions/[email protected]
107+
108+
- name: Setup python
109+
uses: actions/setup-python@v2
110+
with:
111+
python-version: 3.7
112+
- name: Generate token for GitHub API
113+
# This step is necessary because the existing GITHUB_TOKEN cannot be used inside one workflow to trigger another.
114+
#
115+
# Instead, generate a new token here, using our GitHub App's private key and App ID (saved as Secrets).
116+
#
117+
# This method is preferred over the "personal access token" solution, as the GitHub App's scope is limited to just
118+
# the firebase-cpp-sdk repository.
119+
uses: tibdex/github-app-token@v1
120+
id: generate-token
121+
with:
122+
app_id: ${{ secrets.WORKFLOW_TRIGGER_APP_ID }}
123+
private_key: ${{ secrets.WORKFLOW_TRIGGER_APP_PRIVATE_KEY }}
124+
- name: Use GitHub API to start workflow
125+
shell: bash
126+
run: |
127+
pip install -r scripts/gha/requirements.txt
128+
if [[ "${{ github.event_name }}" == "schedule" ]]; then
129+
# reuse flag --test_pull_request=nightly-packaging to generate report
130+
generate_report=(-p test_pull_request nightly-packaging)
131+
fi
132+
set -e
133+
python scripts/gha/trigger_workflow.py -t ${{ steps.generate-token.outputs.token }} -w integration_tests.yml -p test_packaged_sdk ${{ github.run_id }} -s 10 -A -v
134+
96135

scripts/gha/github.py

Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
# Copyright 2021 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""A utility for GitHub REST API.
16+
17+
This script handles GitHub Issue, Pull Request, Comment, Label and Artifact
18+
19+
"""
20+
21+
import requests
22+
import json
23+
import shutil
24+
import re
25+
26+
from absl import logging
27+
from requests.adapters import HTTPAdapter
28+
from requests.packages.urllib3.util.retry import Retry
29+
30+
RETRIES = 3
31+
BACKOFF = 5
32+
RETRY_STATUS = (403, 500, 502, 504)
33+
TIMEOUT = 5
34+
35+
OWNER = 'firebase'
36+
REPO = 'firebase-unity-sdk'
37+
38+
BASE_URL = 'https://api.github.com'
39+
GITHUB_API_URL = '%s/repos/%s/%s' % (BASE_URL, OWNER, REPO)
40+
logging.set_verbosity(logging.INFO)
41+
42+
43+
def set_repo_url(repo):
44+
match = re.match(r'https://github\.com/([^/]+)/([^/.]+)', repo)
45+
if not match:
46+
logging.info('Error, only pattern https://github.com/\{repo_owner\}/\{repo_name\} are allowed.')
47+
return False
48+
49+
(repo_owner, repo_name) = match.groups()
50+
global OWNER, REPO, GITHUB_API_URL
51+
OWNER = repo_owner
52+
REPO = repo_name
53+
GITHUB_API_URL = '%s/repos/%s/%s' % (BASE_URL, OWNER, REPO)
54+
return True
55+
56+
57+
def requests_retry_session(retries=RETRIES,
58+
backoff_factor=BACKOFF,
59+
status_forcelist=RETRY_STATUS):
60+
session = requests.Session()
61+
retry = Retry(total=retries,
62+
read=retries,
63+
connect=retries,
64+
backoff_factor=backoff_factor,
65+
status_forcelist=status_forcelist)
66+
adapter = HTTPAdapter(max_retries=retry)
67+
session.mount('http://', adapter)
68+
session.mount('https://', adapter)
69+
return session
70+
71+
def create_issue(token, title, label, body):
72+
"""Create an issue: https://docs.github.com/en/rest/reference/issues#create-an-issue"""
73+
url = f'{GITHUB_API_URL}/issues'
74+
headers = {'Accept': 'application/vnd.github.v3+json', 'Authorization': f'token {token}'}
75+
data = {'title': title, 'labels': [label], 'body': body}
76+
with requests.post(url, headers=headers, data=json.dumps(data), timeout=TIMEOUT) as response:
77+
logging.info("create_issue: %s response: %s", url, response)
78+
return response.json()
79+
80+
81+
def get_issue_body(token, issue_number):
82+
"""https://docs.github.com/en/rest/reference/issues#get-an-issue-comment"""
83+
url = f'{GITHUB_API_URL}/issues/{issue_number}'
84+
headers = {'Accept': 'application/vnd.github.v3+json', 'Authorization': f'token {token}'}
85+
with requests_retry_session().get(url, headers=headers, timeout=TIMEOUT) as response:
86+
logging.info("get_issue_body: %s response: %s", url, response)
87+
return response.json()["body"]
88+
89+
90+
def update_issue(token, issue_number, data):
91+
"""Update an issue: https://docs.github.com/en/rest/reference/issues#update-an-issue"""
92+
url = f'{GITHUB_API_URL}/issues/{issue_number}'
93+
headers = {'Accept': 'application/vnd.github.v3+json', 'Authorization': f'token {token}'}
94+
with requests_retry_session().patch(url, headers=headers, data=json.dumps(data), timeout=TIMEOUT) as response:
95+
logging.info("update_issue: %s response: %s", url, response)
96+
97+
98+
def open_issue(token, issue_number):
99+
update_issue(token, issue_number, data={'state': 'open'})
100+
101+
102+
def close_issue(token, issue_number):
103+
update_issue(token, issue_number, data={'state': 'closed'})
104+
105+
106+
def update_issue_comment(token, issue_number, comment):
107+
update_issue(token, issue_number, data={'body': comment})
108+
109+
110+
def search_issues_by_label(label):
111+
"""https://docs.github.com/en/rest/reference/search#search-issues-and-pull-requests"""
112+
url = f'{BASE_URL}/search/issues?q=repo:{OWNER}/{REPO}+label:"{label}"+is:issue'
113+
headers = {'Accept': 'application/vnd.github.v3+json'}
114+
with requests_retry_session().get(url, headers=headers, timeout=TIMEOUT) as response:
115+
logging.info("search_issues_by_label: %s response: %s", url, response)
116+
return response.json()["items"]
117+
118+
119+
def list_comments(token, issue_number):
120+
"""https://docs.github.com/en/rest/reference/issues#list-issue-comments"""
121+
url = f'{GITHUB_API_URL}/issues/{issue_number}/comments'
122+
headers = {'Accept': 'application/vnd.github.v3+json', 'Authorization': f'token {token}'}
123+
with requests_retry_session().get(url, headers=headers, timeout=TIMEOUT) as response:
124+
logging.info("list_comments: %s response: %s", url, response)
125+
return response.json()
126+
127+
128+
def add_comment(token, issue_number, comment):
129+
"""https://docs.github.com/en/rest/reference/issues#create-an-issue-comment"""
130+
url = f'{GITHUB_API_URL}/issues/{issue_number}/comments'
131+
headers = {'Accept': 'application/vnd.github.v3+json', 'Authorization': f'token {token}'}
132+
data = {'body': comment}
133+
with requests.post(url, headers=headers, data=json.dumps(data), timeout=TIMEOUT) as response:
134+
logging.info("add_comment: %s response: %s", url, response)
135+
136+
137+
def update_comment(token, comment_id, comment):
138+
"""https://docs.github.com/en/rest/reference/issues#update-an-issue-comment"""
139+
url = f'{GITHUB_API_URL}/issues/comments/{comment_id}'
140+
headers = {'Accept': 'application/vnd.github.v3+json', 'Authorization': f'token {token}'}
141+
data = {'body': comment}
142+
with requests_retry_session().patch(url, headers=headers, data=json.dumps(data), timeout=TIMEOUT) as response:
143+
logging.info("update_comment: %s response: %s", url, response)
144+
145+
146+
def delete_comment(token, comment_id):
147+
"""https://docs.github.com/en/rest/reference/issues#delete-an-issue-comment"""
148+
url = f'{GITHUB_API_URL}/issues/comments/{comment_id}'
149+
headers = {'Accept': 'application/vnd.github.v3+json', 'Authorization': f'token {token}'}
150+
with requests.delete(url, headers=headers, timeout=TIMEOUT) as response:
151+
logging.info("delete_comment: %s response: %s", url, response)
152+
153+
154+
def add_label(token, issue_number, label):
155+
"""https://docs.github.com/en/rest/reference/issues#add-labels-to-an-issue"""
156+
url = f'{GITHUB_API_URL}/issues/{issue_number}/labels'
157+
headers={}
158+
headers = {'Accept': 'application/vnd.github.v3+json', 'Authorization': f'token {token}'}
159+
data = [label]
160+
with requests.post(url, headers=headers, data=json.dumps(data), timeout=TIMEOUT) as response:
161+
logging.info("add_label: %s response: %s", url, response)
162+
163+
164+
def delete_label(token, issue_number, label):
165+
"""https://docs.github.com/en/rest/reference/issues#delete-a-label"""
166+
url = f'{GITHUB_API_URL}/issues/{issue_number}/labels/{label}'
167+
headers = {'Accept': 'application/vnd.github.v3+json', 'Authorization': f'token {token}'}
168+
with requests.delete(url, headers=headers, timeout=TIMEOUT) as response:
169+
logging.info("delete_label: %s response: %s", url, response)
170+
171+
172+
def list_artifacts(token, run_id):
173+
"""https://docs.github.com/en/rest/reference/actions#list-workflow-run-artifacts"""
174+
url = f'{GITHUB_API_URL}/actions/runs/{run_id}/artifacts'
175+
headers = {'Accept': 'application/vnd.github.v3+json', 'Authorization': f'token {token}'}
176+
with requests_retry_session().get(url, headers=headers, timeout=TIMEOUT) as response:
177+
logging.info("list_artifacts: %s response: %s", url, response)
178+
return response.json()["artifacts"]
179+
180+
181+
def download_artifact(token, artifact_id, output_path):
182+
"""https://docs.github.com/en/rest/reference/actions#download-an-artifact"""
183+
url = f'{GITHUB_API_URL}/actions/artifacts/{artifact_id}/zip'
184+
headers = {'Accept': 'application/vnd.github.v3+json', 'Authorization': f'token {token}'}
185+
with requests.get(url, headers=headers, stream=True, timeout=TIMEOUT) as response:
186+
logging.info("download_artifact: %s response: %s", url, response)
187+
with open(output_path, 'wb') as file:
188+
shutil.copyfileobj(response.raw, file)
189+
190+
191+
def dismiss_review(token, pull_number, review_id, message):
192+
"""https://docs.github.com/en/rest/reference/pulls#dismiss-a-review-for-a-pull-request"""
193+
url = f'{GITHUB_API_URL}/pulls/{pull_number}/reviews/{review_id}/dismissals'
194+
headers = {'Accept': 'application/vnd.github.v3+json', 'Authorization': f'token {token}'}
195+
data = {'message': message}
196+
with requests_retry_session().put(url, headers=headers, data=json.dumps(data),
197+
stream=True, timeout=TIMEOUT) as response:
198+
logging.info("dismiss_review: %s response: %s", url, response)
199+
return response.json()
200+
201+
202+
def get_reviews(token, pull_number):
203+
"""https://docs.github.com/en/rest/reference/pulls#list-reviews-for-a-pull-request"""
204+
url = f'{GITHUB_API_URL}/pulls/{pull_number}/reviews'
205+
headers = {'Accept': 'application/vnd.github.v3+json', 'Authorization': f'token {token}'}
206+
page = 1
207+
per_page = 100
208+
results = []
209+
keep_going = True
210+
while keep_going:
211+
params = {'per_page': per_page, 'page': page}
212+
page = page + 1
213+
keep_going = False
214+
with requests_retry_session().get(url, headers=headers, params=params,
215+
stream=True, timeout=TIMEOUT) as response:
216+
logging.info("get_reviews: %s response: %s", url, response)
217+
results = results + response.json()
218+
# If exactly per_page results were retrieved, read the next page.
219+
keep_going = (len(response.json()) == per_page)
220+
return results
221+
222+
223+
def create_workflow_dispatch(token, workflow_id, ref, inputs):
224+
"""https://docs.github.com/en/rest/reference/actions#create-a-workflow-dispatch-event"""
225+
url = f'{GITHUB_API_URL}/actions/workflows/{workflow_id}/dispatches'
226+
headers = {'Accept': 'application/vnd.github.v3+json', 'Authorization': f'token {token}'}
227+
data = {'ref': ref, 'inputs': inputs}
228+
with requests.post(url, headers=headers, data=json.dumps(data),
229+
stream=True, timeout=TIMEOUT) as response:
230+
logging.info("create_workflow_dispatch: %s response: %s", url, response)
231+
# Response Status: 204 No Content
232+
return True if response.status_code == 204 else False
233+
234+
235+
def list_workflows(token, workflow_id, branch):
236+
"""https://docs.github.com/en/rest/reference/actions#list-workflow-runs-for-a-repository"""
237+
url = f'{GITHUB_API_URL}/actions/workflows/{workflow_id}/runs'
238+
headers = {'Accept': 'application/vnd.github.v3+json', 'Authorization': f'token {token}'}
239+
data = {'event': 'workflow_dispatch', 'branch': branch}
240+
with requests.get(url, headers=headers, data=json.dumps(data),
241+
stream=True, timeout=TIMEOUT) as response:
242+
logging.info("list_workflows: %s response: %s", url, response)
243+
return response.json()
244+
245+
246+
def create_pull_request(token, head, base, title, body, maintainer_can_modify):
247+
"""https://docs.github.com/en/rest/reference/pulls#create-a-pull-request"""
248+
url = f'{GITHUB_API_URL}/pulls'
249+
headers = {'Accept': 'application/vnd.github.v3+json', 'Authorization': f'token {token}'}
250+
data = {'head': head, 'base': base, 'title': title, 'body': body,
251+
'maintainer_can_modify': maintainer_can_modify}
252+
with requests.post(url, headers=headers, data=json.dumps(data),
253+
stream=True, timeout=TIMEOUT) as response:
254+
logging.info("create_pull_request: %s response: %s", head, response)
255+
return True if response.status_code == 201 else False
256+
257+
258+
def list_pull_requests(token, state, head, base):
259+
"""https://docs.github.com/en/rest/reference/pulls#list-pull-requests"""
260+
url = f'{GITHUB_API_URL}/pulls'
261+
headers = {'Accept': 'application/vnd.github.v3+json', 'Authorization': f'token {token}'}
262+
page = 1
263+
per_page = 100
264+
results = []
265+
keep_going = True
266+
while keep_going:
267+
params = {'per_page': per_page, 'page': page}
268+
if state: params.update({'state': state})
269+
if head: params.update({'head': head})
270+
if base: params.update({'base': base})
271+
page = page + 1
272+
keep_going = False
273+
with requests_retry_session().get(url, headers=headers, params=params,
274+
stream=True, timeout=TIMEOUT) as response:
275+
logging.info("get_reviews: %s response: %s", url, response)
276+
results = results + response.json()
277+
# If exactly per_page results were retrieved, read the next page.
278+
keep_going = (len(response.json()) == per_page)
279+
return results
280+

0 commit comments

Comments
 (0)