oak-cli 0.2.6__tar.gz → 0.2.8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. {oak_cli-0.2.6 → oak_cli-0.2.8}/PKG-INFO +5 -4
  2. {oak_cli-0.2.6 → oak_cli-0.2.8}/README.md +3 -3
  3. {oak_cli-0.2.6 → oak_cli-0.2.8}/oak_cli/args_parser/apps/delete.py +3 -5
  4. {oak_cli-0.2.6 → oak_cli-0.2.8}/oak_cli/args_parser/apps/status.py +1 -3
  5. {oak_cli-0.2.6 → oak_cli-0.2.8}/oak_cli/args_parser/main.py +5 -0
  6. oak_cli-0.2.8/oak_cli/args_parser/plugins/flops/create.py +16 -0
  7. oak_cli-0.2.8/oak_cli/args_parser/plugins/flops/main.py +25 -0
  8. oak_cli-0.2.8/oak_cli/args_parser/plugins/main.py +25 -0
  9. {oak_cli-0.2.6 → oak_cli-0.2.8}/oak_cli/args_parser/services/deployment.py +1 -3
  10. {oak_cli-0.2.6 → oak_cli-0.2.8}/oak_cli/args_parser/services/status.py +1 -4
  11. oak_cli-0.2.8/oak_cli/commands/apps/main.py +81 -0
  12. oak_cli-0.2.8/oak_cli/commands/apps/status.py +11 -0
  13. oak_cli-0.2.8/oak_cli/commands/plugins/flops/flops.SLA.json +13 -0
  14. oak_cli-0.2.8/oak_cli/commands/plugins/flops/main.py +29 -0
  15. oak_cli-0.2.8/oak_cli/commands/services/__init__.py +0 -0
  16. oak_cli-0.2.8/oak_cli/commands/services/deployment.py +39 -0
  17. oak_cli-0.2.8/oak_cli/commands/services/get.py +38 -0
  18. oak_cli-0.2.8/oak_cli/commands/services/status.py +41 -0
  19. oak_cli-0.2.8/oak_cli/main.py +28 -0
  20. oak_cli-0.2.8/oak_cli/utils/SLAs/__init__.py +0 -0
  21. oak_cli-0.2.8/oak_cli/utils/__init__.py +0 -0
  22. oak_cli-0.2.8/oak_cli/utils/api/__init__.py +0 -0
  23. oak_cli-0.2.8/oak_cli/utils/api/common.py +3 -0
  24. oak_cli-0.2.6/oak_cli/utils/api/common.py → oak_cli-0.2.8/oak_cli/utils/api/custom_http.py +0 -5
  25. oak_cli-0.2.8/oak_cli/utils/api/custom_requests.py +94 -0
  26. oak_cli-0.2.8/oak_cli/utils/api/login.py +36 -0
  27. oak_cli-0.2.8/oak_cli/utils/exceptions.py +23 -0
  28. {oak_cli-0.2.6 → oak_cli-0.2.8}/oak_cli/utils/logging.py +0 -1
  29. {oak_cli-0.2.6 → oak_cli-0.2.8}/oak_cli/utils/types.py +0 -2
  30. {oak_cli-0.2.6 → oak_cli-0.2.8}/pyproject.toml +5 -1
  31. oak_cli-0.2.6/oak_cli/commands/apps/main.py +0 -60
  32. oak_cli-0.2.6/oak_cli/commands/apps/status.py +0 -21
  33. oak_cli-0.2.6/oak_cli/commands/services/deployment.py +0 -30
  34. oak_cli-0.2.6/oak_cli/commands/services/get.py +0 -29
  35. oak_cli-0.2.6/oak_cli/commands/services/status.py +0 -75
  36. oak_cli-0.2.6/oak_cli/main.py +0 -14
  37. oak_cli-0.2.6/oak_cli/utils/api/login.py +0 -33
  38. oak_cli-0.2.6/oak_cli/utils/api/main.py +0 -108
  39. {oak_cli-0.2.6 → oak_cli-0.2.8}/oak_cli/__init__.py +0 -0
  40. {oak_cli-0.2.6 → oak_cli-0.2.8}/oak_cli/args_parser/__init__.py +0 -0
  41. {oak_cli-0.2.6 → oak_cli-0.2.8}/oak_cli/args_parser/apps/__init__.py +0 -0
  42. {oak_cli-0.2.6 → oak_cli-0.2.8}/oak_cli/args_parser/apps/create.py +0 -0
  43. {oak_cli-0.2.6 → oak_cli-0.2.8}/oak_cli/args_parser/apps/main.py +0 -0
  44. {oak_cli-0.2.6/oak_cli/args_parser/services → oak_cli-0.2.8/oak_cli/args_parser/plugins}/__init__.py +0 -0
  45. {oak_cli-0.2.6/oak_cli/commands/apps → oak_cli-0.2.8/oak_cli/args_parser/plugins/flops}/__init__.py +0 -0
  46. {oak_cli-0.2.6/oak_cli/commands → oak_cli-0.2.8/oak_cli/args_parser}/services/__init__.py +0 -0
  47. {oak_cli-0.2.6 → oak_cli-0.2.8}/oak_cli/args_parser/services/main.py +0 -0
  48. {oak_cli-0.2.6/oak_cli/utils/SLAs → oak_cli-0.2.8/oak_cli/commands/apps}/__init__.py +0 -0
  49. {oak_cli-0.2.6/oak_cli/utils → oak_cli-0.2.8/oak_cli/commands/plugins}/__init__.py +0 -0
  50. {oak_cli-0.2.6/oak_cli/utils/api → oak_cli-0.2.8/oak_cli/commands/plugins/flops}/__init__.py +0 -0
  51. {oak_cli-0.2.6 → oak_cli-0.2.8}/oak_cli/utils/SLAs/blank_app_without_services.SLA.json +0 -0
  52. {oak_cli-0.2.6 → oak_cli-0.2.8}/oak_cli/utils/SLAs/common.py +0 -0
  53. {oak_cli-0.2.6 → oak_cli-0.2.8}/oak_cli/utils/SLAs/default_app_with_services.SLA.json +0 -0
  54. {oak_cli-0.2.6 → oak_cli-0.2.8}/oak_cli/utils/argcomplete.py +0 -0
  55. {oak_cli-0.2.6 → oak_cli-0.2.8}/oak_cli/utils/common.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: oak_cli
