Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Environment hiding #55

Merged
merged 3 commits into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type Environment {
packages: [Package!]!
state: State
tags: [String!]!
hidden: Boolean!
requested: DateTime
buildStart: DateTime
buildDone: DateTime
Expand Down Expand Up @@ -72,6 +73,12 @@ type Group {
name: String!
}

union HiddenResponse = HiddenSuccess | InvalidInputError | EnvironmentNotFoundError

type HiddenSuccess implements Success {
message: String!
}

type InvalidInputError implements Error {
message: String!
}
Expand All @@ -95,6 +102,7 @@ type SchemaMutation {
createEnvironment(env: EnvironmentInput!): CreateResponse!
deleteEnvironment(name: String!, path: String!): DeleteResponse!
addTag(name: String!, path: String!, tag: String!): AddTagResponse!
setHidden(name: String!, path: String!, hidden: Boolean!): HiddenResponse!
createFromModule(file: Upload!, modulePath: String!, environmentPath: String!): CreateResponse!
updateFromModule(file: Upload!, modulePath: String!, environmentPath: String!): UpdateResponse!
}
Expand Down
19 changes: 17 additions & 2 deletions softpack_core/artifacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,12 +156,27 @@ def spec(self) -> Box:
map(lambda p: Package.from_name(p), info.packages)
)

metadata = self.metadata()

info["tags"] = getattr(metadata, "tags", [])
info["hidden"] = getattr(metadata, "hidden", False)
info["force_hidden"] = getattr(metadata, "force_hidden", False)

return info

def metadata(self) -> Box:
"""Returns the metadata for an Environment.

Should contain keys:
- tags: list[string]
- hidden: boolean
- force_hidden: boolean
"""
meta = Box()
if Artifacts.meta_file in self.obj:
meta = Box.from_yaml(self.obj[Artifacts.meta_file].data)
info["tags"] = getattr(meta, "tags", [])

return info
return meta

def __iter__(self) -> Iterator["Artifacts.Object"]:
"""A generator for returning items under an artifacts.
Expand Down
80 changes: 76 additions & 4 deletions softpack_core/schemas/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import starlette.datastructures
import strawberry
import yaml
from box import Box
from fastapi import UploadFile
from strawberry.file_uploads import Upload

Expand Down Expand Up @@ -57,6 +58,11 @@ class AddTagSuccess(Success):
"""Successfully added tag to environment."""


@strawberry.type
class HiddenSuccess(Success):
"""Successfully set hidden status on environment."""


@strawberry.type
class DeleteEnvironmentSuccess(Success):
"""Environment successfully deleted."""
Expand Down Expand Up @@ -124,6 +130,15 @@ class BuilderError(Error):
],
)

HiddenResponse = strawberry.union(
"HiddenResponse",
[
HiddenSuccess,
InvalidInputError,
EnvironmentNotFoundError,
],
)

DeleteResponse = strawberry.union(
"DeleteResponse",
[
Expand Down Expand Up @@ -306,6 +321,7 @@ class Environment:
packages: list[Package]
state: Optional[State]
tags: list[str]
hidden: bool

requested: Optional[datetime.datetime] = None
build_start: Optional[datetime.datetime] = None
Expand Down Expand Up @@ -364,6 +380,9 @@ def from_artifact(cls, obj: Artifacts.Object) -> Optional["Environment"]:
"""
try:
spec = obj.spec()
if spec.force_hidden:
return None

