Skip to content

Commit

Permalink
Add option to specify a branch to serve on (#51)
Browse files Browse the repository at this point in the history
* delay repo clone until we can choose branch

* add usage to README
  • Loading branch information
y-popov authored Apr 4, 2024
1 parent d8e41f3 commit 21cfb11
Show file tree
Hide file tree
Showing 10 changed files with 136 additions and 76 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,20 @@ poetry run strawberry export-schema softpack_core.graphql:GraphQL.schema > schem
[Tox]: https://tox.wiki
[MkDocs]: https://www.mkdocs.org

## Usage

To start a server in production:

```bash
softpack-core service run
```

To run a server to test softpack-web:

```bash
softpack-core service run --branch <any-name>
```

## Credits

This package was created with [Cookiecutter](https://github.com/audreyr/cookiecutter) and the [altaf-ali/cookiecutter-pypackage](https://altaf-ali.github.io/cookiecutter-pypackage) project template.
Expand Down
48 changes: 39 additions & 9 deletions softpack_core/artifacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import itertools
import shutil
import tempfile
from dataclasses import dataclass
from enum import Enum
from pathlib import Path
Expand Down Expand Up @@ -180,7 +181,6 @@ def __init__(self) -> None:
self.ldap = LDAP()
self.settings = app.settings

path = self.settings.artifacts.path.expanduser() / ".git"
credentials = None
try:
credentials = pygit2.UserPass(
Expand All @@ -194,10 +194,24 @@ def __init__(self) -> None:
credentials=credentials
)

branch = self.settings.artifacts.repo.branch
@property
def signature(self) -> pygit2.Signature:
"""Get current pygit2 commit signature: author/committer/timestamp."""
# creating one of these implicitly looks up the current time.
return pygit2.Signature(
self.settings.artifacts.repo.author,
self.settings.artifacts.repo.email,
)

def clone_repo(self, branch: Optional[str] = None) -> None:
"""Clone the specified branch (default main) to path in settings."""
if branch is None:
branch = self.settings.artifacts.repo.branch

if branch is None:
branch = "main"

path = self.settings.artifacts.path.expanduser() / ".git"
if path.is_dir():
shutil.rmtree(path)

Expand All @@ -217,15 +231,28 @@ def __init__(self) -> None:
]
)