3
- Version: 0.2.6
3
+ Version: 0.2.8
4
4
  Summary:
5
5
  Author: Alexander Malyuk
6
6
  Author-email: malyuk.alexander1999@gmail.com
@@ -10,20 +10,21 @@ Classifier: Programming Language :: Python :: 3.10
10
10
  Classifier: Programming Language :: Python :: 3.11
11
11
  Classifier: Programming Language :: Python :: 3.12
12
12
  Requires-Dist: argcomplete (>=3.2.3,<4.0.0)
13
+ Requires-Dist: icecream (>=2.1.3,<3.0.0)
13
14
  Description-Content-Type: text/markdown
14
15
 
15
16
  # [Oakestra CLI](https://github.com/oakestra/oakestra-cli)
16
17
 
17
18
  **oakestra-cli** is a very basic command line tool for controlling your [Oakestra](https://github.com/oakestra/oakestra) setup.
18
19
 
19
- It is intended to be a interface for the API as well as a development tool.
20
+ It is intended to be an interface for the API as well as a development tool.
20
21
 
21
22
  The CLI supports command (tab) autocompletion.
22
23
 
23
24
  ## Usage
24
25
  The package can be called via `oak`.
25
26
 
26
- Currently it supports the creation, deletion, inspection, and (un)deployment of applications and services.
27
+ Currently, it supports the creation, deletion, inspection, and (un)deployment of applications and services.
27
28
 
28
29
  E.g. To deploy a default application with two services:
29
30
  ```
@@ -81,7 +82,7 @@ All current services: '2'
81
82
  instances: '1'
82
83
  ```
83
84
 
84
- For a more detailed explanation have a try out the CLI and use the `-h` flag.
85
+ For a more detailed explanation try out the CLI and use the `-h` flag.
85
86
 
86
87
  ## [Installation](https://pypi.org/project/oak-cli/)
87
88
  ```
@@ -2,14 +2,14 @@
2
2
 
3
3
  **oakestra-cli** is a very basic command line tool for controlling your [Oakestra](https://github.com/oakestra/oakestra) setup.
4
4
 
5
- It is intended to be a interface for the API as well as a development tool.
5
+ It is intended to be an interface for the API as well as a development tool.
6
6
 
7
7
  The CLI supports command (tab) autocompletion.
8
8
 
9
9
  ## Usage
10
10
  The package can be called via `oak`.
11
11
 
12
- Currently it supports the creation, deletion, inspection, and (un)deployment of applications and services.
12
+ Currently, it supports the creation, deletion, inspection, and (un)deployment of applications and services.
13
13
 
14
14
  E.g. To deploy a default application with two services:
15
15
  ```
@@ -67,7 +67,7 @@ All current services: '2'
67
67
  instances: '1'
68
68
  ```
69
69
 
70
- For a more detailed explanation have a try out the CLI and use the `-h` flag.
70
+ For a more detailed explanation try out the CLI and use the `-h` flag.
71
71
 
72
72
  ## [Installation](https://pypi.org/project/oak-cli/)
73
73
  ```
@@ -14,7 +14,7 @@ _DELETION_HELP_TEXT = "deletes one or all applications" + _APP_ID_HELP_TEXT
14
14
  def prepare_applications_deletion_argparser(
15
15
  applications_subparsers: Subparsers,
16
16
  ) -> None:
17
- def aux_delete_appliacations(args: Any):
17
+ def aux_delete_applications(args: Any):
18
18
  if args.all:
19
19
  delete_all_applications()
20
20
  else:
@@ -30,7 +30,5 @@ def prepare_applications_deletion_argparser(
30
30
  applications_deletion_parser.add_argument(
31
31
  "-a", "--all", action="store_true", help="delete all applications"
32
32
  )
33
- applications_deletion_parser.add_argument(
34
- "app_id", nargs="?", type=str, help=_APP_ID_HELP_TEXT
35
- )
36
- applications_deletion_parser.set_defaults(func=aux_delete_appliacations)
33
+ applications_deletion_parser.add_argument("app_id", nargs="?", type=str, help=_APP_ID_HELP_TEXT)
34
+ applications_deletion_parser.set_defaults(func=aux_delete_applications)
@@ -13,6 +13,4 @@ def prepare_applications_display_argparser(applications_subparsers: Subparsers)
13
13
  description=HELP_TEXT,
14
14
  formatter_class=argparse.RawTextHelpFormatter,
15
15
  )
16
- applications_status_parser.set_defaults(
17
- func=lambda _: display_current_applications()
18
- )
16
+ applications_status_parser.set_defaults(func=lambda _: display_current_applications())
@@ -5,6 +5,9 @@ import argparse
5
5
  import argcomplete
6
6
 
7
7
  from oak_cli.args_parser.apps.main import prepare_applications_argparsers
8
+
9
+ # from oak_cli.args_parser.docker.main import prepare_docker_argparsers
10
+ from oak_cli.args_parser.plugins.main import prepare_plugins_argparsers
8
11
  from oak_cli.args_parser.services.main import prepare_services_argparsers
9
12
 
10
13
 
@@ -19,6 +22,8 @@ def parse_arguments_and_execute() -> None:
19
22
 
20
23
  prepare_applications_argparsers(subparsers)
21
24
  prepare_services_argparsers(subparsers)
25
+ prepare_plugins_argparsers(subparsers)
26
+ # prepare_docker_argparsers(subparsers)
22
27
 
23
28
  argcomplete.autocomplete(parser)
24
29
  args = parser.parse_args()
@@ -0,0 +1,16 @@
1
+ import argparse
2
+
3
+ from oak_cli.commands.plugins.flops.main import create_new_fl_service
4
+ from oak_cli.utils.types import Subparsers
5
+
6
+
7
+ def prepare_flops_create_argparser(flops_subparser: Subparsers) -> None:
8
+ HELP_TEXT = "creates a new FLOps process - i.e. triggers the init FLOps API"
9
+ flops_create_parser = flops_subparser.add_parser(
10
+ "create",
11
+ aliases=["c"],
12
+ help=HELP_TEXT,
13
+ description=HELP_TEXT,
14
+ formatter_class=argparse.RawTextHelpFormatter,
15
+ )
16
+ flops_create_parser.set_defaults(func=lambda _: create_new_fl_service())
@@ -0,0 +1,25 @@
1
+ import argparse
2
+ from typing import Any
3
+
4
+ from oak_cli.args_parser.plugins.flops.create import prepare_flops_create_argparser
5
+ from oak_cli.utils.types import Subparsers
6
+
7
+
8
+ def prepare_flops_argparsers(subparsers: Subparsers) -> None:
9
+ flops_parser = subparsers.add_parser(
10
+ "flops",
11
+ aliases=["fl"],
12
+ help="command for FLOps related activities",
13
+ formatter_class=argparse.RawTextHelpFormatter,
14
+ )
15
+
16
+ def flops_parser_print_help(_: Any) -> None:
17
+ flops_parser.print_help()
18
+
19
+ flops_parser.set_defaults(func=flops_parser_print_help)
20
+
21
+ flops_subparser = flops_parser.add_subparsers(
22
+ dest="FLOps commands",
23
+ )
24
+
25
+ prepare_flops_create_argparser(flops_subparser)
@@ -0,0 +1,25 @@
1
+ import argparse
2
+ from typing import Any
3
+
4
+ from oak_cli.args_parser.plugins.flops.main import prepare_flops_argparsers
5
+ from oak_cli.utils.types import Subparsers
6
+
7
+
8
+ def prepare_plugins_argparsers(subparsers: Subparsers) -> None:
9
+ plugins_parser = subparsers.add_parser(
10
+ "plugins",
11
+ aliases=["p"],
12
+ help="command for plugin related activities",
13
+ formatter_class=argparse.RawTextHelpFormatter,
14
+ )
15
+
16
+ def plugins_parser_print_help(_: Any) -> None:
17
+ plugins_parser.print_help()
18
+
19
+ plugins_parser.set_defaults(func=plugins_parser_print_help)
20
+
21
+ plugins_subparsers = plugins_parser.add_subparsers(
22
+ dest="plugins commands",
23
+ )
24
+
25
+ prepare_flops_argparsers(plugins_subparsers)
@@ -19,9 +19,7 @@ def prepare_services_deployment_argparser(services_subparsers: Subparsers) -> No
19
19
  formatter_class=argparse.RawTextHelpFormatter,
20
20
  )
