Skip to content

Commit 833acb6

Browse files
committed
add AFWS client
1 parent d5eec65 commit 833acb6

File tree

4 files changed

+191
-0
lines changed

4 files changed

+191
-0
lines changed

MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ include versioneer.py
55
include artiq/_version.py
66
include artiq/coredevice/coredevice_generic.schema.json
77
include artiq/compiler/kernel.ld
8+
include artiq/afws.pem

artiq/afws.pem

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIID0zCCArugAwIBAgIUPkNfEUx/uau3z8SD4mgMbCK/DEgwDQYJKoZIhvcNAQEL
3+
BQAweTELMAkGA1UEBhMCSEsxEzARBgNVBAgMClNvbWUtU3RhdGUxFzAVBgNVBAoM
4+
Dk0tTGFicyBMaW1pdGVkMRkwFwYDVQQDDBBuaXhibGQubS1sYWJzLmhrMSEwHwYJ
5+
KoZIhvcNAQkBFhJoZWxwZGVza0BtLWxhYnMuaGswHhcNMjIwMjA2MTA1ODQ0WhcN
6+
MjUwMjA1MTA1ODQ0WjB5MQswCQYDVQQGEwJISzETMBEGA1UECAwKU29tZS1TdGF0
7+
ZTEXMBUGA1UECgwOTS1MYWJzIExpbWl0ZWQxGTAXBgNVBAMMEG5peGJsZC5tLWxh
8+
YnMuaGsxITAfBgkqhkiG9w0BCQEWEmhlbHBkZXNrQG0tbGFicy5oazCCASIwDQYJ
9+
KoZIhvcNAQEBBQADggEPADCCAQoCggEBAPWetZhoggPR2ae7waGzv1AQ8NQO3noW
10+
8DofVjusNpX5i/YB0waAr1bm1tALLJoHV2r/gTxujlXCe/L/WG1DLseCf6NO9sHg
11+
t0FLhDpF9kPMWBgauVVLepd2Y2yU1G8eFuEVGnsiQSu0IzsZP5FQBJSyxvxJ+V/L
12+
EW9ox91VGOP9VZR9jqdlYjGhcwClHA/nHe0q1fZq42+9rG466I5yIlNSoa7ilhTU
13+
2C2doxy6Sr6VJYnLEMQqoIF65t3MkKi9iaqN7az0OCrj6XR0P5iKBzUhIgMUd2qs
14+
7Id0XUdbQvaoaRI67vhGkNr+f4rdAUNCDGcbbokuBnmE7/gva6BAABUCAwEAAaNT
15+
MFEwHQYDVR0OBBYEFM2e2FmcytXhKyfC1KEjVJ2mPSy3MB8GA1UdIwQYMBaAFM2e
16+
2FmcytXhKyfC1KEjVJ2mPSy3MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL
17+
BQADggEBAKH0z5vlbfTghjYWwd2yEEFBbZx5XxaLHboFQpFpxu9sZoidVs047tco
18+
MOr1py9juiNGGM8G35sw9306f+thDFwqlQfSExUwp5pRQNq+mxglMSF05HWDqBwb
19+
wnItKi/WXpkMQXgpQJFVeflz4B4ZFNlH1UQl5bwacXOM9NM9zO7duCjVXmGE0yxi
20+
VQyApfPQYu9whCSowDYYaA0toJeikMzGfWxhlAH79/2Qmit8KcSCbX1fK/QoRZLa
21+
5NeUi/OlJbBpkgTrfzfMLphmsPWPAVMeUKzqd/vXfG6ZBOZZm6e6sl8RBycBezII
22+
15WekikTE5+T54/E0xiu+zIW/Xhhk14=
23+
-----END CERTIFICATE-----

