← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~twom/launchpad:oci-set-distribution-credentials-via-api into launchpad:master

 

Tom Wardill has proposed merging ~twom/launchpad:oci-set-distribution-credentials-via-api into launchpad:master.

Commit message:
Allow setting oci registry credentials on a distribution via the API

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~twom/launchpad/+git/launchpad/+merge/402084
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~twom/launchpad:oci-set-distribution-credentials-via-api into launchpad:master.
diff --git a/lib/lp/registry/browser/distribution.py b/lib/lp/registry/browser/distribution.py
index e5777ec..aa8a74c 100644
--- a/lib/lp/registry/browser/distribution.py
+++ b/lib/lp/registry/browser/distribution.py
@@ -82,9 +82,6 @@ from lp.bugs.browser.structuralsubscription import (
     )
 from lp.buildmaster.interfaces.processor import IProcessorSet
 from lp.code.browser.vcslisting import TargetDefaultVCSNavigationMixin
-from lp.oci.interfaces.ociregistrycredentials import (
-    IOCIRegistryCredentialsSet,
-    )
 from lp.registry.browser import (
     add_subscribe_link,
     RegistryEditFormView,
diff --git a/lib/lp/registry/interfaces/distribution.py b/lib/lp/registry/interfaces/distribution.py
index 5ec5271..8f43f58 100644
--- a/lib/lp/registry/interfaces/distribution.py
+++ b/lib/lp/registry/interfaces/distribution.py
@@ -14,15 +14,18 @@ __all__ = [
     'IDistributionSet',
     'NoPartnerArchive',
     'NoSuchDistribution',
+    'NoOCIAdminForDistribution',
     ]
 
 from lazr.lifecycle.snapshot import doNotSnapshot
 from lazr.restful.declarations import (
     call_with,
     collection_default_content,
+    error_status,
     export_factory_operation,
     export_operation_as,
     export_read_operation,
+    export_write_operation,
     exported,
     exported_as_webservice_collection,
     exported_as_webservice_entry,
@@ -38,6 +41,7 @@ from lazr.restful.fields import (
     Reference,
     )
 from lazr.restful.interface import copy_field
+from six.moves import http_client
 from zope.interface import (
     Attribute,
     Interface,
@@ -113,6 +117,15 @@ from lp.translations.interfaces.hastranslationimports import (
 from lp.translations.interfaces.translationpolicy import ITranslationPolicy
 
 
+@error_status(http_client.BAD_REQUEST)
+class NoOCIAdminForDistribution(Exception):
+    """There is no OCI Project Admin for this distribution."""
+
+    def __init__(self):
+        super(NoOCIAdminForDistribution, self).__init__(
+            "There is no OCI Project Admin for this distribution.")
+
+
 class IDistributionMirrorMenuMarker(Interface):
     """Marker interface for Mirror navigation."""
 
@@ -129,6 +142,35 @@ class DistributionNameField(PillarNameField):
 class IDistributionEditRestricted(IOfficialBugTagTargetRestricted):
     """IDistribution properties requiring launchpad.Edit permission."""
 
+    @call_with(registrant=REQUEST_USER)
+    @operation_parameters(
+        registry_url=TextLine(
+            title=_("The registry url."),
+            description=_("The url of the OCI registry to use."),
+            required=True),
+        region=TextLine(
+            title=_("OCI registry region."),
+            description=_("The region of the OCI registry."),
+            required=False),
+        username=TextLine(
+            title=_("Username"),
+            description=_("The username for the OCI registry."),
+            required=False),
+        password=TextLine(
+            title=_("Password"),
+            description=_("The password for the OCI registry."),
+            required=False))
+    @export_write_operation()
+    @operation_for_version("devel")
+    def setOCICredentials(registrant, registry_url, region,
+                          username, password):
+        """Set the credentials for the OCI registry for OCI projects."""
+
+    @export_write_operation()
+    @operation_for_version("devel")
+    def deleteOCICredentials():
+        """Delete any existing OCI credentials for the distribution."""
+
 
 class IDistributionDriverRestricted(Interface):
     """IDistribution properties requiring launchpad.Driver permission."""
@@ -727,7 +769,6 @@ class IDistributionPublic(
                       "images in this distribution to a registry."),
         required=False, readonly=False)
 
-
 @exported_as_webservice_entry(as_of="beta")
 class IDistribution(
     IDistributionEditRestricted, IDistributionPublic, IHasBugSupervisor,
diff --git a/lib/lp/registry/model/distribution.py b/lib/lp/registry/model/distribution.py
index 0288c54..f76d28b 100644
--- a/lib/lp/registry/model/distribution.py
+++ b/lib/lp/registry/model/distribution.py
@@ -89,6 +89,7 @@ from lp.bugs.model.structuralsubscription import (
 from lp.code.interfaces.seriessourcepackagebranch import (
     IFindOfficialBranchLinks,
     )
+from lp.oci.interfaces.ociregistrycredentials import IOCIRegistryCredentialsSet
 from lp.registry.enums import (
     BranchSharingPolicy,
     BugSharingPolicy,
@@ -101,6 +102,7 @@ from lp.registry.interfaces.accesspolicy import IAccessPolicySource
 from lp.registry.interfaces.distribution import (
     IDistribution,
     IDistributionSet,
+    NoOCIAdminForDistribution,
     )
 from lp.registry.interfaces.distributionmirror import (
     IDistributionMirror,
@@ -1531,6 +1533,32 @@ class Distribution(SQLBase, BugTargetBase, MakesAnnouncements,
             pillar=self, registrant=registrant, name=name,
             description=description)
 
+    def setOCICredentials(self, registrant, registry_url,
+                          region, username, password):
+        """See `IDistribution`."""
+        if not self.oci_project_admin:
+            raise NoOCIAdminForDistribution()
+        new_credentials = getUtility(IOCIRegistryCredentialsSet).getOrCreate(
+            registrant,
+            self.oci_project_admin,
+            registry_url,
+            {"username": username, "password": password, "region": region},
+            override_owner=True)
+        old_credentials = self.oci_registry_credentials
+        if self.oci_registry_credentials != new_credentials:
+            # Remove the old credentials as we're assigning new ones
+            # or clearing them
+            self.oci_registry_credentials = new_credentials
+            if old_credentials:
+                old_credentials.destroySelf()
+
+    def deleteOCICredentials(self):
+        """See `IDistribution`."""
+        old_credentials = self.oci_registry_credentials
+        if old_credentials:
+            self.oci_registry_credentials = None
+            old_credentials.destroySelf()
+
 
 @implementer(IDistributionSet)
 class DistributionSet:
diff --git a/lib/lp/registry/tests/test_distribution.py b/lib/lp/registry/tests/test_distribution.py
index 0b9f712..44defc1 100644
--- a/lib/lp/registry/tests/test_distribution.py
+++ b/lib/lp/registry/tests/test_distribution.py
@@ -28,6 +28,7 @@ from lp.app.enums import (
     )
 from lp.app.errors import NotFoundError
 from lp.app.interfaces.launchpad import ILaunchpadCelebrities
+from lp.oci.tests.helpers import OCIConfigHelperMixin
 from lp.registry.enums import (
     BranchSharingPolicy,
     BugSharingPolicy,
@@ -761,7 +762,7 @@ class DistributionOCIProjectAdminPermission(TestCaseWithFactory):
         self.assertTrue(distro.canAdministerOCIProjects(admin))
 
 
-class TestDistributionWebservice(TestCaseWithFactory):
+class TestDistributionWebservice(OCIConfigHelperMixin, TestCaseWithFactory):
     """Test the IDistribution API.
 
     Some tests already exist in xx-distribution.txt.
@@ -842,3 +843,83 @@ class TestDistributionWebservice(TestCaseWithFactory):
             start_date=(now - day).isoformat(),
             end_date=now.isoformat())
         self.assertEqual([], empty_response.jsonBody())
+
+    def test_setOCICredentials(self):
+        # We can add OCI Credentials to the distribution
+        self.setConfig()
+        with person_logged_in(self.person):
+            distro = self.factory.makeDistribution(owner=self.person)
+            distro.oci_project_admin = self.person
+            distro_url = api_url(distro)
+
+        resp = self.webservice.named_post(
+            distro_url,
+            "setOCICredentials",
+            registry_url="http://registry.test";,
+        )
+
+        self.assertEqual(200, resp.status)
+        with person_logged_in(self.person):
+            self.assertEqual(
+                "http://registry.test";,
+                distro.oci_registry_credentials.url
+            )
+
+    def test_setOCICredentials_no_oci_admin(self):
+        # If there's no oci_project_admin to own the credentials, error
+        self.setConfig()
+        with person_logged_in(self.person):
+            distro = self.factory.makeDistribution(owner=self.person)
+            distro_url = api_url(distro)
+
+        resp = self.webservice.named_post(
+            distro_url,
+            "setOCICredentials",
+            registry_url="http://registry.test";,
+        )
+
+        self.assertEqual(400, resp.status)
+        self.assertIn(
+            b"no OCI Project Admin for this distribution",
+            resp.body)
+
+    def test_setOCICredentials_changes_credentials(self):
+        # if we have existing credentials, we should change them
+        self.setConfig()
+        with person_logged_in(self.person):
+            distro = self.factory.makeDistribution(owner=self.person)
+            distro.oci_project_admin = self.person
+            credentials = self.factory.makeOCIRegistryCredentials()
+            distro.oci_registry_credentials = credentials
+            distro_url = api_url(distro)
+
+        resp = self.webservice.named_post(
+            distro_url,
+            "setOCICredentials",
+            registry_url="http://registry.test";,
+        )
+
+        self.assertEqual(200, resp.status)
+        with person_logged_in(self.person):
+            self.assertEqual(
+                "http://registry.test";,
+                distro.oci_registry_credentials.url
+            )
+
+    def test_deleteOCICredentials(self):
+        # We can remove existing credentials
+        self.setConfig()
+        with person_logged_in(self.person):
+            distro = self.factory.makeDistribution(owner=self.person)
+            distro.oci_project_admin = self.person
+            credentials = self.factory.makeOCIRegistryCredentials()
+            distro.oci_registry_credentials = credentials
+            distro_url = api_url(distro)
+
+        resp = self.webservice.named_post(
+            distro_url,
+            "deleteOCICredentials")
+
+        self.assertEqual(200, resp.status)
+        with person_logged_in(self.person):
+            self.assertIsNone(distro.oci_registry_credentials)

Follow ups