forked from apache/cassandra-java-driver
-
Notifications
You must be signed in to change notification settings - Fork 38
/
version_fetch.py
212 lines (174 loc) · 7.33 KB
/
version_fetch.py
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
#!/usr/bin/python3
"""
This Python script allows you to list the
latest version numbers of Scylla and Cassandra.
You can specify whether you want the
versions of Scylla OSS or Scylla Enterprise,
either N latest stable X.Y.latest or
all non-obsolete RCs. You can also fetch
the latest version of Cassandra 3.
How are those versions fetched? We use Docker Hub
tags API.
"""
import requests
import argparse
import re
import json
import sys
DOCKER_HUB_TAGS_ENDPOINT = 'https://hub.docker.com/v2/namespaces/%s/repositories/%s/tags?page_size=1000'
DOCKER_HUB_SCYLLA_NAMESPACE = 'scylladb'
SCYLLA_OSS = (DOCKER_HUB_SCYLLA_NAMESPACE, 'scylla')
SCYLLA_ENTERPRISE = (DOCKER_HUB_SCYLLA_NAMESPACE, 'scylla-enterprise')
CASSANDRA_ENDPOINT = 'https://dlcdn.apache.org/cassandra/'
CASSANDRA_REGEX = re.compile(r'a href="([0-9])\.(\d+)\.(\d+)/"')
VERSION_DEFINITION_RE = re.compile(
r'((?:(scylla-oss-stable):(\d+))|(?:(scylla-enterprise-stable)(:\d+)?)|(?:(cassandra3-stable)(:\d+)?)|(?:(cassandra4-stable)(:\d+)?)|(?:(scylla-oss-rc)(:\d+)?)|(?:(scylla-enterprise-rc)(:\d+)?))')
ONLY_DIGITS_RE = re.compile("^[0-9]+")
class Version:
raw = ""
invalid = False
major = 0
minor = 0
minor_raw = ""
minor_rest = ""
patch = 0
patch_raw = ""
patch_rest = ""
is_dev = False
def __init__(self, ver):
self.raw = ver
chunks = ver.split(".", maxsplit=3)
if len(chunks) < 3:
self.invalid = True
return
self.major, self.minor_raw, self.patch_raw = chunks
try:
self.major = int(self.major)
except Exception:
self.invalid = True
return
digits = ONLY_DIGITS_RE.match(self.minor_raw)
if digits:
try:
self.minor = int(self.minor_raw[digits.regs[0][0]:digits.regs[0][1]])
self.minor_rest = self.minor_raw[digits.regs[0][1]:]
if self.minor_rest:
self.is_dev = True
except Exception:
self.invalid = True
else:
self.minor = 0
digits = ONLY_DIGITS_RE.match(self.patch_raw)
if digits:
try:
self.patch = int(self.patch_raw[digits.regs[0][0]:digits.regs[0][1]])
self.patch_rest = self.patch_raw[digits.regs[0][1]:]
if self.patch_rest:
self.is_dev = True
except Exception:
self.invalid = True
else:
self.patch = 0
def __str__(self):
return self.raw
def __repr__(self):
return self.raw
def __lt__(self, other):
if self.major != other.major:
return self.major < other.major
if self.minor != other.minor:
return self.minor < other.minor
if self.patch != other.patch:
return self.patch < other.patch
if self.is_dev == other.is_dev:
return self.patch_rest < other.patch_rest
return self.is_dev
def fetch_docker_hub_tags(namespace, repository):
tags = []
# Fetch all pages of tags for a given repository
current_page_endpoint = DOCKER_HUB_TAGS_ENDPOINT % (namespace, repository)
while True:
# Fetch a page
tags_data = requests.get(current_page_endpoint).json()
# Extract all tags from the response
tags.extend(map(lambda e: e['name'], tags_data['results']))
# Move to the next page if it's needed
if tags_data['next'] is not None:
current_page_endpoint = tags_data['next']
else:
break
result = []
for tag in tags:
try:
ver = Version(tag)
if not ver.patch_raw or ver.invalid:
continue
result.append(ver)
except Exception:
continue
return reversed(sorted(result))
rc_only = lambda x: x.is_dev
release_only = lambda x: not x.is_dev
scylla_oss_only = lambda x: x.major < 2000
scylla_enterprise_only = lambda x: x.major > 2000
cassandra_3_only = lambda x: x.major == 3
cassandra_4_only = lambda x: x.major == 4
def filter_versions(all_versions, count, *filters):
result = []
for ver in filter(lambda ver: all(map(lambda ft: ft(ver), filters)), all_versions):
for existing_ver in result:
if ver.major == existing_ver.major and ver.minor == existing_ver.minor:
break
else:
if len(result) >= count:
return result
result.append(ver)
return result
def fetch_all_cassandra_versions():
# Download folder listing for Cassandra download site
data = requests.get(CASSANDRA_ENDPOINT).text
# Parse only those version numbers which match '3.NUM.NUM'
# into tuple (3, NUM, NUM)
data = CASSANDRA_REGEX.finditer(data)
data = map(lambda e: e.groups(), data)
data = map(lambda e: Version(f"{e[0]}.{e[1]}.{e[2]}"), data)
return reversed(sorted(data))
if __name__ == '__main__':
total_result = []
def version_definition_type(arg):
if not VERSION_DEFINITION_RE.match(arg):
raise argparse.ArgumentTypeError(f"invalid version definition `{arg}`")
return arg
parser = argparse.ArgumentParser(description='Get scylla and cassandra versions.')
parser.add_argument('definitions', metavar='VERSION-DEFINITION', type=version_definition_type, nargs='+',
help='A list of scylla and cassandra version definitions: scylla-oss-stable[:COUNT] | scylla-enterprise-stable[:COUNT] | cassandra3-stable[:COUNT] | cassandra4-stable[:COUNT] | scylla-oss-rc[:COUNT] | scylla-enterprise-rc[:COUNT]')
parser.add_argument('--version-index', dest='version_index', type=int, default=0, help='print single version only')
args = parser.parse_args()
for version_def in args.definitions:
groups = VERSION_DEFINITION_RE.match(version_def).groups()
groups = [g for g in groups if g][1:]
mode_name = groups[0]
versions_count = int(groups[1].replace(":","")) if len(groups) > 1 else 1
result = []
if mode_name == 'scylla-oss-stable':
result = filter_versions(fetch_docker_hub_tags(*SCYLLA_OSS), versions_count, scylla_oss_only, release_only)
elif mode_name == 'scylla-enterprise-stable':
result = filter_versions(fetch_docker_hub_tags(*SCYLLA_ENTERPRISE), versions_count, scylla_enterprise_only, release_only)
elif mode_name == 'cassandra3-stable':
result = filter_versions(fetch_all_cassandra_versions(), versions_count, cassandra_3_only)
elif mode_name == 'cassandra4-stable':
result = filter_versions(fetch_all_cassandra_versions(), versions_count, cassandra_4_only)
elif mode_name == 'scylla-oss-rc':
result = filter_versions(fetch_docker_hub_tags(*SCYLLA_OSS), versions_count, scylla_oss_only, rc_only)
elif mode_name == 'scylla-enterprise-rc':
result = filter_versions(fetch_docker_hub_tags(*SCYLLA_ENTERPRISE), versions_count, scylla_enterprise_only, rc_only)
total_result += result
total_result = [str(k) for k in reversed(sorted(total_result))]
if args.version_index == 0:
print(json.dumps(list(total_result)))
sys.exit(0)
if len(total_result) < args.version_index:
print("No versions found", file=sys.stderr)
sys.exit(1)
print(json.dumps(total_result[args.version_index-1]))
sys.exit(0)