21
21
  services_deployment_parser.add_argument("service_id", type=str)
22
- services_deployment_parser.set_defaults(
23
- func=lambda args: deploy_new_instance(args.service_id)
24
- )
22
+ services_deployment_parser.set_defaults(func=lambda args: deploy_new_instance(args.service_id))
25
23
 
26
24
 
27
25
  def prepare_services_undeployment_argparser(services_subparsers: Subparsers) -> None:
@@ -2,10 +2,7 @@ import argparse
2
2
  from typing import Any
3
3
 
4
4
  from oak_cli.commands.services.get import get_single_service
5
- from oak_cli.commands.services.status import (
6
- display_all_current_services,
7
- display_single_service,
8
- )
5
+ from oak_cli.commands.services.status import display_all_current_services, display_single_service
9
6
  from oak_cli.utils.types import Subparsers
10
7
 
11
8
 
@@ -0,0 +1,81 @@
1
+ import enum
2
+ import json
3
+ from typing import List
4
+
5
+ import oak_cli.utils.api.custom_requests as custom_requests
6
+ from oak_cli.utils.api.common import SYSTEM_MANAGER_URL
7
+ from oak_cli.utils.api.custom_http import HttpMethod
8
+ from oak_cli.utils.logging import logger
9
+ from oak_cli.utils.SLAs.common import get_SLAs_path
10
+ from oak_cli.utils.types import Application, ApplicationId
11
+
12
+
13
+ def get_application(app_id: ApplicationId) -> Application:
14
+ return custom_requests.CustomRequest(
15
+ custom_requests.RequestCore(
16
+ base_url=SYSTEM_MANAGER_URL,
17
+ api_endpoint=f"/api/application/{app_id}",
18
+ ),
19
+ custom_requests.RequestAuxiliaries(
20
+ what_should_happen=f"Get application '{app_id}'",
21
+ ),
22
+ ).execute()
23
+
24
+
25
+ def get_applications() -> List[Application]:
26
+ apps = custom_requests.CustomRequest(
27
+ custom_requests.RequestCore(
28
+ base_url=SYSTEM_MANAGER_URL,
29
+ api_endpoint="/api/applications",
30
+ ),
31
+ custom_requests.RequestAuxiliaries(
32
+ what_should_happen="Get all applications",
33
+ ),
34
+ ).execute()
35
+ if not apps:
36
+ logger.info("No applications exist yet")
37
+ return apps
38
+
39
+
40
+ def send_sla(sla_enum: enum) -> List[Application]:
41
+ sla_file_name = f"{sla_enum}.SLA.json"
42
+ SLA = ""
43
+ with open(get_SLAs_path() / sla_file_name, "r") as f:
44
+ SLA = json.load(f)
45
+ sla_apps = SLA["applications"]
46
+ sla_app_names = [app["application_name"] for app in sla_apps]
47
+ # Note: The API endpoint returns all user apps and not just the newly posted ones.
48
+ all_user_apps = custom_requests.CustomRequest(
49
+ custom_requests.RequestCore(
50
+ http_method=HttpMethod.POST,
51
+ base_url=SYSTEM_MANAGER_URL,
52
+ api_endpoint="/api/application",
53
+ data=SLA,
54
+ ),
55
+ custom_requests.RequestAuxiliaries(
56
+ what_should_happen=f"Create new application based on '{sla_enum}'",
57
+ show_msg_on_success=True,
58
+ ),
59
+ ).execute()
60
+
61
+ newly_added_apps = [app for app in all_user_apps if (app["application_name"] in sla_app_names)]
62
+ return newly_added_apps
63
+
64
+
65
+ def delete_application(app_id: ApplicationId) -> None:
66
+ custom_requests.CustomRequest(
67
+ custom_requests.RequestCore(
68
+ http_method=HttpMethod.DELETE,
69
+ base_url=SYSTEM_MANAGER_URL,
70
+ api_endpoint=f"/api/application/{app_id}",
71
+ ),
72
+ custom_requests.RequestAuxiliaries(
73
+ what_should_happen=f"Delete application '{app_id}'",
74
+ show_msg_on_success=True,
75
+ ),
76
+ ).execute()
77
+
78
+
79
+ def delete_all_applications() -> None:
80
+ for app in get_applications():
81
+ delete_application(app["applicationID"])
@@ -0,0 +1,11 @@
1
+ from icecream import ic
2
+
3
+ from oak_cli.commands.apps.main import get_applications
4
+
5
+ ic.configureOutput(prefix="")
6
+
7
+
8
+ def display_current_applications() -> None:
9
+ current_applications = get_applications()
10
+ for i, application in enumerate(current_applications):
11
+ ic(i, application)
@@ -0,0 +1,13 @@
1
+ {
2
+ "verbose": true,
3
+ "customerID": "Admin",
4
+ "applicationID": "",
5
+ "code": "https://github.com/Malyuk-A/mlflower-test-a",
6
+ "memory": 100,
7
+ "vcpus": 1,
8
+ "vgpus": 0,
9
+ "vtpus": 0,
10
+ "bandwidth_in": 0,
11
+ "bandwidth_out": 0,
12
+ "storage": 0
13
+ }
@@ -0,0 +1,29 @@
1
+ import json
2
+ import os
3
+ import pathlib
4
+
5
+ import oak_cli.utils.api.custom_requests as custom_requests
6
+ from oak_cli.utils.api.custom_http import HttpMethod
7
+
8
+ ROOT_FL_MANAGER_URL = f"http://{os.environ.get('SYSTEM_MANAGER_URL')}:5072"
9
+
10
+
11
+ def create_new_fl_service() -> None:
12
+ sla_file_name = "flops.SLA.json"
13
+ current_file_path = pathlib.Path(__file__).resolve()
14
+ sla_file_path = current_file_path.parent / sla_file_name
15
+ with open(sla_file_path, "r") as f:
16
+ SLA = json.load(f)
17
+
18
+ custom_requests.CustomRequest(
19
+ custom_requests.RequestCore(
20
+ http_method=HttpMethod.POST,
21
+ base_url=ROOT_FL_MANAGER_URL,
22
+ api_endpoint="/api/flops",
23
+ data=SLA,
24
+ ),
25
+ custom_requests.RequestAuxiliaries(
26
+ what_should_happen="Init new FLOps processes",
27
+ show_msg_on_success=True,
28
+ ),
29
+ ).execute()
File without changes
@@ -0,0 +1,39 @@
1
+ import oak_cli.utils.api.custom_requests as custom_requests
2
+ from oak_cli.commands.services.get import get_single_service
3
+ from oak_cli.utils.api.common import SYSTEM_MANAGER_URL
4
+ from oak_cli.utils.api.custom_http import HttpMethod
5
+ from oak_cli.utils.types import Id, ServiceId
6
+
7
+
8
+ def deploy_new_instance(service_id: ServiceId) -> None:
9
+ custom_requests.CustomRequest(
10
+ custom_requests.RequestCore(
11
+ http_method=HttpMethod.POST,
12
+ base_url=SYSTEM_MANAGER_URL,
13
+ api_endpoint=f"/api/service/{service_id}/instance",
14
+ ),
15
+ custom_requests.RequestAuxiliaries(
16
+ what_should_happen=f"Deploy a new instance for the service '{service_id}'",
17
+ show_msg_on_success=True,
18
+ ),
19
+ ).execute()
20
+
21
+
22
+ def undeploy_instance(service_id: ServiceId, instance_id: Id = None) -> None:
23
+ custom_requests.CustomRequest(
24
+ custom_requests.RequestCore(
25
+ http_method=HttpMethod.DELETE,
26
+ base_url=SYSTEM_MANAGER_URL,
27
+ api_endpoint=f"/api/service/{service_id}/instance/{instance_id or 0}",
28
+ ),
29
+ custom_requests.RequestAuxiliaries(
30
+ what_should_happen=f"Undeploy instance '{instance_id or 0}' for service '{service_id}'",
31
+ show_msg_on_success=True,
32
+ ),
33
+ ).execute()
34
+
35
+
36
+ def undeploy_all_instances_of_service(service_id: ServiceId) -> None:
37
+ service = get_single_service(service_id)
38
+ for instance in service["instance_list"]:
39
+ undeploy_instance(service_id, instance["instance_number"])
@@ -0,0 +1,38 @@
1
+ from typing import List
2
+
3
+ import oak_cli.utils.api.custom_requests as custom_requests
4
+ from oak_cli.utils.api.common import SYSTEM_MANAGER_URL
5
+ from oak_cli.utils.logging import logger
6
+ from oak_cli.utils.types import Service, ServiceId
7
+
8
+
9
+ def get_single_service(service_id: ServiceId) -> Service:
10
+ return custom_requests.CustomRequest(
11
+ custom_requests.RequestCore(
12
+ base_url=SYSTEM_MANAGER_URL,
13
+ api_endpoint=f"/api/service/{service_id}",
14
+ ),
15
+ custom_requests.RequestAuxiliaries(
16
+ what_should_happen=f"Get single service '{service_id}'",
17
+ ),
18
+ ).execute()
19
+
20
+
21
+ def get_all_services(app_id: ServiceId = None) -> List[Service]:
22
+ what_should_happen = "Get all services"
23
+ if app_id:
24
+ what_should_happen += f" of app '{app_id}'"
25
+
26
+ services = custom_requests.CustomRequest(
27
+ custom_requests.RequestCore(
28
+ base_url=SYSTEM_MANAGER_URL,
29
+ api_endpoint=f"/api/services/{app_id or ''}",
30
+ ),
31
+ custom_requests.RequestAuxiliaries(
32
+ what_should_happen=what_should_happen,
33
+ ),
34
+ ).execute()
35
+
36
+ if not services:
37
+ logger.info("No applications exist yet")
38
+ return services
@@ -0,0 +1,41 @@
1
+ from icecream import ic
2
+
3
+ from oak_cli.commands.services.get import get_all_services
4
+ from oak_cli.utils.types import ApplicationId, Service
5
+
6
+ ic.configureOutput(prefix="")
7
+
8
+
9
+ def display_single_service(service: Service, verbose: bool = False) -> None:
10
+ if verbose:
11
+ for instance in service["instance_list"]:
12
+ instance["cpu_history"] = "..."
13
+ instance["memory_history"] = "..."
14
+ instance["logs"] = "..."
15
+ else:
16
+ mask = [
17
+ "addresses",
18
+ "app_name",
19
+ "app_ns",
20
+ "applicationID",
21
+ "service_name",
22
+ "service_ns",
23
+ "service_ns",
24
+ "one_shot",
25
+ "microserviceID",
26
+ "cmd",
27
+ "code",
28
+ "image",
29
+ ]
30
+ service = {key: service[key] for key in mask if key in service}
31
+ ic(service)
32
+
33
+
34
+ def display_all_current_services(
35
+ verbose: bool = False,
36
+ app_id: ApplicationId = None,
37
+ ) -> None:
38
+ all_current_services = get_all_services(app_id)
39
+ for i, service in enumerate(all_current_services):
40
+ ic(i)
41
+ display_single_service(service, verbose)
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env python3
2
+ # PYTHON_ARGCOMPLETE_OK
3
+
4
+ from oak_cli.args_parser.main import parse_arguments_and_execute
5
+ from oak_cli.utils.argcomplete import handle_argcomplete
6
+ from oak_cli.utils.exceptions import OakCliException
7
+ from oak_cli.utils.logging import logger
8
+
9
+
10
+ def main():
11
+ handle_argcomplete()
12
+ # Potential Future Work:
13
+ # This implementation uses argparse & argcomplete for user friendly CLI behavior.
14
+ # However especially argparse comes with a lot of additional boilerplate code.
15
+ # It might be useful to look into more modern solutions for python CLI's like
16
+ # https://github.com/pallets/click
17
+
18
+ try:
19
+ parse_arguments_and_execute()
20
+ except OakCliException as e:
21
+ logger.fatal(f"{e.msg}, {e.http_status}")
22
+ except Exception as e:
23
+ err_msg = f"Unexpected error occured: {e}"
24
+ logger.fatal(err_msg)
25
+
26
+
27
+ if __name__ == "__main__":
28
+ main()
File without changes
File without changes
File without changes
@@ -0,0 +1,3 @@
1
+ import os
2
+
3
+ SYSTEM_MANAGER_URL = f"http://{os.environ.get('SYSTEM_MANAGER_URL')}:10000"
@@ -1,12 +1,7 @@
1
- import os
2
-
3
1
  import requests
