-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmender-api
executable file
·197 lines (163 loc) · 8.39 KB
/
mender-api
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
#!/usr/bin/env python3
import os
import sys
import re
import time
import uuid
import json
import argparse
import requests
import subprocess
class ScriptError(Exception):
pass
class MenderClient:
def __init__(self, server, user, password, token):
self._server = server
self._user = user
self._password = password
self._token = token
def _get_token(self):
if not self._token:
ret = requests.post('{}/api/management/v1/useradm/auth/login'.format(self._server),
auth=(self._user, self._password))
ret.raise_for_status()
self._token = ret.text
return self._token
def _get_headers(self):
headers = {
'Accept': 'application/json',
'Authorization': 'Bearer {}'.format(self._get_token())
}
return headers
def upload(self, artifact):
print("Analyzing artifact {}.".format(artifact.name))
ret = subprocess.run(['mender-artifact', 'read', artifact.name], capture_output=True)
ret.check_returncode()
match = re.search(' Name: (.+?)\n', ret.stdout.decode("utf-8"))
if not match:
raise ScriptError('Unable to extract artifact name from {}'.format(artifact.name))
artifact_name = match.group(1)
parameters = {
'name': artifact_name
}
ret = requests.get('{}/api/management/v1/deployments/deployments/releases/list'.format(self._server),
headers=self._get_headers(), params=parameters)
ret.raise_for_status()
if len(ret.json()) > 0:
print("{} already got uploaded to {}.".format(artifact.name, self._server))
else:
print("Uploading {} to {}.".format(artifact.name, self._server))
files = {'artifact': artifact}
ret = requests.post('{}/api/management/v1/deployments/artifacts'.format(self._server),
headers=self._get_headers(), files=files)
ret.raise_for_status()
print("Upload completed!")
return artifact_name
def dispatch(self, artifact_name, device_id, wait_for_completion=False):
deployment_name = str(uuid.uuid4())
deployment = {
'name': deployment_name,
'artifact_name': artifact_name,
'devices': [device_id]
}
print("Dispatching {} to {}.".format(artifact_name, device_id))
ret = requests.post('{}/api/management/v1/deployments/deployments'.format(self._server),
headers=self._get_headers(), json=deployment)
ret.raise_for_status()
if wait_for_completion:
parameters = {
'search': deployment_name
}
ret = requests.get('{}/api/management/v1/deployments/deployments'.format(self._server),
headers=self._get_headers(), params=parameters)
ret.raise_for_status()
deployment_id = None
for deployment in ret.json():
if deployment.get('name') == deployment_name:
deployment_id = deployment.get('id')
break
if not deployment_id:
raise ScriptError("Failed to retrieve deployment id (response={})".format(ret.json()))
deployment_status = None
for _ in range(120):
time.sleep(30)
ret = requests.get('{}/api/management/v1/deployments/deployments/{}'.format(self._server,
deployment_id),
headers=self._get_headers())
ret.raise_for_status()
deployment_status = ret.json().get('status')
print("Current deployment status is {}.".format(deployment_status))
if deployment_status == 'finished':
break
if deployment_status != 'finished':
raise ScriptError("Deployment (status={}) did not finish on time".format(deployment_status))
ret = requests.get('{}/api/management/v1/deployments/deployments/{}/devices/list'.format(self._server,
deployment_id),
headers=self._get_headers())
ret.raise_for_status()
if len(ret.json()) != 1:
raise ScriptError("Failed to retrieve deployment status of device (response={})".format(ret.json()))
device_deployment_status = ret.json()[0].get('status')
if device_deployment_status not in ['success', 'already-installed']:
raise ScriptError("Deployment for device {} failed with status {}".format(device_id,
device_deployment_status))
print("Artifact {} successfully deployed to {} (status={}).".format(artifact_name, device_id,
device_deployment_status))
def get_inventory(self, device_id, attribute=None):
ret = requests.get('{}/api/management/v1/inventory/devices/{}'.format(self._server, device_id),
headers=self._get_headers())
ret.raise_for_status()
if not attribute:
return json.dumps(ret.json())
value = None
for item in ret.json().get('attributes', list()):
if item.get('name') == attribute:
value = item.get('value')
if not value:
raise ScriptError("Device {} does not have the attribute {}".format(device_id, attribute))
return value
def main():
default_server = 'https://hosted.mender.io'
parser = argparse.ArgumentParser()
parser.add_argument('--server', default=default_server,
help='The Mender server (default: {}).'.format(default_server))
subparsers = parser.add_subparsers(title='commands', dest="command_name")
dispatch_parser = subparsers.add_parser('dispatch', help='dispatch artifact to device using Mender')
upload_parser = subparsers.add_parser('upload', help='upload artifact to Mender')
query_parser = subparsers.add_parser('query', help='query device inventory')
dispatch_parser.add_argument('--nowait', action='store_true',
help='Do not wait until deployment got completed.')
dispatch_parser.add_argument('DEVICE_ID', help='The ID of the device that shall receive the artifact.')
dispatch_parser.add_argument('ARTIFACT', type=argparse.FileType('rb'), help='The artifact file.')
upload_parser.add_argument('ARTIFACT', type=argparse.FileType('rb'), help='The artifact file.')
query_parser.add_argument('DEVICE_ID', help='The ID of the device that shall get queried.')
query_parser.add_argument('ATTRIBUTE', nargs='?', help='The attribute that shall get looked up.')
args = parser.parse_args()
mender_user = os.getenv('MENDER_USER')
mender_password = os.environ.get('MENDER_PASSWORD')
mender_token = os.environ.get('MENDER_ACCESS_TOKEN')
try:
if not mender_token:
if not mender_user:
raise ScriptError("Failed to retrieve Mender user or token from environment (MENDER_USER "
"or MENDER_ACCESS_TOKEN)")
if not mender_password:
raise ScriptError("Failed to retrieve Mender password from environment (MENDER_PASSWORD)")
client = MenderClient(args.server, mender_user, mender_password, mender_token)
if args.command_name == 'dispatch' or args.command_name == 'upload':
artifact_name = client.upload(args.ARTIFACT)
if args.DEVICE_ID:
client.dispatch(artifact_name, args.DEVICE_ID, not args.nowait)
elif args.command_name == 'query':
print(client.get_inventory(args.DEVICE_ID, args.ATTRIBUTE))
except requests.HTTPError as http_error:
print("Error: {}!".format(http_error), file=sys.stderr)
sys.exit(1)
except subprocess.CalledProcessError as process_error:
print("Error: {}!".format(process_error), file=sys.stderr)
sys.exit(1)
except ScriptError as script_error:
print("Error: {}!".format(script_error), file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()