From 0f165a7bd901d3bb9d55daea1c3b32de7a4a9cd9 Mon Sep 17 00:00:00 2001 From: Michael Woolnough Date: Mon, 4 Nov 2024 14:30:00 +0000 Subject: [PATCH] Reuse previous version number if the build failed. --- softpack_core/artifacts.py | 2 - softpack_core/schemas/environment.py | 55 ++++++++++++++----- tests/integration/test_environment.py | 78 ++++++++++++++++++++++++++- 3 files changed, 119 insertions(+), 16 deletions(-) diff --git a/softpack_core/artifacts.py b/softpack_core/artifacts.py index 39bd32f..ebbba17 100644 --- a/softpack_core/artifacts.py +++ b/softpack_core/artifacts.py @@ -666,7 +666,6 @@ def delete_environment( Args: name: the name of the environment path: the path of the environment - commit_message: the commit message Returns: the OID of the new tree structure of the repository @@ -682,7 +681,6 @@ def remove(self, full_path: Path, name: str) -> pygit2.Oid: Args: full_path: the parent directory to remove from name: the file/directory to remove from the parent - commit_message: the commit message """ # Get repository tree root_tree = self.repo.head.peel(pygit2.Tree) diff --git a/softpack_core/schemas/environment.py b/softpack_core/schemas/environment.py index 43be38a..6df5924 100644 --- a/softpack_core/schemas/environment.py +++ b/softpack_core/schemas/environment.py @@ -393,6 +393,22 @@ def has_requested_recipes(self) -> bool: """Do any of the requested packages have an unmade recipe.""" return any(pkg.name.startswith("*") for pkg in self.packages) + @classmethod + def get_env(cls, path: Path, name: str) -> Optional["Environment"]: + """Return an Environment object given a path. + + Args: + path: A path. + + Returns: + Environment: An Environment object. + """ + artifact_env = artifacts.get(path, name) + if not artifact_env: + return None + + return cls.from_artifact(artifact_env) + @classmethod def from_artifact(cls, obj: Artifacts.Object) -> Optional["Environment"]: """Create an Environment object from an artifact. @@ -435,27 +451,40 @@ def create(cls, env: EnvironmentInput) -> CreateResponse: # type: ignore Returns: A message confirming the success or failure of the operation. """ - versionless_name = env.name version = 1 - if not versionless_name: + if not env.name: return InvalidInputError( message="environment name must not be blank" ) - while True: - env.name = versionless_name + "-" + str(version) - response = cls.create_new_env( - env, Artifacts.built_by_softpack_file - ) - if isinstance(response, CreateEnvironmentSuccess): - break - - if not isinstance(response, EnvironmentAlreadyExistsError): - return response - + while not isinstance( + cls.check_env_exists( + Path(env.path, env.name + "-" + str(version)) + ), + EnvironmentNotFoundError, + ): version += 1 + if version != 1: + prevEnv = cls.get_env( + Path(env.path), env.name + "-" + str(version - 1) + ) + if cast(Environment, prevEnv).state == State.failed: + version -= 1 + + tree_oid = artifacts.delete_environment( + env.name + "-" + str(version), env.path + ) + artifacts.commit_and_push( + tree_oid, "remove failed environment" + ) + + env.name += "-" + str(version) + response = cls.create_new_env(env, Artifacts.built_by_softpack_file) + if not isinstance(response, CreateEnvironmentSuccess): + return response + response = cls.submit_env_to_builder(env) if response is not None: return response diff --git a/tests/integration/test_environment.py b/tests/integration/test_environment.py index 5181881..b73a1c2 100644 --- a/tests/integration/test_environment.py +++ b/tests/integration/test_environment.py @@ -39,7 +39,10 @@ pytestmark = pytest.mark.repo -def test_create(httpx_post, testable_env_input: EnvironmentInput) -> None: +@pytest.mark.asyncio +async def test_create( + httpx_post, testable_env_input: EnvironmentInput +) -> None: orig_input_name = testable_env_input.name result = Environment.create(testable_env_input) testable_env_input.name = orig_input_name @@ -115,6 +118,79 @@ def test_create(httpx_post, testable_env_input: EnvironmentInput) -> None: ) assert file_in_remote(path) + env = Environment.get_env( + testable_env_input.path, testable_env_input.name + "-2" + ) + assert env.state == State.queued + + upload = UploadFile( + filename=Artifacts.builder_out, + file=io.BytesIO(b""), + ) + + result = await Environment.write_artifact( + file=upload, + folder_path=f"{testable_env_input.path}/{testable_env_input.name}-2", + file_name=upload.filename, + ) + assert isinstance(result, WriteArtifactSuccess) + + env = Environment.get_env( + testable_env_input.path, testable_env_input.name + "-2" + ) + assert env.state == State.failed + + assert isinstance( + Environment.check_env_exists( + Path(testable_env_input.path, testable_env_input.name + "-3") + ), + EnvironmentNotFoundError, + ) + + result = Environment.create(testable_env_input) + assert isinstance(result, CreateEnvironmentSuccess) + assert testable_env_input.name == "test_env_create-2" + testable_env_input.name = orig_input_name + + assert isinstance( + Environment.check_env_exists( + Path(testable_env_input.path, testable_env_input.name + "-3") + ), + EnvironmentNotFoundError, + ) + + env = Environment.get_env( + testable_env_input.path, testable_env_input.name + "-2" + ) + assert env.state == State.queued + + upload = UploadFile( + filename=Artifacts.builder_out, + file=io.BytesIO(b""), + ) + + result = await Environment.write_artifact( + file=upload, + folder_path=f"{testable_env_input.path}/{testable_env_input.name}-2", + file_name=upload.filename, + ) + assert isinstance(result, WriteArtifactSuccess) + + env = Environment.get_env( + testable_env_input.path, testable_env_input.name + "-2" + ) + assert env.state == State.failed + + result = Environment.create(testable_env_input) + assert isinstance(result, CreateEnvironmentSuccess) + assert testable_env_input.name == "test_env_create-2" + testable_env_input.name = orig_input_name + + result = Environment.create(testable_env_input) + assert isinstance(result, CreateEnvironmentSuccess) + assert testable_env_input.name == "test_env_create-3" + testable_env_input.name = orig_input_name + def test_create_no_tags(httpx_post, testable_env_input): testable_env_input.tags = None