4
2
 
5
3
  from oak_cli.utils.types import CustomEnum
6
4
 
7
- GITHUB_PREFIX = "https://github.com/"
8
- SYSTEM_MANAGER_URL = f"http://{os.environ.get('SYSTEM_MANAGER_URL')}:10000"
9
-
10
5
 
11
6
  class HttpMethod(CustomEnum):
12
7
  GET = "get"
@@ -0,0 +1,94 @@
1
+ import json
2
+ from http import HTTPStatus
3
+ from typing import NamedTuple
4
+
5
+ import requests
6
+
7
+ from oak_cli.utils.api.custom_http import HttpMethod
8
+ from oak_cli.utils.api.login import get_login_token
9
+ from oak_cli.utils.exceptions import OakCliException
10
+ from oak_cli.utils.logging import logger
11
+
12
+
13
+ class RequestCore(NamedTuple):
14
+ base_url: str
15
+ api_endpoint: str = None
16
+ query_params: str = None
17
+ http_method: HttpMethod = HttpMethod.GET
18
+ custom_headers: dict = None
19
+ data: dict = None
20
+
21
+
22
+ class RequestAuxiliaries(NamedTuple):
23
+ what_should_happen: str
24
+ exception: OakCliException = OakCliException
25
+ show_msg_on_success: bool = False
26
+ is_oakestra_api: bool = True
27
+
28
+
29
+ class CustomRequest:
30
+ def __init__(self, core: RequestCore, aux: RequestAuxiliaries):
31
+ self.core = core
32
+ self.aux = aux
33
+ self.headers = None
34
+ self.url = None
35
+ self.args = None
36
+ self.response = None
37
+ self._prepare()
38
+
39
+ def _prepare(self) -> None:
40
+ self.url = self.core.base_url
41
+ if self.core.api_endpoint is not None:
42
+ self.url = f"{self.core.base_url}{self.core.api_endpoint}"
43
+ if self.core.query_params is not None:
44
+ self.url += f"?{self.core.query_params}"
45
+
46
+ if self.core.custom_headers:
47
+ self.headers = self.core.custom_headers
48
+ else:
49
+ self.headers = (
50
+ {"Authorization": f"Bearer {get_login_token()}"} if self.aux.is_oakestra_api else {}
51
+ )
52
+
53
+ if self.core.data and not self.core.custom_headers:
54
+ self.headers["Content-Type"] = "application/json"
55
+
56
+ self.args = {
57
+ "url": self.url,
58
+ "verify": False,
59
+ **({"headers": self.headers} if self.headers else {}),
60
+ **({"json": self.core.data} if self.core.data else {}),
61
+ }
62
+
63
+ def _create_failure_msg(self) -> str:
64
+ return " ".join(
65
+ (
66
+ self.aux.what_should_happen,
67
+ "request failed",
68
+ f"with {self.response.status}" if self.response else "- no response",
69
+ f"for '{self.core.http_method}' '{self.url}",
70
+ )
71
+ )
72
+
73
+ def execute(self) -> any:
74
+ error_msg = ""
75
+ try:
76
+ self.response = self.core.http_method.call(**self.args)
77
+ self.response.status = HTTPStatus(self.response.status_code)
78
+ if self.response.status == HTTPStatus.OK:
79
+ if self.aux.show_msg_on_success:
80
+ logger.info(f"Success: '{self.aux.what_should_happen}'")
81
+ response = self.response.json()
82
+ if isinstance(response, str):
83
+ response = json.loads(response)
84
+ return response
85
+
86
+ except requests.exceptions.RequestException as e:
87
+ error_msg = f"exception: {e}: "
88
+
89
+ error_msg += self._create_failure_msg()
90
+
91
+ raise self.aux.exception(
92
+ msg=error_msg,
93
+ http_status=self.response.status if self.response else None,
94
+ )
@@ -0,0 +1,36 @@
1
+ import oak_cli.utils.api.custom_requests as custom_requests
2
+ from oak_cli.utils.api.common import SYSTEM_MANAGER_URL
3
+ from oak_cli.utils.api.custom_http import HttpMethod
4
+ from oak_cli.utils.exceptions import LoginException
5
+
6
+ _login_token = ""
7
+
8
+
9
+ class LoginFailed(Exception):
10
+ pass
11
+
12
+
13
+ def _login_and_set_token() -> str:
14
+ response = custom_requests.CustomRequest(
15
+ custom_requests.RequestCore(
16
+ http_method=HttpMethod.POST,
17
+ base_url=SYSTEM_MANAGER_URL,
18
+ api_endpoint="/api/auth/login",
19
+ data={"username": "Admin", "password": "Admin"},
20
+ custom_headers={"accept": "application/json", "Content-Type": "application/json"},
21
+ ),
22
+ custom_requests.RequestAuxiliaries(
23
+ what_should_happen="Login",
24
+ exception=LoginException,
25
+ ),
26
+ ).execute()
27
+
28
+ global _login_token
29
+ _login_token = response["token"]
30
+ return _login_token
31
+
32
+
33
+ def get_login_token() -> str:
34
+ if _login_token == "":
35
+ return _login_and_set_token()
36
+ return _login_token
@@ -0,0 +1,23 @@
1
+ from http import HTTPStatus
2
+
3
+
4
+ class OakCliException(Exception):
5
+ def __init__(
6
+ self,
7
+ msg: str,
8
+ http_status: HTTPStatus = None,
9
+ ):
10
+ self.msg = msg
11
+ self.http_status = http_status
12
+
13
+
14
+ class OakestraException(OakCliException):
15
+ pass
16
+
17
+
18
+ class MQTTException(OakCliException):
19
+ pass
20
+
21
+
22
+ class LoginException(OakCliException):
23
+ pass
@@ -33,7 +33,6 @@ class CustomFormatter(logging.Formatter):
33
33
 