artiq/frontend/afws_client.py

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
#!/usr/bin/env python3
2+
3+
import sys
4+
import argparse
5+
import os
6+
import socket
7+
import ssl
8+
import io
9+
import zipfile
10+
from getpass import getpass
11+
12+
13+
def get_artiq_cert():
14+
try:
15+
import artiq
16+
except ImportError:
17+
return None
18+
filename = os.path.join(os.path.dirname(artiq.__file__), "afws.pem")
19+
if not os.path.isfile(filename):
20+
return None
21+
return filename
22+
23+
24+
def get_artiq_rev():
25+
try:
26+
import artiq
27+
except ImportError:
28+
return None
29+
version = artiq.__version__
30+
if version.endswith(".beta"):
31+
version = version[:-5]
32+
version = version.split(".")
33+
if len(version) != 3:
34+
return None
35+
major, minor, rev = version
36+
return rev
37+
38+
39+
def zip_unarchive(data, directory):
40+
buf = io.BytesIO(data)
41+
with zipfile.ZipFile(buf) as archive:
42+
archive.extractall(directory)
43+
44+
45+
class Client:
46+
def __init__(self, server, port, cafile):
47+
self.ssl_context = ssl.create_default_context(cafile=cafile)
48+
self.raw_socket = socket.create_connection((server, port))
49+
try:
50+
self.socket = self.ssl_context.wrap_socket(self.raw_socket, server_hostname=server)
51+
except:
52+
self.raw_socket.close()
53+
raise
54+
self.fsocket = self.socket.makefile("rwb")
55+
56+
def close(self):
57+
self.socket.close()
58+
self.raw_socket.close()
59+
60+
def send_command(self, *command):
61+
self.fsocket.write((" ".join(command) + "\n").encode())
62+
self.fsocket.flush()
63+
64+
def read_reply(self):
65+
return self.fsocket.readline().decode("ascii").split()
66+
67+
def login(self, username, password):
68+
self.send_command("LOGIN", username, password)
69+
return self.read_reply() == ["HELLO"]
70+
71+
def build(self, rev, variant):
72+
self.send_command("BUILD", rev, variant)
73+
reply = self.read_reply()[0]
74+
if reply != "BUILDING":
75+
return reply, None
76+
print("Build in progress. This may take 10-15 minutes.")
77+
reply, status = self.read_reply()
78+
if reply != "DONE":
79+
raise ValueError("Unexpected server reply: expected 'DONE', got '{}'".format(reply))
80+
if status != "done":
81+
return status, None
82+
print("Build completed. Downloading...")
83+
reply, length = self.read_reply()
84+
if reply != "PRODUCT":
85+
raise ValueError("Unexpected server reply: expected 'PRODUCT', got '{}'".format(reply))
86+
contents = self.fsocket.read(int(length))
87+
print("Download completed.")
88+
return "OK", contents
89+
90+
def passwd(self, password):
91+
self.send_command("PASSWD", password)
92+
return self.read_reply() == ["OK"]
93+
94+
95+
def main():
96+
parser = argparse.ArgumentParser()
97+
parser.add_argument("--server", default="nixbld.m-labs.hk", help="server to connect to (default: %(default)s)")
98+
parser.add_argument("--port", default=7402, type=int, help="port to connect to (default: %(default)d)")
99+
parser.add_argument("--cert", default=None, help="SSL certificate file used to authenticate server (default: afws.pem in ARTIQ)")
100+
parser.add_argument("username", help="user name for logging into AFWS")
101+
action = parser.add_subparsers(dest="action")
102+
action.required = True
103+
act_build = action.add_parser("build", help="build and download firmware")
104+
act_build.add_argument("--rev", default=None, help="revision to build (default: currently installed ARTIQ revision)")
105+
act_build.add_argument("variant", help="variant to build")
106+
act_build.add_argument("directory", help="output directory")
107+
act_passwd = action.add_parser("passwd", help="change password")
108+
args = parser.parse_args()
109+
110+
cert = args.cert
111+
if cert is None:
112+
cert = get_artiq_cert()
113+
if cert is None:
114+
print("SSL certificate not found in ARTIQ. Specify manually using --cert.")
115+
sys.exit(1)
116+
117+
if args.action == "passwd":
118+
password = getpass("Current password: ")
119+
else:
120+
password = getpass()
121+
122+
client = Client(args.server, args.port, cert)
123+
try:
124+
if not client.login(args.username, password):
125+
print("Login failed")
126+
sys.exit(1)
127+
if args.action == "passwd":
128+
print("Password must made of alphanumeric characters (a-z, A-Z, 0-9) and be at least 8 characters long.")
129+
password = getpass("New password: ")
130+
password_confirm = getpass("New password (again): ")
131+
while password != password_confirm:
132+
print("Passwords do not match")
133+
password = getpass("New password: ")
134+
password_confirm = getpass("New password (again): ")
135+
if not client.passwd(password):
136+
print("Failed to change password")
137+
sys.exit(1)
138+
elif args.action == "build":
139+
try:
140+
os.mkdir(args.directory)
141+
except FileExistsError:
142+
if any(os.scandir(args.directory)):
143+
print("Output directory already exists and is not empty. Please remove it and try again.")
144+
sys.exit(1)
145+
rev = args.rev
146+
if rev is None:
147+
rev = get_artiq_rev()
148+
if rev is None:
149+
print("Unable to determine currently installed ARTIQ revision. Specify manually using --rev.")
150+
sys.exit(1)
151+
result, contents = client.build(rev, args.variant)
152+
if result != "OK":
153+
if result == "UNAUTHORIZED":
154+
print("You are not authorized to build this variant. Your firmware subscription may have expired. Contact helpdesk\x40m-labs.hk.")
155+
else:
156+
print("Build failed: {}".format(result))
157+
sys.exit(1)
158+
zip_unarchive(contents, args.directory)
159+
else:
160+
raise ValueError
161+
finally:
162+
client.close()
163+
164+
165+
if __name__ == "__main__":
166+
main()

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"artiq_run = artiq.frontend.artiq_run:main",
3434
"artiq_flash = artiq.frontend.artiq_flash:main",
3535
"aqctl_corelog = artiq.frontend.aqctl_corelog:main",
36+
"afws_client = artiq.frontend.afws_client:main",
3637
]
3738

3839
gui_scripts = [

0 commit comments

Comments
 (0)