launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #31672
[Merge] ~ruinedyourlife/launchpad:expose-git-path-and-git-repository-url into launchpad:master
Quentin Debhi has proposed merging ~ruinedyourlife/launchpad:expose-git-path-and-git-repository-url into launchpad:master with ~ruinedyourlife/launchpad:fetch-service-configuration-for-craft-builds as a prerequisite.
Commit message:
Expose git_path & git_repository_url
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~ruinedyourlife/launchpad/+git/launchpad/+merge/474651
--
Your team Launchpad code reviewers is requested to review the proposed merge of ~ruinedyourlife/launchpad:expose-git-path-and-git-repository-url into launchpad:master.
diff --git a/lib/lp/crafts/interfaces/craftrecipe.py b/lib/lp/crafts/interfaces/craftrecipe.py
index ef8e752..401ecfb 100644
--- a/lib/lp/crafts/interfaces/craftrecipe.py
+++ b/lib/lp/crafts/interfaces/craftrecipe.py
@@ -74,7 +74,7 @@ from lp.code.interfaces.gitref import IGitRef
from lp.code.interfaces.gitrepository import IGitRepository
from lp.registry.interfaces.person import IPerson
from lp.registry.interfaces.product import IProduct
-from lp.services.fields import PersonChoice, PublicPersonChoice
+from lp.services.fields import PersonChoice, PublicPersonChoice, URIField
from lp.snappy.validators.channels import channels_validator
CRAFT_RECIPE_ALLOW_CREATE = "craft.recipe.create.enabled"
@@ -559,24 +559,48 @@ class ICraftRecipeEditableAttributes(Interface):
)
)
- git_repository = ReferenceChoice(
- title=_("Git repository"),
- schema=IGitRepository,
- vocabulary="GitRepository",
- required=False,
- readonly=True,
- description=_(
- "A Git repository with a branch containing a craft.yaml recipe."
- ),
+ git_repository = exported(
+ ReferenceChoice(
+ title=_("Git repository"),
+ schema=IGitRepository,
+ vocabulary="GitRepository",
+ required=False,
+ readonly=True,
+ description=_(
+ "A Git repository with a branch containing a sourcecraft.yaml "
+ "recipe."
+ ),
+ )
)
- git_path = TextLine(
- title=_("Git branch path"),
- required=False,
- readonly=True,
- description=_(
- "The path of the Git branch containing a craft.yaml recipe."
- ),
+ git_path = exported(
+ TextLine(
+ title=_("Git branch path"),
+ required=False,
+ readonly=True,
+ description=_(
+ "The path of the Git branch containing a sourcecraft.yaml "
+ "recipe."
+ ),
+ )
+ )
+
+ git_repository_url = exported(
+ URIField(
+ title=_("Git repository URL"),
+ required=False,
+ readonly=True,
+ description=_(
+ "The URL of a Git repository with a branch containing a "
+ "sourcecraft.yaml at the top level."
+ ),
+ allowed_schemes=["git", "http", "https"],
+ allow_userinfo=True,
+ allow_port=True,
+ allow_query=False,
+ allow_fragment=False,
+ trailing_slash=False,
+ )
)
git_ref = exported(
@@ -776,6 +800,9 @@ class ICraftRecipeSet(Interface):
project,
name,
description=None,
+ git_repository=None,
+ git_repository_url=None,
+ git_path=None,
git_ref=None,
build_path=None,
require_virtualized=True,
diff --git a/lib/lp/crafts/model/craftrecipe.py b/lib/lp/crafts/model/craftrecipe.py
index aea0593..1693b16 100644
--- a/lib/lp/crafts/model/craftrecipe.py
+++ b/lib/lp/crafts/model/craftrecipe.py
@@ -38,6 +38,7 @@ from lp.app.enums import (
PUBLIC_INFORMATION_TYPES,
InformationType,
)
+from lp.app.errors import IncompatibleArguments
from lp.buildmaster.enums import BuildStatus
from lp.buildmaster.interfaces.buildqueue import IBuildQueueSet
from lp.buildmaster.model.builder import Builder
@@ -48,7 +49,7 @@ from lp.code.interfaces.gitcollection import (
IAllGitRepositories,
IGitCollection,
)
-from lp.code.interfaces.gitref import IGitRef
+from lp.code.interfaces.gitref import IGitRef, IGitRefRemoteSet
from lp.code.interfaces.gitrepository import IGitRepository
from lp.code.model.gitcollection import GenericGitCollection
from lp.code.model.gitref import GitRef
@@ -172,6 +173,8 @@ class CraftRecipe(StormBase):
git_path = Unicode(name="git_path", allow_none=True)
+ git_repository_url = Unicode(name="git_repository_url", allow_none=True)
+
build_path = Unicode(name="build_path", allow_none=True)
require_virtualized = Bool(name="require_virtualized")
@@ -269,6 +272,10 @@ class CraftRecipe(StormBase):
def _git_ref(self):
if self.git_repository is not None:
return self.git_repository.getRefByPath(self.git_path)
+ elif self.git_repository_url is not None:
+ return getUtility(IGitRefRemoteSet).new(
+ self.git_repository_url, self.git_path
+ )
else:
return None
@@ -282,9 +289,11 @@ class CraftRecipe(StormBase):
"""See `ICraftRecipe`."""
if value is not None:
self.git_repository = value.repository
+ self.git_repository_url = value.repository_url
self.git_path = value.path
else:
self.git_repository = None
+ self.git_repository_url = None
self.git_path = None
get_property_cache(self)._git_ref = value
@@ -663,6 +672,9 @@ class CraftRecipeSet:
project,
name,
description=None,
+ git_repository=None,
+ git_repository_url=None,
+ git_path=None,
git_ref=None,
build_path=None,
require_virtualized=True,
@@ -689,6 +701,34 @@ class CraftRecipeSet:
% (registrant.displayname, owner.displayname)
)
+ if (
+ sum(
+ [
+ git_repository is not None,
+ git_repository_url is not None,
+ git_ref is not None,
+ ]
+ )
+ > 1
+ ):
+ raise IncompatibleArguments(
+ "You cannot specify more than one of 'git_repository', "
+ "'git_repository_url', and 'git_ref'."
+ )
+ if (git_repository is None and git_repository_url is None) != (
+ git_path is None
+ ):
+ raise IncompatibleArguments(
+ "You must specify both or neither of "
+ "'git_repository'/'git_repository_url' and 'git_path'."
+ )
+ if git_repository is not None:
+ git_ref = git_repository.getRefByPath(git_path)
+ elif git_repository_url is not None:
+ git_ref = getUtility(IGitRefRemoteSet).new(
+ git_repository_url, git_path
+ )
+
if git_ref is None:
raise NoSourceForCraftRecipe
if self.exists(owner, project, name):
@@ -788,7 +828,12 @@ class CraftRecipeSet:
git_collection = removeSecurityProxy(getUtility(IAllGitRepositories))
git_recipes = _getRecipes(git_collection)
- return git_recipes
+ git_url_recipes = IStore(CraftRecipe).find(
+ CraftRecipe,
+ CraftRecipe.owner == person,
+ CraftRecipe.git_repository_url != None,
+ )
+ return git_recipes.union(git_url_recipes)
def findByProject(self, project, visible_by_user=None):
"""See `ICraftRecipeSet`."""
diff --git a/lib/lp/crafts/model/craftrecipebuildbehaviour.py b/lib/lp/crafts/model/craftrecipebuildbehaviour.py
index 5a328ad..bfcd9ff 100644
--- a/lib/lp/crafts/model/craftrecipebuildbehaviour.py
+++ b/lib/lp/crafts/model/craftrecipebuildbehaviour.py
@@ -96,7 +96,12 @@ class CraftRecipeBuildBehaviour(BuilderProxyMixin, BuildFarmJobBehaviourBase):
if build.recipe.build_path is not None:
args["build_path"] = build.recipe.build_path
if build.recipe.git_ref is not None:
- args["git_repository"] = build.recipe.git_repository.git_https_url
+ if build.recipe.git_ref.repository_url is not None:
+ args["git_repository"] = build.recipe.git_ref.repository_url
+ else:
+ args["git_repository"] = (
+ build.recipe.git_repository.git_https_url
+ )
# "git clone -b" doesn't accept full ref names. If this becomes
# a problem then we could change launchpad-buildd to do "git
# clone" followed by "git checkout" instead.
diff --git a/lib/lp/crafts/tests/test_craftrecipe.py b/lib/lp/crafts/tests/test_craftrecipe.py
index b5c2903..7425438 100644
--- a/lib/lp/crafts/tests/test_craftrecipe.py
+++ b/lib/lp/crafts/tests/test_craftrecipe.py
@@ -626,6 +626,17 @@ class TestCraftRecipeSet(TestCaseWithFactory):
self.assertEqual([], recipe.store_channels)
self.assertFalse(recipe.use_fetch_service)
+ def test_creation_git_url(self):
+ # A craft recipe can be backed directly by a URL for an external Git
+ # repository, rather than a Git repository hosted in Launchpad.
+ ref = self.factory.makeGitRefRemote()
+ components = self.makeCraftRecipeComponents(git_ref=ref)
+ craft_recipe = getUtility(ICraftRecipeSet).new(**components)
+ self.assertIsNone(craft_recipe.git_repository)
+ self.assertEqual(ref.repository_url, craft_recipe.git_repository_url)
+ self.assertEqual(ref.path, craft_recipe.git_path)
+ self.assertEqual(ref, craft_recipe.git_ref)
+
def test_creation_no_source(self):
# Attempting to create a craft recipe without a Git repository
# fails.
diff --git a/lib/lp/crafts/tests/test_craftrecipebuildbehaviour.py b/lib/lp/crafts/tests/test_craftrecipebuildbehaviour.py
index 62bdbfd..83ebd8f 100644
--- a/lib/lp/crafts/tests/test_craftrecipebuildbehaviour.py
+++ b/lib/lp/crafts/tests/test_craftrecipebuildbehaviour.py
@@ -459,6 +459,51 @@ class TestAsyncCraftRecipeBuildBehaviour(
self.assertTrue(args["private"])
@defer.inlineCallbacks
+ def test_extraBuildArgs_git_url(self):
+ # extraBuildArgs returns appropriate arguments if asked to build a
+ # job for a Git branch backed by a URL for an external repository.
+ url = "https://git.example.org/foo"
+ ref = self.factory.makeGitRefRemote(
+ repository_url=url, path="refs/heads/master"
+ )
+ job = self.makeJob(git_ref=ref)
+ (
+ expected_archives,
+ expected_trusted_keys,
+ ) = yield get_sources_list_for_building(
+ job, job.build.distro_arch_series, None
+ )
+ for archive_line in expected_archives:
+ self.assertIn("universe", archive_line)
+ with dbuser(config.builddmaster.dbuser):
+ args = yield job.extraBuildArgs()
+ self.assertThat(
+ args,
+ MatchesDict(
+ {
+ "archive_private": Is(False),
+ "archives": Equals(expected_archives),
+ "arch_tag": Equals("i386"),
+ "build_url": Equals(canonical_url(job.build)),
+ "builder_constraints": Equals([]),
+ "channels": Equals({}),
+ "fast_cleanup": Is(True),
+ "git_repository": Equals(url),
+ "git_path": Equals("master"),
+ "name": Equals("test-craft"),
+ "private": Is(False),
+ "proxy_url": ProxyURLMatcher(job, self.now),
+ "revocation_endpoint": RevocationEndpointMatcher(
+ job, self.now
+ ),
+ "series": Equals("unstable"),
+ "trusted_keys": Equals(expected_trusted_keys),
+ "use_fetch_service": Is(False),
+ }
+ ),
+ )
+
+ @defer.inlineCallbacks
def test_composeBuildRequest_proxy_url_set(self):
job = self.makeJob()
build_request = yield job.composeBuildRequest(None)