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

Support for deleting deployments #15

Merged
merged 4 commits into from
Feb 21, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
43 changes: 35 additions & 8 deletions backend/app.py → backend/operate/cli.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# ------------------------------------------------------------------------------
#
# Copyright 2023 Valory AG
# Copyright 2024 Valory AG
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand All @@ -17,14 +17,14 @@
#
# ------------------------------------------------------------------------------

"""Operate app entrypoint."""

"""Operate app CLI module."""

import os
import shutil
from pathlib import Path

from aea_ledger_ethereum.ethereum import EthereumCrypto
from clea import command, params, run
from clea import group, params, run
from operate.constants import KEY, KEYS, OPERATE, SERVICES
from operate.http import Resource
from operate.keys import Keys
Expand Down Expand Up @@ -86,15 +86,20 @@ def json(self) -> None:
}


@command(name="operate")
def _operate(
@group(name="operate")
def _operate() -> None:
"""Operate - deploy autonomous services."""


@_operate.command(name="daemon")
def _daemon(
host: Annotated[str, params.String(help="HTTP server host string")] = "localhost",
port: Annotated[int, params.Integer(help="HTTP server port")] = 8000,
home: Annotated[
int, params.Directory(long_flag="--home", help="Home directory")
Path, params.Directory(long_flag="--home", help="Home directory")
] = None,
) -> None:
"""Operate - deploy autonomous services."""
"""Launch operate daemon."""
app = App(home=home)
uvicorn(
app=Starlette(
Expand All @@ -118,6 +123,28 @@ def _operate(
)


@_operate.command(name="prune")
def _prune(
home: Annotated[
Path, params.Directory(long_flag="--home", help="Home directory")
] = None,
) -> None:
"""Delete unused/cached data."""
Comment on lines +126 to +132
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dvilelaf When installing the operate app, we will install operate as a CLI tool as well so that users can run operate prune to delete the cache and unused deployments

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we're requiring users to run a command? Not sure if that bodes well with the idea of the app. If users don't run this command, what will happen? Old deployments will just be there?

app = App(home=home)
for service in app.services.json:
if service["active"]:
continue
try:
shutil.rmtree(app.services.path / service["hash"])
print("Removed service " + service["hash"])
except PermissionError:
print(
"Error removing "
+ service["hash"]
+ " please try with admin previledges"
)


def main() -> None:
"""CLI entry point."""
run(cli=_operate)
Expand Down
12 changes: 4 additions & 8 deletions backend/operate/services/manage.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,9 @@ async def access(
def json(self) -> GetServices:
"""Returns the list of available services."""
data = []
for service in self.path.iterdir():
data.append(Service.load(path=service).json)
for path in self.path.iterdir():
service = Service.load(path=path)
data.append(service.json)
return data

def _stake(self) -> None:
Expand Down Expand Up @@ -337,12 +338,7 @@ def update(self, data: PutServices) -> ServiceType:
reuse_multisig=True,
update_token=old.chain_data["token"],
)

# try:
# shutil.rmtree(old.path)
# except Exception as e:
# print(e)

old.delete({})
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this possible now? What about thr superuser permissions issue?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we get permission error we set service as in-active and it won't show up on the front-end and the user can perform clean up using operate prune

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this command require sudo password? Can I confirm that the superuser permission only happens on Linux?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, this required sudo. it does not happen on mac, we haven't tested on windows so cannot say

return service.json

def delete(self, data: DeleteServicesPayload) -> ServicesType:
Expand Down
25 changes: 23 additions & 2 deletions backend/operate/services/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,13 +364,16 @@ class Service(
service_path: Path
path: Path

_helper: t.Optional[ServiceHelper]

def __init__(
self,
service_path: Path,
phash: str,
keys: KeysType,
ledger: t.Optional[LedgerConfig] = None,
chain_data: t.Optional[ChainData] = None,
active: bool = False,
name: t.Optional[str] = None,
) -> None:
"""Initialize object."""
Expand All @@ -379,10 +382,18 @@ def __init__(
self.keys = keys
self.hash = phash
self.ledger = ledger
self.active = active
self.service_path = service_path
self.helper = ServiceHelper(path=self.service_path)
self.chain_data = chain_data or {}
self.path = self.service_path.parent
self._helper = None

@property
def helper(self) -> ServiceHelper:
"""Get service helper."""
if self._helper is None:
self._helper = ServiceHelper(path=self.service_path)
return t.cast(ServiceHelper, self._helper)

def deployment(self) -> Deployment:
"""Load deployment object for the service."""
Expand Down Expand Up @@ -416,6 +427,7 @@ def json(self) -> ServiceType:
"readme": (
readme.read_text(encoding="utf-8") if readme.exists() else None
),
"active": self.active,
}
)

Expand All @@ -437,6 +449,7 @@ def load(cls, path: Path) -> "Service":
chain_data=config.get("chain_data"),
service_path=Path(config["service_path"]),
name=config["name"],
active=config["active"],
)

@classmethod
Expand Down Expand Up @@ -467,6 +480,7 @@ def new(
ledger=ledger,
service_path=Path(downloaded),
name=name,
active=True,
)
service.store()
return service
Expand All @@ -485,5 +499,12 @@ def __update(self, data: ServiceType) -> ServiceType:

def delete(self, data: DeleteServicePayload) -> DeleteServiceResponse:
"""Delete service."""
shutil.rmtree(self.path)
try:
shutil.rmtree(self.path)
except (PermissionError, OSError):
# If we get permission error we set the service in-active
# user can then run operate prune with admin priviledges
# to clean up the cache/unused data
self.active = False
self.store()
return DeleteServiceResponse({})
2 changes: 2 additions & 0 deletions backend/operate/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ class ServiceType(TypedDict):
readme: NotRequired[str]
ledger: NotRequired[LedgerConfig]
chain_data: NotRequired[ChainData]
active: bool


ServicesType = t.List[ServiceType]
Expand Down Expand Up @@ -245,5 +246,6 @@ class DeployedNodes(TypedDict):
class DeploymentType(TypedDict):
"""Deployment type."""

version: int
status: Status
nodes: DeployedNodes
3 changes: 3 additions & 0 deletions backend/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ description = ""
authors = ["David Vilela <[email protected]>"]
readme = "README.md"

[tool.poetry.scripts]
operate = "operate.cli:main"

[tool.poetry.dependencies]
python = "^3.10"
open-autonomy = "^0.14.2"
Expand Down
5 changes: 3 additions & 2 deletions frontend/client/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ export type Service = {
hash: string;
keys: Keys;
readme?: string;
ledger?: LedgerConfig;
chain_data?: ChainData;
ledger: LedgerConfig;
chain_data: ChainData;
active: boolean;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@truemiller only display the service with active set to true on the front end

};

export type ServiceTemplate = {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"private": true,
"scripts": {
"build:frontend": "cd frontend && yarn build",
"dev:backend": "cd backend && poetry run python app.py",
"dev:backend": "cd backend && poetry run python operate/cli.py",
"dev:frontend": "cd frontend && yarn dev",
"dev:hardhat": "npx hardhat node",
"install-deps": "yarn && concurrently \"yarn install:backend\" \"yarn install:frontend\" ",
Expand Down
Loading