summaryrefslogtreecommitdiff
path: root/morphlib/ostreeartifactcache.py
diff options
context:
space:
mode:
Diffstat (limited to 'morphlib/ostreeartifactcache.py')
-rw-r--r--morphlib/ostreeartifactcache.py147
1 files changed, 106 insertions, 41 deletions
diff --git a/morphlib/ostreeartifactcache.py b/morphlib/ostreeartifactcache.py
index ea659c8f..230460f8 100644
--- a/morphlib/ostreeartifactcache.py
+++ b/morphlib/ostreeartifactcache.py
@@ -15,8 +15,10 @@
import collections
+import contextlib
import logging
import os
+import stat
import shutil
import tarfile
import tempfile
@@ -27,26 +29,48 @@ from gi.repository import GLib
import morphlib
from morphlib.artifactcachereference import ArtifactCacheReference
+
+class NotCachedError(morphlib.Error):
+
+ def __init__(self, ref):
+ self.msg = 'Failed to checkout %s from the artifact cache.' % ref
+
+
class OSTreeArtifactCache(object):
"""Class to provide the artifact cache API using an OSTree repo."""
- def __init__(self, cachedir, mode):
+ def __init__(self, cachedir, mode='bare', status_cb=None):
repo_dir = os.path.join(cachedir, 'repo')
self.repo = morphlib.ostree.OSTreeRepo(repo_dir, mode=mode)
self.cachedir = cachedir
+ self.status_cb = status_cb
+
+ def status(self, *args, **kwargs):
+ if self.status_cb is not None:
+ self.status_cb(*args, **kwargs)
+ @contextlib.contextmanager
def _get_file_from_remote(self, artifact, remote, metadata_name=None):
if metadata_name:
handle = remote.get_artifact_metadata(artifact, metadata_name)
+ self.status(
+ msg='Downloading %(name)s %(metadata_name)s as a file.',
+ chatty=True, name=artifact.basename(),
+ metadata_name=metadata_name)
else:
handle = remote.get(artifact)
- fd, path = tempfile.mkstemp()
- with open(path, 'w+') as temp:
- shutil.copyfileobj(handle, temp)
- return path
+ self.status(
+ msg='Downloading %(name)s as a tarball.', chatty=True,
+ name=artifact.basename())
+
+ try:
+ temporary_download = tempfile.NamedTemporaryFile(dir=self.cachedir)
+ shutil.copyfileobj(handle, temporary_download)
+ yield temporary_download.name
+ finally:
+ temporary_download.close()
def _get_artifact_cache_name(self, artifact):
- logging.debug('LAC: %s' % artifact.basename())
cache_key, kind, name = artifact.basename().split('.', 2)
suffix = name.split('-')[-1]
return '%s-%s' % (cache_key, suffix)
@@ -58,11 +82,13 @@ class OSTreeArtifactCache(object):
contents of directory should be the contents of the artifact.
"""
+ cache_key, kind, name = artifact.basename().split('.', 2)
ref = self._get_artifact_cache_name(artifact)
- subject = artifact.name
+ subject = name
try:
- logging.debug('Committing %s to artifact cache at %s.' %
- (subject, ref))
+ self.status(
+ msg='Committing %(subject)s to artifact cache at %(ref)s.',
+ chatty=True, subject=subject, ref=ref)
self.repo.commit(subject, directory, ref)
except GLib.GError as e:
logging.debug('OSTree raised an exception: %s' % e)
@@ -77,44 +103,71 @@ class OSTreeArtifactCache(object):
else:
filename = self.artifact_filename(artifact)
shutil.copy(location, filename)
- os.remove(location)
+
+ def _remove_device_nodes(self, path):
+ for dirpath, dirnames, filenames in os.walk(path):
+ for f in filenames:
+ filepath = os.path.join(dirpath, f)
+ mode = os.lstat(filepath).st_mode
+ if stat.S_ISBLK(mode) or stat.S_ISCHR(mode):
+ logging.debug('Removing device node %s from artifact' %
+ filepath)
+ os.remove(filepath)
+
+ def _copy_metadata_from_remote(self, artifact, remote):
+ """Copy a metadata file from a remote cache."""
+ a, name = artifact.basename().split('.', 1)
+ with self._get_file_from_remote(ArtifactCacheReference(a),
+ remote, name) as location:
+ self.put_non_ostree_artifact(ArtifactCacheReference(a),
+ location, name)
def copy_from_remote(self, artifact, remote):
- """Get 'artifact' from remote artifact cache and store it locally."""
+ """Get 'artifact' from remote artifact cache and store it locally.
+
+ This takes an Artifact object and a RemoteArtifactCache. Note that
+ `remote` here is not the same as a `remote` for and OSTree repo.
+
+ """
if remote.method == 'tarball':
- logging.debug('Downloading artifact tarball for %s.' %
- artifact.name)
- location = self._get_file_from_remote(artifact, remote)
- try:
- tempdir = tempfile.mkdtemp()
- with tarfile.open(name=location) as tf:
- tf.extractall(path=tempdir)
+ with self._get_file_from_remote(artifact, remote) as location:
try:
+ cache_key, kind, name = artifact.basename().split('.', 2)
+ except ValueError:
+ # We can't split the name properly, it must be metadata!
+ self._copy_metadata_from_remote(artifact, remote)
+ return
+
+ if kind == 'stratum':
+ self.put_non_ostree_artifact(artifact, location)
+ return
+ try:
+ tempdir = tempfile.mkdtemp(dir=self.cachedir)
+ with tarfile.open(name=location) as tf:
+ tf.extractall(path=tempdir)
+ self._remove_device_nodes(tempdir)
self.put(tempdir, artifact)
+ except tarfile.ReadError:
+ # Reading the tarball failed, and we expected a
+ # tarball artifact. Something must have gone
+ # wrong.
+ raise
finally:
- os.remove(location)
shutil.rmtree(tempdir)
- except tarfile.ReadError:
- # Reading the artifact as a tarball failed, so it must be a
- # single file (for example a stratum artifact).
- self.put_non_ostree_artifact(artifact, location)
elif remote.method == 'ostree':
- logging.debug('Pulling artifact for %s from remote.' %
- artifact.basename())
+ self.status(msg='Pulling artifact for %(name)s from remote.',
+ chatty=True, name=artifact.basename())
try:
ref = self._get_artifact_cache_name(artifact)
- except Exception:
+ except ValueError:
# if we can't split the name properly, we must want metadata
- a, name = artifact.basename().split('.', 1)
- location = self._get_file_from_remote(
- ArtifactCacheReference(a), remote, name)
- self.put_non_ostree_artifact(artifact, location, name)
+ self._copy_metadata_from_remote(artifact, remote)
return
if artifact.basename().split('.', 2)[1] == 'stratum':
- location = self._get_file_from_remote(artifact, remote)
- self.put_non_ostree_artifact(artifact, location)
+ with self._get_file_from_remote(artifact, remote) as location:
+ self.put_non_ostree_artifact(artifact, location)
return
try:
@@ -126,7 +179,7 @@ class OSTreeArtifactCache(object):
raise cliapp.AppException('Failed to pull %s from remote '
'cache.' % ref)
- def get(self, artifact, directory=None, status=lambda a: a):
+ def get(self, artifact, directory=None):
"""Checkout an artifact from the repo and return its location."""
cache_key, kind, name = artifact.basename().split('.', 2)
if kind == 'stratum':
@@ -136,11 +189,13 @@ class OSTreeArtifactCache(object):
ref = self._get_artifact_cache_name(artifact)
try:
self.repo.checkout(ref, directory)
+ # We need to update the mtime and atime of the ref file in the
+ # repository so that we can decide which refs were least recently
+ # accessed when doing `morph gc`.
self.repo.touch_ref(ref)
except GLib.GError as e:
logging.debug('OSTree raised an exception: %s' % e)
- raise cliapp.AppException('Failed to checkout %s from artifact '
- 'cache.' % ref)
+ raise NotCachedError(ref)
return directory
def list_contents(self):
@@ -173,16 +228,26 @@ class OSTreeArtifactCache(object):
self.repo.prune()
def has(self, artifact):
- cachekey, kind, name = artifact.basename().split('.', 2)
- logging.debug('OSTreeArtifactCache: got %s, %s, %s' %
- (cachekey, kind, name))
+ try:
+ cachekey, kind, name = artifact.basename().split('.', 2)
+ except ValueError:
+ # We couldn't split the basename properly, we must want metadata
+ cachekey, name = artifact.basename().split('.', 1)
+ if self.has_artifact_metadata(artifact, name):
+ return True
+ else:
+ return False
+
+ if kind == 'stratum':
+ if self._has_file(self.artifact_filename(artifact)):
+ return True
+ else:
+ return False
+
sha = self.repo.resolve_rev(self._get_artifact_cache_name(artifact))
if sha:
self.repo.touch_ref(self._get_artifact_cache_name(artifact))
return True
- if kind == 'stratum' and \
- self._has_file(self.artifact_filename(artifact)):
- return True
return False
def get_artifact_metadata(self, artifact, name):