34
34
 
35
35
  _LOGGER_NAME = "logger"
36
-
37
36
  _FORMAT = "%(message)s"
38
37
 
39
38
  logger = logging.getLogger(_LOGGER_NAME)
@@ -17,6 +17,4 @@ Application = dict
17
17
  SLA = dict
18
18
  DbObject = dict
19
19
 
20
-
21
- # Custom type for nicer type hints
22
20
  Subparsers = argparse._SubParsersAction
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "oak_cli"
3
- version = "0.2.6"
3
+ version = "0.2.8"
4
4
  description = ""
5
5
  authors = ["Alexander Malyuk <malyuk.alexander1999@gmail.com>"]
6
6
  readme = "README.md"
@@ -8,6 +8,7 @@ readme = "README.md"
8
8
  [tool.poetry.dependencies]
9
9
  python = "^3.10"
10
10
  argcomplete = "^3.2.3"
11
+ icecream = "^2.1.3"
11
12
 
12
13
  [build-system]
13
14
  requires = ["poetry-core"]
@@ -15,3 +16,6 @@ build-backend = "poetry.core.masonry.api"
15
16
 
16
17
  [tool.poetry.scripts]
17
18
  oak = "oak_cli.main:main"
19
+
20
+ [tool.black]
21
+ line-length = 100
@@ -1,60 +0,0 @@
1
- import enum
2
- import json
3
- from typing import List
4
-
5
- from oak_cli.utils.api.common import SYSTEM_MANAGER_URL, HttpMethod
6
- from oak_cli.utils.api.main import handle_request
7
- from oak_cli.utils.SLAs.common import get_SLAs_path
8
- from oak_cli.utils.types import Application, ApplicationId
9
-
10
-
11
- def get_application(app_id: ApplicationId) -> Application:
12
- app = handle_request(
13
- base_url=SYSTEM_MANAGER_URL,
14
- http_method=HttpMethod.GET,
15
- api_endpoint=f"/api/application/{app_id}",
16
- what_should_happen=f"Get application '{app_id}'",
17
- )
18
- return app
19
-
20
-
21
- def get_applications() -> List[Application]:
22
- apps = handle_request(
23
- base_url=SYSTEM_MANAGER_URL,
24
- http_method=HttpMethod.GET,
25
- api_endpoint="/api/applications",
26
- what_should_happen="Get all applications",
27
- )
28
- return apps
29
-
30
-
31
- def send_sla(sla_enum: enum) -> List[Application]:
32
- sla_file_name = f"{sla_enum}.SLA.json"
33
- SLA = ""
34
- with open(get_SLAs_path() / sla_file_name, "r") as f:
35
- SLA = json.load(f)
36
-
37
- apps = handle_request(
38
- base_url=SYSTEM_MANAGER_URL,
39
- http_method=HttpMethod.POST,
40
- data=SLA,
41
- api_endpoint="/api/application",
42
- what_should_happen=f"Create new application based on '{sla_enum}'",
43
- show_msg_on_success=True,
44
- )
45
- return apps
46
-
47
-
48
- def delete_application(app_id: ApplicationId) -> None:
49
- handle_request(
50
- base_url=SYSTEM_MANAGER_URL,
51
- http_method=HttpMethod.DELETE,
52
- api_endpoint=f"/api/application/{app_id}",
53
- what_should_happen=f"Delete application '{app_id}'",
54
- show_msg_on_success=True,
55
- )
56
-
57
-
58
- def delete_all_applications() -> None:
59
- for app in get_applications():
60
- delete_application(app["applicationID"])
@@ -1,21 +0,0 @@
1
- from oak_cli.commands.apps.main import get_applications
2
- from oak_cli.utils.logging import logger
3
-
4
-
5
- def display_current_applications() -> None:
6
- current_applications = get_applications()
7
-
8
- def log_aux(key: str, value: str) -> None:
9
- logger.debug(f" {key}: '{value}'")
10
-
11
- logger.info(f"Current apps: '{len(current_applications)}'")
12
- for i, application in enumerate(current_applications):
13
- logger.info(f" App '{i}':")
14
- log_aux("id", application["applicationID"])
15
- log_aux("name", application["application_name"])
16
- log_aux("ns", application["application_namespace"])
17
- log_aux("desc", application["application_desc"])
18
- log_aux(
19
- "microservices",
20
- f"{len(application['microservices'])}: {application['microservices']}",
21
- )
@@ -1,30 +0,0 @@
1
- from oak_cli.commands.services.get import get_single_service
2
- from oak_cli.utils.api.common import SYSTEM_MANAGER_URL, HttpMethod
3
- from oak_cli.utils.api.main import handle_request
4
- from oak_cli.utils.types import Id, ServiceId
5
-
6
-
7
- def deploy_new_instance(service_id: ServiceId) -> None:
8
- handle_request(
9
- base_url=SYSTEM_MANAGER_URL,
10
- http_method=HttpMethod.POST,
11
- api_endpoint=f"/api/service/{service_id}/instance",
12
- what_should_happen=f"Deploy a new instance for the service '{service_id}'",
13
- show_msg_on_success=True,
14
- )
15
-
16
-
17
- def undeploy_instance(service_id: ServiceId, instance_id: Id = None) -> None:
18
- handle_request(
19
- base_url=SYSTEM_MANAGER_URL,
20
- http_method=HttpMethod.DELETE,
21
- api_endpoint=f"/api/service/{service_id}/instance/{instance_id or 0}",
22
- what_should_happen=f"Undeploy instance '{instance_id or 0}' for the service '{service_id}'",
23
- show_msg_on_success=True,
24
- )
25
-
26
-
27
- def undeploy_all_instances_of_service(service_id: ServiceId) -> None:
28
- service = get_single_service(service_id)
29
- for instance in service["instance_list"]:
30
- undeploy_instance(service_id, instance["instance_number"])
@@ -1,29 +0,0 @@
1
- from typing import List
2
-
3
- from oak_cli.utils.api.common import SYSTEM_MANAGER_URL, HttpMethod
4
- from oak_cli.utils.api.main import handle_request
5
- from oak_cli.utils.types import Service, ServiceId
6
-
7
-
8
- def get_single_service(service_id: ServiceId) -> Service:
9
- service = handle_request(
10
- base_url=SYSTEM_MANAGER_URL,
11
- http_method=HttpMethod.GET,
12
- api_endpoint=f"/api/service/{service_id}",
13
- what_should_happen=f"Get single service '{service_id}'",
14
- )
15
- return service
16
-
17
-
18
- def get_all_services(app_id: ServiceId = None) -> List[Service]:
19
- what_should_happen = "Get all services"
20
- if app_id:
21
- what_should_happen += f" of app '{app_id}'"
22
-
23
- services = handle_request(
24
- base_url=SYSTEM_MANAGER_URL,
25
- http_method=HttpMethod.GET,
26
- api_endpoint=f"/api/services/{app_id or ''}",
27
- what_should_happen=what_should_happen,
28
- )
29
- return services
@@ -1,75 +0,0 @@
1
- from oak_cli.commands.services.get import get_all_services
2
- from oak_cli.utils.logging import logger
3
- from oak_cli.utils.types import ApplicationId, Service
4
-
5
-
6
- def _log_aux_service(key: str, value: str, service: Service) -> None:
7
- value = service.get(value, None)
8
- if value is not None:
9
- _log_aux(key, value)
10
-
11
-
12
- def _log_aux(key: str, value: str) -> None:
13
- logger.debug(f" {key}: '{value}'")
14
-
15
-
16
- def verbose_log_aux(section_name: str) -> None:
17
- logger.info(f" - {section_name} -")
18
-
19
-
20
- def display_single_service(service: Service, verbose: bool = False) -> None:
21
- verbose_log_aux("microservice")
22
- _log_aux_service("id", "microserviceID", service)
23
- _log_aux_service(
24
- "microservice name" if verbose else "name", "microservice_name", service
25
- )
26
- _log_aux_service(
27
- "microservice ns" if verbose else "ns", "microservice_namespace", service
28
- )
29
- _log_aux("parent app", f"{service['app_name']}: {service['applicationID']}")
30
-
31
- if verbose:
32
- verbose_log_aux("service")
33
- _log_aux_service("service name", "service_name", service)
34
-
35
- verbose_log_aux("resources")
36
- _log_aux_service("memory", "memory", service)
37
- _log_aux_service("vcpus", "vcpus", service)
38
- if verbose:
39
- _log_aux_service("storage", "storage", service)
40
- _log_aux_service("vgpus", "vgpus", service)
41
-
42
- verbose_log_aux("container")
43
- _log_aux_service("image", "image", service)
44
- if verbose:
45
- _log_aux_service("code", "code", service)
46
-
47
- verbose_log_aux("networking")
48
- _log_aux_service("port", "port", service)
49
- if verbose:
50
- _log_aux_service("bandwidth in", "bandwidth_in", service)
51
- _log_aux_service("bandwidth out", "bandwidth_out", service)
52
-
53
- verbose_log_aux("instances")
54
- instances = service["instance_list"]
55
- num_instances = len(instances)
56
- _log_aux("instances", num_instances)
57
- if verbose and num_instances > 0:
58
- for i, instance in enumerate(instances):
59
- logger.info(f" Instance '{i}':")
60
- _log_aux(" instance_number", instance["instance_number"])
61
- _log_aux(" status", instance["status"])
62
- _log_aux(" publicip", instance["publicip"])
63
- _log_aux(" cpu", instance["cpu"])
64
-
65
-
66
- def display_all_current_services(
67
- verbose: bool = False,
68
- app_id: ApplicationId = None,
69
- ) -> None:
70
- all_current_services = get_all_services(app_id)
71
-
72
- logger.info(f"All current services: '{len(all_current_services)}'")
73
- for i, service in enumerate(all_current_services):
74
- logger.warning(f" Service '{i}':")
75
- display_single_service(service, verbose)
@@ -1,14 +0,0 @@
1
- #!/usr/bin/env python3
2
- # PYTHON_ARGCOMPLETE_OK
3
-
4
- from oak_cli.args_parser.main import parse_arguments_and_execute
5
- from oak_cli.utils.argcomplete import handle_argcomplete
6
-
7
-
8
- def main():
9
- handle_argcomplete()
10
- parse_arguments_and_execute()
11
-
12
-
13
- if __name__ == "__main__":
14
- main()
@@ -1,33 +0,0 @@
1
- from http import HTTPStatus
2
-
3
- import oak_cli.utils.api as oak_api
4
-
5
- _login_token = ""
6
-
7
-
8
- class LoginFailed(Exception):
9
- pass
10
-
11
-
12
- def _login_and_set_token() -> str:
13
- data = {"username": "Admin", "password": "Admin"}
14
- headers = {"accept": "application/json", "Content-Type": "application/json"}
15
-
16
- json_data = oak_api.main.handle_request(
17
- base_url=oak_api.common.SYSTEM_MANAGER_URL,
18
- http_method=oak_api.common.HttpMethod.POST,
19
- api_endpoint="/api/auth/login",
20
- headers=headers,
21
- data=data,
22
- what_should_happen="Login",
23
- )
24
-
25
- global _login_token
26
- _login_token = json_data["token"]
27
- return _login_token
28
-
29
-
30
- def get_login_token() -> str:
31
- if _login_token == "":
32
- return _login_and_set_token()
33
- return _login_token
@@ -1,108 +0,0 @@
1
- import json
2
- import sys
3
- from http import HTTPStatus
4
- from typing import NamedTuple, Optional, Tuple, Union
5
-
6
- import requests
7
-
8
- from oak_cli.utils.api.common import HttpMethod
9
- from oak_cli.utils.api.login import get_login_token
10
- from oak_cli.utils.logging import logger
11
-
12
-
13
- class ApiQueryComponents(NamedTuple):
14
- url: str
15
- headers: dict
16
- data: dict = None
17
-
18
-
19
- def _prepare_api_query_components(
20
- base_url: str,
21
- api_endpoint: str = None,
22
- custom_headers: dict = None,
23
- data: dict = None,
24
- query_params: str = None,
25
- ) -> ApiQueryComponents:
26
- url = base_url
27
- if api_endpoint is not None:
28
- url = f"{base_url}{api_endpoint}"
29
- if query_params is not None:
30
- url += f"?{query_params}"
31
- headers = custom_headers or {"Authorization": f"Bearer {get_login_token()}"}
32
- if data and not custom_headers:
33
- headers["Content-Type"] = "application/json"
34
- return ApiQueryComponents(url, headers, data)
35
-
36
-
37
- def _create_failure_msg(
38
- what_should_happen: str,
39
- http_method: HttpMethod,
40
- url: str,
41
- response_status: HTTPStatus = None,
42
- ) -> str:
43
- return (
44
- " ".join(
45
- (
46
- what_should_happen,
47
- "request failed with",
48
- str(response_status),
49
- f"for '{http_method}' '{url}",
50
- )
51
- ),
52
- )
53
-
54
-
55
- def handle_request(
56
- base_url: str,
57
- what_should_happen: str,
58
- http_method: HttpMethod = HttpMethod.GET,
59
- api_endpoint: str = None,
60
- headers: dict = None,
61
- data: dict = None,
62
- show_msg_on_success: bool = False,
63
- special_msg_on_fail: str = None,
64
- query_params: str = None,
65
- terminate_when_failed: bool = True,
66
- ) -> Union[HTTPStatus, Tuple[HTTPStatus, Optional[dict]]]:
67
-
68
- url, headers, data = _prepare_api_query_components(
69
- base_url, api_endpoint, headers, data, query_params
70
- )
71
- args = {
72
- "url": url,
73
- "verify": False,
74
- **({"headers": headers} if headers else {}),
75
- **({"json": data} if data else {}),
76
- }
77
-
78
- try:
79
- response = http_method.call(**args)
80
- response_status = HTTPStatus(response.status_code)
81
-
82
- if response_status == HTTPStatus.OK:
83
- if show_msg_on_success:
84
- logger.info(f"Success: {what_should_happen}")
85
- response = response.json()
86
- if isinstance(response, str):
87
- response = json.loads(response)
88
- if terminate_when_failed:
89
- return response
90
- return response_status, response
91
- else:
92
- logger.error(f"FAILED: {special_msg_on_fail or what_should_happen}!")
93
- logger.error(
94
- _create_failure_msg(
95
- what_should_happen, http_method, url, response_status
96
- )
97
- )
98
- logger.error(f"response: '{response}'")
99
- if terminate_when_failed:
100
- sys.exit(1)
101
- return response_status, None
102
-
103
- except requests.exceptions.RequestException as e:
104
- logger.error(_create_failure_msg(what_should_happen, http_method, url))
105
- logger.error(e)
106
- if terminate_when_failed:
107
- sys.exit(1)
108
- return HTTPStatus.INTERNAL_SERVER_ERROR, None
File without changes
File without changes