← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~twom/launchpad:oci-buildjob into launchpad:master

 

Tom Wardill has proposed merging ~twom/launchpad:oci-buildjob into launchpad:master with ~twom/launchpad:oci-ocirecipebuild as a prerequisite.

Commit message:
OCIRecipeBuildJob support

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~twom/launchpad/+git/launchpad/+merge/376475

Derive a new type of BuildJob and use it to build and upload OCI Recipe artifacts
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~twom/launchpad:oci-buildjob into launchpad:master.
diff --git a/lib/lp/oci/interfaces/ocirecipebuildjob.py b/lib/lp/oci/interfaces/ocirecipebuildjob.py
new file mode 100644
index 0000000..d28c3e8
--- /dev/null
+++ b/lib/lp/oci/interfaces/ocirecipebuildjob.py
@@ -0,0 +1,38 @@
+# Copyright 2019 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""OCIRecipe build job interfaces"""
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+__metaclass__ = type
+__all__ = [
+    'IOCIRecipeBuildJob',
+    ]
+
+from lazr.restful.fields import Reference
+from zope.interface import (
+    Attribute,
+    Interface
+    )
+from zope.schema import TextLine
+
+from lp import _
+from lp.oci.interfaces.ocirecipebuild import IOCIRecipeBuild
+from lp.services.job.interfaces.job import (
+    IJob,
+    IJobSource,
+    IRunnableJob
+    )
+
+
+class IOCIRecipeBuildJob(Interface):
+    job = Reference(
+        title=_("The common Job attributes."), schema=IJob,
+        required=True, readonly=True)
+
+    build = Reference(
+        title=_("The OCI Recipe Build to use for this job."),
+        schema=IOCIRecipeBuild, required=True, readonly=True)
+
+    json_data = Attribute(_("A dict of data about the job."))
diff --git a/lib/lp/oci/model/ocirecipebuildjob.py b/lib/lp/oci/model/ocirecipebuildjob.py
new file mode 100644
index 0000000..5491e3d
--- /dev/null
+++ b/lib/lp/oci/model/ocirecipebuildjob.py
@@ -0,0 +1,143 @@
+# Copyright 2019 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""OCIRecipe build jobs."""
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+__metaclass__ = type
+__all__ = [
+    'OCIRecipeBuildJob',
+    ]
+
+import json
+
+from lazr.delegates import delegate_to
+from lazr.enum import (
+    DBEnumeratedType,
+    DBItem,
+    )
+from storm.locals import (
+    Int,
+    JSON,
+    Reference,
+    )
+from zope.interface import (
+    implementer,
+    provider,
+    )
+
+from lp.app.errors import NotFoundError
+from lp.oci.interfaces.ocirecipebuildjob import (
+    IOCIRecipeBuildJob,
+    )
+from lp.services.database.enumcol import EnumCol
+from lp.services.database.interfaces import (
+    IMasterStore,
+    IStore,
+    )
+from lp.services.database.stormbase import StormBase
+from lp.services.propertycache import get_property_cache
+from lp.services.job.model.job import (
+    EnumeratedSubclass,
+    Job,
+    )
+from lp.services.job.runner import BaseRunnableJob
+
+
+class OCIRecipeBuildJobType(DBEnumeratedType):
+    """Values that `OCIBuildJobType.job_type` can take."""
+
+    REGISTRY_UPLOAD = DBItem(0, """
+        Registry upload
+
+        This job uploads an OCI Image to registry.
+        """)
+
+
+@implementer(IOCIRecipeBuildJob)
+class OCIRecipeBuildJob(StormBase):
+    """See `IOCIRecipeBuildJob`."""
+
+    __storm_table__ = 'OCIRecipeBuildJob'
+
+    job_id = Int(name='job', primary=True, allow_none=False)
+    job = Reference(job_id, 'Job.id')
+
+    build_id = Int(name='build', allow_none=False)
+    build = Reference(build_id, 'OCIRecipeBuild.id')
+
+    job_type = EnumCol(enum=OCIRecipeBuildJobType, notNull=True)
+
+    metadata = JSON('json_data', allow_none=False)
+
+    def __init__(self, build, job_type, json_data, **job_args):
+        """Constructor.
+
+        Extra keyword arguments are used to construct the underlying Job
+        object.
+
+        :param build: The `IOCIRecipeBuild` this job relates to.
+        :param job_type: The `OCIRecipeBuildJobType` of this job.
+        :param json_data: The type-specific variables, as a JSON-compatible
+            dict.
+        """
+        super(OCIRecipeBuildJob, self).__init__()
+        self.job = Job(**job_args)
+        self.build = build
+        self.job_type = job_type
+        self.json_data = json_data
+
+    def makeDerived(self):
+        return OCIRecipeBuildJob.makeSubclass(self)
+
+
+@delegate_to(IOCIRecipeBuildJob)
+class OCIRecipeBuildJobDerived(BaseRunnableJob):
+
+    __metaclass__ = EnumeratedSubclass
+
+    def __init__(self, oci_build_job):
+        self.context = oci_build_job
+
+    def __repr__(self):
+        """An informative representation of the job."""
+        return "<%s for %s>" % (
+            self.__class__.__name__, self.build.id)
+
+    @classmethod
+    def get(cls, job_id):
+        """Get a job by id.
+
+        :return: The `OCIBuildJob` with the specified id, as the current
+            `OCIBuildJobDerived` subclass.
+        :raises: `NotFoundError` if there is no job with the specified id,
+            or its `job_type` does not match the desired subclass.
+        """
+        oci_build_job = IStore(OCIRecipeBuildJob).get(
+            OCIRecipeBuildJob, job_id)
+        if oci_build_job.job_type != cls.class_job_type:
+            raise NotFoundError(
+                "No object found with id %d and type %s" %
+                (job_id, cls.class_job_type.title))
+        return cls(oci_build_job)
+
+    @classmethod
+    def iterReady(cls):
+        """See `IJobSource`."""
+        jobs = IMasterStore(OCIRecipeBuildJob).find(
+            OCIRecipeBuildJob,
+            OCIRecipeBuildJob.job_type == cls.class_job_type,
+            OCIRecipeBuildJob.job == Job.id,
+            Job.id.is_in(Job.ready_jobs))
+        return (cls(job) for job in jobs)
+
+    def getOopsVars(self):
+        """See `IRunnableJob`."""
+        oops_vars = super(OCIRecipeBuildJobDerived, self).getOopsVars()
+        oops_vars.extend([
+            ('job_id', self.context.job.id),
+            ('job_type', self.context.job_type.title),
+            ('build_id', self.context.ocibuild.id),
+            ])
+        return oops_vars
diff --git a/lib/lp/oci/tests/test_ocirecipebuildjob.py b/lib/lp/oci/tests/test_ocirecipebuildjob.py
new file mode 100644
index 0000000..2501f0a
--- /dev/null
+++ b/lib/lp/oci/tests/test_ocirecipebuildjob.py
@@ -0,0 +1,29 @@
+# Copyright 2019 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""OCIRecipeBuildJob tests"""
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+__metaclass__ = type
+
+
+from lp.oci.interfaces.ocirecipebuildjob import IOCIRecipeBuildJob
+from lp.oci.model.ocirecipebuildjob import (
+    OCIRecipeBuildJob,
+    OCIRecipeBuildJobType,
+    )
+from lp.testing import TestCaseWithFactory
+from lp.testing.layers import DatabaseFunctionalLayer
+
+
+class TestOCIRecipeBuildJob(TestCaseWithFactory):
+
+    layer = DatabaseFunctionalLayer
+
+    def test_provides_interface(self):
+        oci_build = self.factory.makeOCIRecipeBuild()
+        self.assertProvides(
+            OCIRecipeBuildJob(
+                oci_build, OCIRecipeBuildJobType.REGISTRY_UPLOAD, {}),
+            IOCIRecipeBuildJob)

References