1 | import os
|
2 | import sys
|
3 | import json
|
4 | import time
|
5 | import logging
|
6 |
|
7 | try:
|
8 | import urllib.request as urllib2
|
9 | except ImportError:
|
10 | import urllib2
|
11 |
|
12 | log = logging.getLogger("travis.leader")
|
13 | log.addHandler(logging.StreamHandler())
|
14 | log.setLevel(logging.INFO)
|
15 |
|
16 | TRAVIS_JOB_NUMBER = 'TRAVIS_JOB_NUMBER'
|
17 | TRAVIS_BUILD_ID = 'TRAVIS_BUILD_ID'
|
18 | POLLING_INTERVAL = 'LEADER_POLLING_INTERVAL'
|
19 | GITHUB_TOKEN = 'GITHUB_TOKEN'
|
20 |
|
21 |
|
22 |
|
23 | travis_entry = sys.argv[1] if len(sys.argv) > 1 else 'https://api.travis-ci.org'
|
24 |
|
25 | build_id = os.getenv(TRAVIS_BUILD_ID)
|
26 | polling_interval = int(os.getenv(POLLING_INTERVAL, '5'))
|
27 | gh_token = os.getenv(GITHUB_TOKEN)
|
28 |
|
29 |
|
30 | is_leader = lambda job_number: job_number.endswith('.1')
|
31 |
|
32 | job_number = os.getenv(TRAVIS_JOB_NUMBER)
|
33 |
|
34 | if not job_number:
|
35 |
|
36 | log.fatal("Don't use defining leader for build without matrix")
|
37 | exit(1)
|
38 | elif is_leader(job_number):
|
39 | log.info("This is a leader")
|
40 | else:
|
41 |
|
42 | with open(".to_export_back", "w") as export_var:
|
43 | export_var.write("BUILD_MINION=YES")
|
44 | log.info("This is a minion")
|
45 | exit(0)
|
46 |
|
47 |
|
48 | class MatrixElement(object):
|
49 | def __init__(self, json_raw):
|
50 | self.is_finished = json_raw['finished_at'] is not None
|
51 | self.is_succeeded = json_raw['result'] == 0
|
52 | self.number = json_raw['number']
|
53 | self.is_leader = is_leader(self.number)
|
54 |
|
55 |
|
56 | def matrix_snapshot(travis_token):
|
57 | """
|
58 | :return: Matrix List
|
59 | """
|
60 | headers = {'content-type': 'application/json', 'Authorization': 'token {}'.format(travis_token)}
|
61 | req = urllib2.Request("{0}/builds/{1}".format(travis_entry, build_id), headers=headers)
|
62 | response = urllib2.urlopen(req).read()
|
63 | raw_json = json.loads(response)
|
64 | matrix_without_leader = [MatrixElement(job) for job in raw_json["matrix"] if not is_leader(job['number'])]
|
65 | return matrix_without_leader
|
66 |
|
67 |
|
68 | def wait_others_to_finish(travis_token):
|
69 | def others_finished():
|
70 | """
|
71 | Dumps others to finish
|
72 | Leader cannot finish, it is working now
|
73 | :return: tuple(True or False, List of not finished jobs)
|
74 | """
|
75 | snapshot = matrix_snapshot(travis_token)
|
76 | finished = [job.is_finished for job in snapshot if not job.is_leader]
|
77 | return reduce(lambda a, b: a and b, finished), [job.number for job in snapshot if
|
78 | not job.is_leader and not job.is_finished]
|
79 |
|
80 | while True:
|
81 | finished, waiting_list = others_finished()
|
82 | if finished:
|
83 | break
|
84 | log.info("Leader waits for minions {0}...".format(waiting_list))
|
85 | time.sleep(polling_interval)
|
86 |
|
87 |
|
88 | def get_token():
|
89 | assert gh_token, 'GITHUB_TOKEN is not set'
|
90 | data = {"github_token": gh_token}
|
91 | headers = {'content-type': 'application/json'}
|
92 |
|
93 | req = urllib2.Request("{0}/auth/github".format(travis_entry), json.dumps(data), headers)
|
94 | response = urllib2.urlopen(req).read()
|
95 | travis_token = json.loads(response).get('access_token')
|
96 |
|
97 | return travis_token
|
98 |
|
99 |
|
100 | try:
|
101 | token = get_token()
|
102 | wait_others_to_finish(token)
|
103 |
|
104 | final_snapshot = matrix_snapshot(token)
|
105 | log.info("Final Results: {0}".format([(e.number, e.is_succeeded) for e in final_snapshot]))
|
106 |
|
107 | BUILD_AGGREGATE_STATUS = 'BUILD_AGGREGATE_STATUS'
|
108 | others_snapshot = [el for el in final_snapshot if not el.is_leader]
|
109 | if reduce(lambda a, b: a and b, [e.is_succeeded for e in others_snapshot]):
|
110 | os.environ[BUILD_AGGREGATE_STATUS] = "others_succeeded"
|
111 | elif reduce(lambda a, b: a and b, [not e.is_succeeded for e in others_snapshot]):
|
112 | log.error("Others Failed")
|
113 | os.environ[BUILD_AGGREGATE_STATUS] = "others_failed"
|
114 | else:
|
115 | log.warn("Others Unknown")
|
116 | os.environ[BUILD_AGGREGATE_STATUS] = "unknown"
|
117 |
|
118 | with open(".to_export_back", "w") as export_var:
|
119 | export_var.write("BUILD_LEADER=YES {0}={1}".format(BUILD_AGGREGATE_STATUS, os.environ[BUILD_AGGREGATE_STATUS]))
|
120 |
|
121 | except Exception as e:
|
122 | log.fatal(e)
|