Merge ~jocave/plainbox-provider-resource:simplify-snapd-resource into plainbox-provider-resource:master

Proposed by Jonathan Cave
Status: Merged
Approved by: Jonathan Cave
Approved revision: 0066655d64cbc236a987a15d2e2f927c4437529a
Merged at revision: b715b0b22b91138b9a4749e286435ccefd7b5dd4
Proposed branch: ~jocave/plainbox-provider-resource:simplify-snapd-resource
Merge into: plainbox-provider-resource:master
Diff against target: 310 lines (+60/-177)
1 file modified
bin/snapd_resource (+60/-177)
Reviewer Review Type Date Requested Status
Sylvain Pineau (community) Approve
Review via email: mp+365455@code.launchpad.net

Description of the change

Use the new snapd class in checkbox-support to simplify the snapd_resource script.

Tested that all the resource jobs that use this script work as before.

Depends on https://code.launchpad.net/~jocave/checkbox-support/+git/checkbox-support/+merge/365454

To post a comment you must log in.
Revision history for this message
Sylvain Pineau (sylvain-pineau) wrote :

Also tested all jobs using snapd_resource, +1.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/bin/snapd_resource b/bin/snapd_resource
2index d88ffa0..234ccd8 100755
3--- a/bin/snapd_resource
4+++ b/bin/snapd_resource
5@@ -5,100 +5,50 @@
6 # Written by:
7 # Authors: Jonathan Cave <jonathan.cave@canonical.com>
8
9-import json
10 import io
11-import logging
12-import os
13 import string
14-import sys
15-import time
16-import traceback
17
18 from guacamole import Command
19-import requests
20-import requests_unixsocket
21+from checkbox_support.snap_utils.snapd import Snapd
22
23 from collections import namedtuple
24
25
26-SNAPD_BASE_URL = 'http+unix://%2Frun%2Fsnapd.socket'
27-
28-
29-class SnapdQuery(Command):
30-
31- def __init__(self):
32- self._session = requests_unixsocket.Session()
33-
34- def get(self, path):
35- try:
36- r = self._session.get(SNAPD_BASE_URL + path)
37- except requests.exceptions.RequestException as e:
38- # snapd might not be installed, silently return an empty Response
39- if 'FileNotFoundError' in traceback.format_exc():
40- r = requests.Response()
41- r.status_code = 200
42- r._content = b'{"result":""}'
43- r.headers['X-Ubuntu-Assertions-Count'] = 0
44- else:
45- raise
46- if r.status_code != requests.codes.ok:
47- logging.error("Got error {} attempting to access {}".format(
48- r.status_code, path))
49- sys.exit(1)
50- return r
51-
52- def post(self, path, data=None):
53- try:
54- res = self._session.post(SNAPD_BASE_URL + path, data=data)
55- except requests.exceptions.RequestException as e:
56- # snapd might not be installed, silently return an empty Response
57- if 'FileNotFoundError' in traceback.format_exc():
58- r = requests.Response()
59- r.status_code = 200
60- r._content = b'{"result":""}'
61- r.headers['X-Ubuntu-Assertions-Count'] = 0
62- else:
63- raise
64- if not res.ok:
65- logging.error("Got error %i attempting to access %s",
66- res.status_code, path)
67- sys.exit(1)
68- return res.json()
69-
70-
71-class AssertionQuery(SnapdQuery):
72-
73- prefix = '/v2/assertions/'
74-
75- def convert(self, assertion):
76- """ Super naive Assertion parser
77-
78- No attempt to handle assertions with a body. Discards signatures based
79- on lack of colon characters. Update: need to be less naive about
80- gadget and kernel names on UC18
81- """
82- data = self.get(self.prefix + assertion)
83- count = int(data.headers['X-Ubuntu-Assertions-Count'])
84- if count > 0:
85- for line in io.StringIO(data.text):
86- if line.strip() == "":
87- print()
88- if line.count(':') == 1:
89- key, val = [x.strip() for x in line.split(':')]
90- if key in ('gadget', 'kernel'):
91- if '=' in val:
92- snap, track = [x.strip() for x in val.split('=')]
93- print('{}: {}'.format(key, snap))
94- print('{}_track: {}'.format(key, track))
95- continue
96- print(line.strip())
97- return count
98-
99-
100-class ModelAssertion(AssertionQuery):
101+def slugify(_string):
102+ """Transform string to one that can be used as the key in a resource job"""
103+ valid_chars = frozenset(
104+ "_{}{}".format(string.ascii_letters, string.digits))
105+ return ''.join(c if c in valid_chars else '_' for c in _string)
106+
107+
108+def http_to_resource(assertion_stream):
109+ """ Super naive Assertion parser
110+
111+ No attempt to handle assertions with a body. Discards signatures based
112+ on lack of colon characters. Update: need to be less naive about
113+ gadget and kernel names on UC18
114+ """
115+ count = int(assertion_stream.headers['X-Ubuntu-Assertions-Count'])
116+ if count > 0:
117+ for line in io.StringIO(assertion_stream.text):
118+ if line.strip() == "":
119+ print()
120+ if line.count(':') == 1:
121+ key, val = [x.strip() for x in line.split(':')]
122+ if key in ('gadget', 'kernel'):
123+ if '=' in val:
124+ snap, track = [x.strip() for x in val.split('=')]
125+ print('{}: {}'.format(key, snap))
126+ print('{}_track: {}'.format(key, track))
127+ continue
128+ print(line.strip())
129+ return count
130+
131+
132+class ModelAssertion(Command):
133
134 def invoked(self, ctx):
135- count = self.convert('model')
136+ count = http_to_resource(Snapd().get_assertions('model'))
137 if count == 0:
138 # Print a dummy assertion - not nice but really trick to use
139 # plainbox resources without some defualt value
140@@ -108,10 +58,10 @@ class ModelAssertion(AssertionQuery):
141 print()
142
143
144-class SerialAssertion(AssertionQuery):
145+class SerialAssertion(Command):
146
147 def invoked(self, ctx):
148- count = self.convert('serial')
149+ count = http_to_resource(Snapd().get_assertions('serial'))
150 if count == 0:
151 # Print a dummy assertion - not nice but really trick to use
152 # plainbox resources without some defualt value
153@@ -129,14 +79,11 @@ class Assertions(Command):
154 )
155
156
157-class Snaps(SnapdQuery):
158-
159- prefix = '/v2/snaps'
160+class Snaps(Command):
161
162 def invoked(self, ctx):
163- data = self.get(self.prefix).json()
164-
165- for snap in data['result']:
166+ data = Snapd().list()
167+ for snap in data:
168 def print_field(key):
169 try:
170 val = snap[key]
171@@ -153,25 +100,13 @@ class Snaps(SnapdQuery):
172 print()
173
174
175-class InterfacesQuery(SnapdQuery):
176-
177- prefix = '/v2/interfaces'
178-
179-
180-def slugify(_string):
181- """Transform string to one that can be used as the key in a resource job"""
182- valid_chars = frozenset(
183- "_{}{}".format(string.ascii_letters, string.digits))
184- return ''.join(c if c in valid_chars else '_' for c in _string)
185-
186-
187-class Endpoints(InterfacesQuery):
188+class Endpoints(Command):
189
190 def invoked(self, ctx):
191- data = self.get(self.prefix).json()
192+ data = Snapd().interfaces()
193
194- if 'plugs' in data['result']:
195- for plug in data['result']['plugs']:
196+ if 'plugs' in data:
197+ for plug in data['plugs']:
198 def print_field(key):
199 val = plug[key]
200 if val != '':
201@@ -186,8 +121,8 @@ class Endpoints(InterfacesQuery):
202 print('attr_{}: {}'.format(slugify(attr), val))
203 print()
204
205- if 'slots' in data['result']:
206- for slot in data['result']['slots']:
207+ if 'slots' in data:
208+ for slot in data['slots']:
209 def print_field(key):
210 val = slot[key]
211 if val != '':
212@@ -208,85 +143,33 @@ Connection = namedtuple(
213 ['target_snap', 'target_slot', 'plug_snap', 'plug_plug'])
214
215
216-class Connections(InterfacesQuery):
217+def get_connections():
218+ data = Snapd().interfaces()
219+ connections = []
220+ if 'plugs' in data:
221+ for plug in data['plugs']:
222+ if 'connections' in plug:
223+ for con in plug['connections']:
224+ connections.append(Connection(
225+ con['snap'], con['slot'],
226+ plug['snap'], plug['plug']))
227+ return connections
228+
229+
230+class Connections(Command):
231
232 def invoked(self, ctx):
233- for conn in self.get_connections():
234+ for conn in get_connections():
235 print('slot: {}:{}'.format(conn.target_snap, conn.target_slot))
236 print('plug: {}:{}'.format(conn.plug_snap, conn.plug_plug))
237 print()
238
239- def get_connections(self):
240- data = self.get('/v2/interfaces').json()['result']
241- connections = []
242- if 'plugs' in data:
243- for plug in data['plugs']:
244- if 'connections' in plug:
245- for con in plug['connections']:
246- connections.append(Connection(
247- con['snap'], con['slot'],
248- plug['snap'], plug['plug']))
249- return connections
250-
251-
252-class Connect(Connections):
253- """
254- Connect 'current snap' to other snaps.
255-
256- The syntax of the command is A:B:C, where:
257- A - plug to be connected
258- B - target snap (snap the plug will connect to)
259- C - target slot (slot to connect to)
260-
261- Note that originating snap is implied. $SNAP_NAME is used.
262-
263- Example:
264-
265- $ sudo snapd_resource interfaces connect \
266- udisks2:udisks2:service
267-
268- Note that the program needs sudo to connect plugs.
269- """
270-
271- def register_arguments(self, parser):
272- parser.add_argument(
273- 'connection', nargs='+', default=[],
274- metavar='plug:target_snap:target_slot')
275-
276- def invoked(self, ctx):
277- for conn in [spec.split(':') for spec in ctx.args.connection]:
278- if len(conn) != 3:
279- raise SystemExit("Bad connection description")
280- assert os.environ['SNAP_NAME']
281- snap = os.environ['SNAP_NAME']
282- existing_connections = self.get_connections()
283- new_connection = Connection(
284- target_snap=conn[1], target_slot=conn[2],
285- plug_snap=snap, plug_plug=conn[0])
286- if new_connection not in existing_connections:
287- self.connect(new_connection)
288-
289- def connect(self, con):
290- json_data = json.dumps({
291- 'action': 'connect',
292- 'slots': [{'snap': con.target_snap, 'slot': con.target_slot}],
293- 'plugs': [{'snap': con.plug_snap, 'plug': con.plug_plug}]
294- })
295- res = self.post('/v2/interfaces', json_data)
296- ready = False
297- while not ready:
298- # busy wait until snapd reports connection job as finised
299- time.sleep(0.5)
300- con_res = self.get('/v2/changes/{}'.format(res['change']))
301- ready = con_res.json()['result']['ready']
302-
303
304 class Interfaces(Command):
305
306 sub_commands = (
307 ('endpoints', Endpoints),
308 ('connections', Connections),
309- ('connect', Connect),
310 )
311
312

Subscribers

People subscribed via source and target branches