Skip to content

Commit

Permalink
recording: record the snaps installed in the machine
Browse files Browse the repository at this point in the history
Closes: #1556
  • Loading branch information
Leo Arias committed Sep 22, 2017
1 parent c6a4822 commit 2ac88fd
Show file tree
Hide file tree
Showing 8 changed files with 228 additions and 102 deletions.
15 changes: 15 additions & 0 deletions integration_tests/test_asset_recording.py
Expand Up @@ -88,6 +88,21 @@ def test_prime_records_installed_packages(self):
recorded_yaml['parts']['dummy-part']['installed-packages'],
Contains(expected_package))

def test_prime_records_installed_snaps(self):
self.run_snapcraft('prime', project_dir='basic')

recorded_yaml_path = os.path.join(
self.prime_dir, 'snap', 'manifest.yaml')
with open(recorded_yaml_path) as recorded_yaml_file:
recorded_yaml = yaml.load(recorded_yaml_file)

expected_package = 'core={}'.format(
snaps.SnapPackage(
'core').get_local_snap_info()['revision'])
self.assertThat(
recorded_yaml['parts']['dummy-part']['installed-snaps'],
Contains(expected_package))

def test_prime_with_architectures(self):
"""Test the recorded manifest for a basic snap
Expand Down
3 changes: 2 additions & 1 deletion snapcraft/internal/pluginhandler/__init__.py
Expand Up @@ -344,7 +344,8 @@ def mark_build_done(self):
def _get_machine_manifest(self):
return {
'uname': common.run_output(['uname', '-srvmpio']),
'installed-packages': repo.Repo.get_installed_packages()
'installed-packages': repo.Repo.get_installed_packages(),
'installed-snaps': repo.snaps.get_installed_snaps()
}

def clean_build(self, hint=''):
Expand Down
19 changes: 19 additions & 0 deletions snapcraft/internal/repo/snaps.py
Expand Up @@ -193,6 +193,16 @@ def _snap_command_requires_sudo():
return requires_root


def get_installed_snaps():
"""Return all the snaps installed in the system.
:return: a list of "name=revision" for the snaps installed.
"""
local_snaps = _get_local_snaps()
return ['{}={}'.format(snap['name'], snap['revision']) for
snap in local_snaps]


def _get_parsed_snap(snap):
if '/' in snap:
sep_index = snap.find('/')
Expand Down Expand Up @@ -226,3 +236,12 @@ def _get_store_snap_info(snap_name):
snap_info = session.get(url)
snap_info.raise_for_status()
return snap_info.json()['result'][0]


def _get_local_snaps():
slug = 'snaps'
url = get_snapd_socket_path_template().format(slug)
with requests_unixsocket.Session() as session:
snap_info = session.get(url)
snap_info.raise_for_status()
return snap_info.json()['result']
28 changes: 20 additions & 8 deletions snapcraft/tests/fake_servers/snapd.py
Expand Up @@ -20,19 +20,31 @@

class FakeSnapdServer(BaseHTTPRequestHandler):

snaps_not_found = []
installed_snaps = []
snaps = []
snap_details_not_found = []
snap_details_installed = []

def do_GET(self):
parsed_url = parse.urlparse(self.path)
if parsed_url.path.startswith('/v2/snaps/'):
self._handle_snaps(parsed_url)
if parsed_url.path == '/v2/snaps':
self._handle_snaps()
elif parsed_url.path.startswith('/v2/snaps/'):
self._handle_snap_details(parsed_url)
elif parsed_url.path == '/v2/find':
self._handle_find(parsed_url)
else:
self.wfile.write(parsed_url.path.encode())

def _handle_snaps(self, parsed_url):
def _handle_snaps(self):
status_code = 200
params = self.snaps
self.send_response(status_code)
self.send_header('Content-Type', 'text/application+json')
self.end_headers()
response = json.dumps({'result': params}).encode()
self.wfile.write(response)

def _handle_snap_details(self, parsed_url):
status_code = 404
params = {}

Expand All @@ -57,14 +69,14 @@ def _handle_snaps(self, parsed_url):
elif parsed_url.path.endswith('/fake-snap-edge'):
status_code = 200
params = {'channel': 'edge'}
elif (parsed_url.path in self.snaps_not_found and
parsed_url.path.split('/')[-1] in self.installed_snaps):
elif (parsed_url.path in self.snap_details_not_found and
parsed_url.path.split('/')[-1] in self.snap_details_installed):
# XXX when the snaps end point fails, the snap is installed and
# it needs a revision the next time the same endpoint is called.
status_code = 200
params = {'channel': 'dummy', 'revision': 'dummy'}
else:
self.snaps_not_found.append(parsed_url.path)
self.snap_details_not_found.append(parsed_url.path)

self.send_response(status_code)
self.send_header('Content-Type', 'text/application+json')
Expand Down
90 changes: 49 additions & 41 deletions snapcraft/tests/fixture_setup.py
Expand Up @@ -19,9 +19,11 @@
import copy
import io
import os
import socketserver
import string
import subprocess
import sys
import tempfile
import threading
import urllib.parse
import uuid
Expand All @@ -39,6 +41,7 @@
from snapcraft.tests.fake_servers import (
api,
search,
snapd,
upload
)
from snapcraft.tests.subprocess_utils import (
Expand Down Expand Up @@ -595,47 +598,6 @@ def terminate(self):
return Popen(args)


class FakeSnapd(fixtures.Fixture):
'''...'''

def __init__(self):
self.snaps = {
'core': {'confinement': 'strict',
'id': '2kkitQurgOkL3foImG4wDwn9CIANuHlt',
'revision': '123'},
'snapcraft': {'confinement': 'classic',
'id': '3lljuRvshPlM4gpJnH5xExo0DJBOvImu',
'revision': '345'},
}

def _setUp(self):
patcher = mock.patch('requests_unixsocket.Session.request')
self.session_request_mock = patcher.start()
self.session_request_mock.side_effect = self.request_side_effect()
self.addCleanup(patcher.stop)

def request_side_effect(self):
def request_effect(*args, **kwargs):
if args[0] == 'GET' and '/v2/snaps/' in args[1]:
class Session:
def __init__(self, name, snaps):
self._name = name
self._snaps = snaps

def json(self):
if self._name not in self._snaps:
return {'status': 'Not Found',
'result': {'message': 'not found'},
'status-code': 404,
'type': 'error'}
return {'status': 'OK',
'type': 'sync',
'result': self._snaps[self._name]}
name = args[1].split('/')[-1]
return Session(name, self.snaps)
return request_effect


class GitRepo(fixtures.Fixture):
'''Create a git repo in the current directory'''

Expand Down Expand Up @@ -972,3 +934,49 @@ def setUp(self):
with open(os.path.join(self.path, 'snapcraft.yaml'),
'w') as snapcraft_yaml_file:
yaml.dump(self.data, snapcraft_yaml_file)


class UnixHTTPServer(socketserver.UnixStreamServer):

def get_request(self):
request, client_address = self.socket.accept()
# BaseHTTPRequestHandler expects a tuple with the client address at
# index 0, so we fake one
if len(client_address) == 0:
client_address = (self.server_address,)
return (request, client_address)


class FakeSnapd(fixtures.Fixture):

@property
def snaps(self):
self.server.RequestHandlerClass.snaps

@snaps.setter
def snaps(self, value):
self.server.RequestHandlerClass.snaps = value

def _setUp(self):
snapd_fake_socket_path = tempfile.mkstemp()[1]
os.unlink(snapd_fake_socket_path)

socket_path_patcher = mock.patch(
'snapcraft.internal.repo.snaps.get_snapd_socket_path_template')
mock_socket_path = socket_path_patcher.start()
mock_socket_path.return_value = 'http+unix://{}/v2/{{}}'.format(
snapd_fake_socket_path.replace('/', '%2F'))
self.addCleanup(socket_path_patcher.stop)

self._start_fake_server(snapd_fake_socket_path)

def _start_fake_server(self, socket):
self.server = UnixHTTPServer(socket, snapd.FakeSnapdServer)
server_thread = threading.Thread(target=self.server.serve_forever)
server_thread.start()
self.addCleanup(self._stop_fake_server, server_thread)

def _stop_fake_server(self, thread):
self.server.shutdown()
self.server.socket.close()
thread.join()
65 changes: 20 additions & 45 deletions snapcraft/tests/repo/test_snaps.py
Expand Up @@ -13,19 +13,16 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import socketserver

import subprocess
import tempfile
from threading import Thread
from unittest import mock

import fixtures
from testtools.matchers import Equals, Is

from snapcraft import tests
from snapcraft.internal.repo import errors, snaps
from snapcraft.tests.fake_servers.snapd import FakeSnapdServer
from snapcraft.tests import fixture_setup


class FakeSnapCommand(fixtures.Fixture):
Expand Down Expand Up @@ -92,50 +89,12 @@ def _fake_snap_command(self, cmd, *args, **kwargs):
return 'email: {}'.format(self._email).encode()


class UnixHTTPServer(socketserver.UnixStreamServer):

def get_request(self):
request, client_address = self.socket.accept()
# BaseHTTPRequestHandler expects a tuple with the client address at
# index 0, so we fake one
if len(client_address) == 0:
client_address = (self.server_address,)
return (request, client_address)


class FakeSnapd(fixtures.Fixture):

def _setUp(self):
snapd_fake_socket_path = tempfile.mkstemp()[1]
os.unlink(snapd_fake_socket_path)

socket_path_patcher = mock.patch(
'snapcraft.internal.repo.snaps.get_snapd_socket_path_template')
mock_socket_path = socket_path_patcher.start()
mock_socket_path.return_value = 'http+unix://{}/v2/{{}}'.format(
snapd_fake_socket_path.replace('/', '%2F'))
self.addCleanup(socket_path_patcher.stop)

self._start_fake_server(snapd_fake_socket_path)

def _start_fake_server(self, socket):
self.server = UnixHTTPServer(socket, FakeSnapdServer)
server_thread = Thread(target=self.server.serve_forever)
server_thread.start()
self.addCleanup(self._stop_fake_server, server_thread)

def _stop_fake_server(self, thread):
self.server.shutdown()
self.server.socket.close()
thread.join()


class SnapPackageBaseTestCase(tests.TestCase):

def setUp(self):
super().setUp()

self.fake_snapd = FakeSnapd()
self.fake_snapd = fixture_setup.FakeSnapd()
self.useFixture(self.fake_snapd)


Expand Down Expand Up @@ -326,7 +285,7 @@ def test_install_snaps_returns_revision(self):
Equals(['fake-snap=test-fake-snap-revision']))

def test_install_multiple_snaps(self):
self.fake_snapd.server.RequestHandlerClass.installed_snaps = [
self.fake_snapd.server.RequestHandlerClass.snap_details_installed = [
'new-fake-snap']
snaps.install_snaps([
'fake-snap/classic/stable',
Expand All @@ -338,3 +297,19 @@ def test_install_multiple_snaps(self):
'--channel', 'classic/stable', '--classic'],
['snap', 'whoami'],
['sudo', 'snap', 'install', 'new-fake-snap']]))


class InstalledSnapsTestCase(SnapPackageBaseTestCase):

def test_get_installed_snaps(self):
self.fake_snapd.snaps = [
{'name': 'test-snap-1',
'revision': 'test-snap-1-revision'},
{'name': 'test-snap-2',
'revision': 'test-snap-2-revision'},
]
installed_snaps = snaps.get_installed_snaps()
self.assertThat(
installed_snaps,
Equals(['test-snap-1=test-snap-1-revision',
'test-snap-2=test-snap-2-revision']))

0 comments on commit 2ac88fd

Please sign in to comment.