return Environment(
id=obj.oid,
name=obj.name,
Expand All @@ -374,6 +393,7 @@ def from_artifact(cls, obj: Artifacts.Object) -> Optional["Environment"]:
readme=spec.get("readme", ""),
type=spec.get("type", ""),
tags=spec.tags,
hidden=spec.hidden,
)
except KeyError:
return None
Expand Down Expand Up @@ -586,12 +606,63 @@ def add_tag(
return AddTagSuccess(message="Tag already present")
tags.add(tag)

metadata = yaml.dump({"tags": sorted(tags)})
metadata = cls.read_metadata(path, name)
metadata.tags = sorted(tags)

cls.store_metadata(environment_path, metadata)

return AddTagSuccess(message="Tag successfully added")

@classmethod
def read_metadata(cls, path: str, name: str) -> Box:
"""Read an environments metadata.

This method returns the metadata for an environment with the given
path and name.
"""
arts = artifacts.get(Path(path), name)

if arts is not None:
return arts.metadata()

return Box()

@classmethod
def store_metadata(cls, environment_path: Path, metadata: Box) -> None:
"""Store an environments metadata.

This method writes the given metadata to the repo for the
environment path given.
"""
tree_oid = artifacts.create_file(
environment_path, artifacts.meta_file, metadata, overwrite=True
environment_path,
artifacts.meta_file,
metadata.to_yaml(),
overwrite=True,
)
artifacts.commit_and_push(tree_oid, "create environment folder")
return AddTagSuccess(message="Tag successfully added")

artifacts.commit_and_push(tree_oid, "update metadata")

@classmethod
def set_hidden(
cls, name: str, path: str, hidden: bool
) -> HiddenResponse: # type: ignore
"""This method sets the hidden status for the given environment."""
environment_path = Path(path, name)
response: Optional[Error] = cls.check_env_exists(environment_path)
if response is not None:
return response

metadata = cls.read_metadata(path, name)

if metadata.get("hidden") == hidden:
return HiddenSuccess(message="Hidden metadata already set")

metadata.hidden = hidden

cls.store_metadata(environment_path, metadata)

return HiddenSuccess(message="Hidden metadata set")

@classmethod
def delete(cls, name: str, path: str) -> DeleteResponse: # type: ignore
Expand Down Expand Up @@ -847,6 +918,7 @@ class Mutation:
createEnvironment: CreateResponse = Environment.create # type: ignore
deleteEnvironment: DeleteResponse = Environment.delete # type: ignore
addTag: AddTagResponse = Environment.add_tag # type: ignore
setHidden: HiddenResponse = Environment.set_hidden # type: ignore
# writeArtifact: WriteArtifactResponse = ( # type: ignore
# Environment.write_artifact
# )
Expand Down
50 changes: 50 additions & 0 deletions tests/integration/test_environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
EnvironmentAlreadyExistsError,
EnvironmentInput,
EnvironmentNotFoundError,
HiddenSuccess,
InvalidInputError,
Package,
State,
Expand Down Expand Up @@ -553,3 +554,52 @@ def test_tagging(httpx_post, testable_env_input: EnvironmentInput) -> None:

example_env = Environment.iter()[0]
assert example_env.tags == ["second test", "test"]


def test_hidden(httpx_post, testable_env_input: EnvironmentInput) -> None:
example_env = Environment.iter()[0]
assert not example_env.hidden
name, path = example_env.name, example_env.path

result = Environment.set_hidden(name, path, True)
assert isinstance(result, HiddenSuccess)
assert result.message == "Hidden metadata set"
example_env = Environment.iter()[0]
assert example_env.hidden

result = Environment.set_hidden(name, path, True)
assert isinstance(result, HiddenSuccess)
assert result.message == "Hidden metadata already set"
example_env = Environment.iter()[0]
assert example_env.hidden

result = Environment.set_hidden(name, path, False)
assert isinstance(result, HiddenSuccess)
assert result.message == "Hidden metadata set"
example_env = Environment.iter()[0]
assert not example_env.hidden

result = Environment.set_hidden(name, path, False)
assert isinstance(result, HiddenSuccess)
assert result.message == "Hidden metadata already set"
example_env = Environment.iter()[0]
assert not example_env.hidden

result = Environment.set_hidden(name, path, True)
assert isinstance(result, HiddenSuccess)
assert result.message == "Hidden metadata set"
example_env = Environment.iter()[0]
assert example_env.hidden


def test_force_hidden(
httpx_post, testable_env_input: EnvironmentInput
) -> None:
first_env = Environment.iter()[0]
metadata = Environment.read_metadata(first_env.path, first_env.name)
metadata.force_hidden = True
Environment.store_metadata(Path(first_env.path, first_env.name), metadata)

new_first = Environment.iter()[0]

assert first_env.path != new_first.path or first_env.name != new_first.name
3 changes: 2 additions & 1 deletion tests/integration/test_groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
"""

import getpass
import os

import ldap

from softpack_core.schemas.groups import Group


def test_groups(mocker) -> None:
username = getpass.getuser()
username = os.environ.get("LDAP_USER", getpass.getuser())
groups = list(Group.from_username(username))
assert len(groups)
assert groups[0].name
Expand Down
53 changes: 0 additions & 53 deletions tests/unit/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,65 +5,12 @@
"""


import time
from multiprocessing import Process
from threading import Thread

import httpx
import pytest
import requests
from fastapi.testclient import TestClient
from typer.testing import CliRunner

from softpack_core.app import app


@pytest.fixture
def client() -> TestClient:
return TestClient(app.router)


class CLI:
def __init__(self):
self.runner = CliRunner()

def invoke(self, *args, **kwargs):
return self.runner.invoke(app.commands, *args, **kwargs)


@pytest.fixture
def cli() -> CLI:
return CLI()


def service_run():
cli = CLI()
cli.invoke(["service", "run"])


@pytest.fixture
def service_factory():
def create_service(module):
service = module(target=service_run, daemon=True)
service.start()
while True:
try:
response = requests.get(app.url())
if response.status_code == httpx.codes.OK:
break
except requests.ConnectionError:
time.sleep(0.1)
continue
return service

return create_service


@pytest.fixture
def service(service_factory):
return service_factory(Process)


@pytest.fixture
def service_thread(service_factory):
return service_factory(Thread)
13 changes: 0 additions & 13 deletions tests/unit/test_graphql.py

This file was deleted.

17 changes: 0 additions & 17 deletions tests/unit/test_service.py

This file was deleted.

Loading