@property
def signature(self) -> pygit2.Signature:
"""Get current pygit2 commit signature: author/committer/timestamp."""
# creating one of these implicitly looks up the current time.
return pygit2.Signature(
self.settings.artifacts.repo.author,
self.settings.artifacts.repo.email,
def create_remote_branch(self, branch: str) -> None:
"""Create a branch in remote if it doesn't exist yet."""
temp_dir = tempfile.TemporaryDirectory()

repo = pygit2.clone_repository(
self.settings.artifacts.repo.url,
path=temp_dir.name,
callbacks=self.credentials_callback,
bare=True,
)

try:
repo.branches['origin/' + branch]
except KeyError:
commit = repo.revparse_single('HEAD')
repo.create_branch(branch, commit)

remote = repo.remotes[0]
remote.push(
[f'refs/heads/{branch}'], callbacks=self.credentials_callback
)

def user_folder(self, user: Optional[str] = None) -> Path:
"""Get the user folder for a given user.
Expand Down Expand Up @@ -511,3 +538,6 @@ def delete_environment(
new_tree = tree_builder.write()

return self.build_tree(self.repo, root_tree, new_tree, full_path)


artifacts = Artifacts()
49 changes: 23 additions & 26 deletions softpack_core/schemas/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from strawberry.file_uploads import Upload

from softpack_core.app import app
from softpack_core.artifacts import Artifacts, Package, State, Type
from softpack_core.artifacts import Artifacts, Package, State, Type, artifacts
from softpack_core.module import GenerateEnvReadme, ToSoftpackYML
from softpack_core.schemas.base import BaseSchema

Expand Down Expand Up @@ -306,7 +306,6 @@ class Environment:
packages: list[Package]
state: Optional[State]
tags: list[str]
artifacts = Artifacts()

requested: Optional[datetime.datetime] = None
build_start: Optional[datetime.datetime] = None
Expand Down Expand Up @@ -337,7 +336,7 @@ def iter(cls) -> list["Environment"]:
except statistics.StatisticsError:
avg_wait_secs = None

environment_folders = cls.artifacts.iter()
environment_folders = artifacts.iter()
environment_objects = list(
filter(None, map(cls.from_artifact, environment_folders))
)
Expand Down Expand Up @@ -488,7 +487,7 @@ def create_new_env(
return input_err

# Check if an env with same name already exists at given path
if cls.artifacts.get(Path(env.path), env.name):
if artifacts.get(Path(env.path), env.name):
return EnvironmentAlreadyExistsError(
message="This name is already used in this location",
path=env.path,
Expand All @@ -510,21 +509,19 @@ def create_new_env(
meta = dict(tags=sorted(set(env.tags or [])))
metaData = yaml.dump(meta)

tree_oid = cls.artifacts.create_files(
tree_oid = artifacts.create_files(
new_folder_path,
[
(env_type, ""), # e.g. .built_by_softpack
(
cls.artifacts.environments_file,
artifacts.environments_file,
definitionData,
), # softpack.yml
(cls.artifacts.meta_file, metaData),
(artifacts.meta_file, metaData),
],
True,
)
cls.artifacts.commit_and_push(
tree_oid, "create environment folder"
)
artifacts.commit_and_push(tree_oid, "create environment folder")
except RuntimeError as e:
return InvalidInputError(
message="".join(format_exception_only(type(e), e))
Expand All @@ -546,7 +543,7 @@ def check_env_exists(
Returns:
Union[None, EnvironmentNotFoundError]: an error if env not found.
"""
if cls.artifacts.get(path.parent, path.name):
if artifacts.get(path.parent, path.name):
return None

return EnvironmentNotFoundError(
Expand Down Expand Up @@ -580,7 +577,7 @@ def add_tag(
if (response := validate_tag(tag)) is not None:
return response

tree = cls.artifacts.get(Path(path), name)
tree = artifacts.get(Path(path), name)
if tree is None:
return EnvironmentNotFoundError(path=path, name=name)
box = tree.spec()
Expand All @@ -590,10 +587,10 @@ def add_tag(
tags.add(tag)

metadata = yaml.dump({"tags": sorted(tags)})
tree_oid = cls.artifacts.create_file(
environment_path, cls.artifacts.meta_file, metadata, overwrite=True
tree_oid = artifacts.create_file(
environment_path, artifacts.meta_file, metadata, overwrite=True
)
cls.artifacts.commit_and_push(tree_oid, "create environment folder")
artifacts.commit_and_push(tree_oid, "create environment folder")
return AddTagSuccess(message="Tag successfully added")

@classmethod
Expand All @@ -607,9 +604,9 @@ def delete(cls, name: str, path: str) -> DeleteResponse: # type: ignore
Returns:
A message confirming the success or failure of the operation.
"""
if cls.artifacts.get(Path(path), name):
tree_oid = cls.artifacts.delete_environment(name, path)
cls.artifacts.commit_and_push(tree_oid, "delete environment")
if artifacts.get(Path(path), name):
tree_oid = artifacts.delete_environment(name, path)
artifacts.commit_and_push(tree_oid, "delete environment")
return DeleteEnvironmentSuccess(
message="Successfully deleted the environment"
)
Expand Down Expand Up @@ -690,13 +687,13 @@ async def convert_module_file_to_artifacts(
readme = GenerateEnvReadme(module_path)

module_file = UploadFile(
filename=cls.artifacts.module_file, file=io.BytesIO(contents)
filename=artifacts.module_file, file=io.BytesIO(contents)
)
softpack_file = UploadFile(
filename=cls.artifacts.environments_file, file=io.BytesIO(yml)
filename=artifacts.environments_file, file=io.BytesIO(yml)
)
readme_file = UploadFile(
filename=cls.artifacts.readme_file, file=io.BytesIO(readme)
filename=artifacts.readme_file, file=io.BytesIO(readme)
)

return await cls.write_module_artifacts(
Expand Down Expand Up @@ -729,9 +726,9 @@ async def write_module_artifacts(
WriteArtifactResponse: contains message and commit hash of
softpack.yml upload.
"""
module_file.name = cls.artifacts.module_file
readme_file.name = cls.artifacts.readme_file
softpack_file.name = cls.artifacts.environments_file
module_file.name = artifacts.module_file
readme_file.name = artifacts.readme_file
softpack_file.name = artifacts.environments_file

return await cls.write_artifacts(
folder_path=environment_path,
Expand Down Expand Up @@ -775,10 +772,10 @@ async def write_artifacts(
(file.name, cast(str, (await file.read()).decode()))
)

tree_oid = cls.artifacts.create_files(
tree_oid = artifacts.create_files(
Path(folder_path), new_files, overwrite=True
)
cls.artifacts.commit_and_push(tree_oid, "write artifact")
artifacts.commit_and_push(tree_oid, "write artifact")
return WriteArtifactSuccess(
message="Successfully written artifact(s)",
)
Expand Down
21 changes: 18 additions & 3 deletions softpack_core/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from typer import Typer
from typing_extensions import Annotated

from softpack_core.artifacts import State
from softpack_core.artifacts import State, artifacts
from softpack_core.schemas.environment import (
CreateEnvironmentSuccess,
Environment,
Expand Down Expand Up @@ -43,16 +43,31 @@ def run(
"--reload",
help="Automatically reload when changes are detected.",
),
] = False
] = False,
branch: Annotated[
str,
typer.Option(
"--branch",
help="Create and use this branch of Artefacts repo.",
),
] = 'main',
) -> None:
"""Start the SoftPack Core REST API service.
Args:
reload: Enable auto-reload.
branch: branch to use
Returns:
None.
"""
if branch != 'main':
print(f'Changing branch to {branch}')
# FIXME do only when branch does not exist
artifacts.create_remote_branch(branch)

artifacts.clone_repo(branch=branch)

uvicorn.run(
"softpack_core.app:app.router",
host=app.settings.server.host,
Expand Down Expand Up @@ -83,7 +98,7 @@ async def upload_artifacts( # type: ignore[no-untyped-def]
if Environment.check_env_exists(Path(env_path)) is not None:
create_response = Environment.create_new_env(
EnvironmentInput.from_path(env_path),
Environment.artifacts.built_by_softpack_file,
artifacts.built_by_softpack_file,
)

if not isinstance(create_response, CreateEnvironmentSuccess):
Expand Down
4 changes: 2 additions & 2 deletions tests/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import pytest

from softpack_core.artifacts import Artifacts, Package, app
from softpack_core.schemas.environment import Environment, EnvironmentInput
from softpack_core.schemas.environment import EnvironmentInput
from tests.integration.utils import (
get_user_path_without_environments,
new_test_artifacts,
Expand Down Expand Up @@ -48,7 +48,7 @@ def testable_env_input(mocker) -> EnvironmentInput:
artifacts: Artifacts = ad["artifacts"]
user = ad["test_user"]

mocker.patch.object(Environment, 'artifacts', new=artifacts)
# mocker.patch.object(Environment, 'artifacts', new=artifacts)

testable_env_input = EnvironmentInput(
name="test_env_create",
Expand Down
2 changes: 2 additions & 0 deletions tests/integration/test_artifacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def test_clone() -> None:
assert os.path.isdir(path) is False

artifacts = Artifacts()
artifacts.clone_repo()
assert os.path.isdir(path) is True

orig_repo_path = app.settings.artifacts.path
Expand All @@ -48,6 +49,7 @@ def test_clone() -> None:

app.settings.artifacts.path = orig_repo_path
artifacts = Artifacts()
artifacts.clone_repo()

assert file_in_repo(artifacts, file_path)

Expand Down
11 changes: 6 additions & 5 deletions tests/integration/test_builderupload.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from fastapi.testclient import TestClient

from softpack_core.app import app
from softpack_core.artifacts import artifacts
from softpack_core.schemas.environment import Environment
from softpack_core.service import ServiceAPI
from tests.integration.utils import file_in_repo
Expand Down Expand Up @@ -43,15 +44,15 @@ def test_builder_upload(testable_env_input):
assert resp.json().get("message") == "Successfully written artifact(s)"
assert Environment.check_env_exists(Path(env_path)) is None
assert file_in_repo(
Environment.artifacts,
Path(Environment.artifacts.environments_root, env_path, softpackYaml),
artifacts,
Path(artifacts.environments_root, env_path, softpackYaml),
)
assert file_in_repo(
Environment.artifacts,
Path(Environment.artifacts.environments_root, env_path, spackLock),
artifacts,
Path(artifacts.environments_root, env_path, spackLock),
)

tree = Environment.artifacts.get(env_parent, env_name)
tree = artifacts.get(env_parent, env_name)
assert tree is not None

assert tree.get(softpackYaml).data == softpackYamlContents
Expand Down
Loading

0 comments on commit 21cfb11

Please sign in to comment.