oak-cli 0.2.5__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 (36) hide show
  1. oak_cli-0.2.5/PKG-INFO +98 -0
  2. oak_cli-0.2.5/README.md +83 -0
  3. oak_cli-0.2.5/oak_cli/__init__.py +0 -0
  4. oak_cli-0.2.5/oak_cli/args_parser/__init__.py +0 -0
  5. oak_cli-0.2.5/oak_cli/args_parser/apps/__init__.py +0 -0
  6. oak_cli-0.2.5/oak_cli/args_parser/apps/create.py +40 -0
  7. oak_cli-0.2.5/oak_cli/args_parser/apps/delete.py +36 -0
  8. oak_cli-0.2.5/oak_cli/args_parser/apps/main.py +29 -0
  9. oak_cli-0.2.5/oak_cli/args_parser/apps/status.py +18 -0
  10. oak_cli-0.2.5/oak_cli/args_parser/main.py +25 -0
  11. oak_cli-0.2.5/oak_cli/args_parser/services/__init__.py +0 -0
  12. oak_cli-0.2.5/oak_cli/args_parser/services/deployment.py +53 -0
  13. oak_cli-0.2.5/oak_cli/args_parser/services/main.py +31 -0
  14. oak_cli-0.2.5/oak_cli/args_parser/services/status.py +49 -0
  15. oak_cli-0.2.5/oak_cli/commands/apps/__init__.py +0 -0
  16. oak_cli-0.2.5/oak_cli/commands/apps/main.py +60 -0
  17. oak_cli-0.2.5/oak_cli/commands/apps/status.py +21 -0
  18. oak_cli-0.2.5/oak_cli/commands/services/__init__.py +0 -0
  19. oak_cli-0.2.5/oak_cli/commands/services/deployment.py +30 -0
  20. oak_cli-0.2.5/oak_cli/commands/services/get.py +29 -0
  21. oak_cli-0.2.5/oak_cli/commands/services/status.py +75 -0
  22. oak_cli-0.2.5/oak_cli/main.py +14 -0
  23. oak_cli-0.2.5/oak_cli/utils/SLAs/__init__.py +0 -0
  24. oak_cli-0.2.5/oak_cli/utils/SLAs/blank_app_without_services.SLA.json +13 -0
  25. oak_cli-0.2.5/oak_cli/utils/SLAs/common.py +13 -0
  26. oak_cli-0.2.5/oak_cli/utils/SLAs/default_app_with_services.SLA.json +57 -0
  27. oak_cli-0.2.5/oak_cli/utils/__init__.py +0 -0
  28. oak_cli-0.2.5/oak_cli/utils/api/__init__.py +0 -0
  29. oak_cli-0.2.5/oak_cli/utils/api/common.py +31 -0
  30. oak_cli-0.2.5/oak_cli/utils/api/login.py +33 -0
  31. oak_cli-0.2.5/oak_cli/utils/api/main.py +108 -0
  32. oak_cli-0.2.5/oak_cli/utils/argcomplete.py +16 -0
  33. oak_cli-0.2.5/oak_cli/utils/common.py +6 -0
  34. oak_cli-0.2.5/oak_cli/utils/logging.py +47 -0
  35. oak_cli-0.2.5/oak_cli/utils/types.py +22 -0
  36. oak_cli-0.2.5/pyproject.toml +17 -0
oak_cli-0.2.5/PKG-INFO ADDED
@@ -0,0 +1,98 @@
1
+ Metadata-Version: 2.1
2
+ Name: oak_cli
3
+ Version: 0.2.5
4
+ Summary:
5
+ Author: Alexander Malyuk
6
+ Author-email: malyuk.alexander1999@gmail.com
7
+ Requires-Python: >=3.10,<4.0
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.10
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Requires-Dist: argcomplete (>=3.2.3,<4.0.0)
13
+ Description-Content-Type: text/markdown
14
+
15
+ # Oakestra CLI
16
+
17
+ > NM: Rename system commands to Oakestra
18
+
19
+ **oakestra-cli** is a very basic command line tool for controlling the EdgeIO framework.
20
+
21
+ ## Installation
22
+
23
+ Use the package manager [pip](https://pip.pypa.io/en/stable/) and the .whl file in the dist directory to install
24
+ edgeiocli.
25
+
26
+ ```bash
27
+ pip install edgeiocli-0.1.0-py3-none-any.whl
28
+ ```
29
+
30
+ ## Build
31
+
32
+ Use the package [poetry](https://python-poetry.org/) to build the tool and create a new .whl file.
33
+
34
+ Updates the dependency's:
35
+ ```bash
36
+ poetry update
37
+ ```
38
+
39
+ Installs the package:
40
+ ```bash
41
+ poetry install
42
+ ```
43
+
44
+ Creates the .whl file:
45
+ ```bash
46
+ poetry build
47
+ ```
48
+
49
+ ## Usage
50
+
51
+ ```bash
52
+ edgeiocli COMMAND [ARGS] [OPTIONS]
53
+
54
+ # Try 'edgeiocli --help' for help.
55
+ ```
56
+
57
+ ## Available Commands
58
+
59
+ - `login [USERNAME]` logs the user into the system and creates access token
60
+ - `logout` logs out the user and deletes the token
61
+ - `change-password` changes the password of the user
62
+ - `application [COMMAND]`
63
+ - `create` creates a new application
64
+ - `delete [APPLICATION_ID]` deletes a application
65
+ - `list-jobs [APPLICATION_ID]` displays all jobs in the application
66
+ - `list` displays all applications of the user
67
+ - `service [COMMAND]`
68
+ - `delete [SERVICE_ID]` deletes the job
69
+ - `create [PATH]` creates a new service
70
+ - `deploy [SERVICE_ID]` tries to deploy a service
71
+ - `user [COMMAND]`
72
+ - `create --role [Admin | Application_Provider | Infrastructure_Provider]` creates a new user
73
+ - `delete [USERNAME] deletes the user`
74
+ - `list` displays all user of the system
75
+ - `set-roles [USERNAME] --role [Admin | Application_Provider | Infrastructure_Provider]`
76
+
77
+ ## Deployment
78
+ The given json file should contain only a microservice configuration, as in the example, all the application and user information will be added automatically.
79
+
80
+ ```
81
+ {
82
+ ...
83
+ "microservice_name": "service",
84
+ "microservice_namespace": "dev",
85
+ "virtualization": "docker",
86
+ "memory": 100,
87
+ ...
88
+ }
89
+
90
+ ```
91
+
92
+ ## Contributing
93
+
94
+ This is the first version of **oakestra-cli** and does not yet offer all features, also there
95
+ might be some bugs, if you find one please report it or fix it.
96
+
97
+ To add a new command create a function in the main.py file and add the `@app.command()` annotation to the function. If you want to create sub commands, create the command in the corresponding file.
98
+
@@ -0,0 +1,83 @@
1
+ # Oakestra CLI
2
+
3
+ > NM: Rename system commands to Oakestra
4
+
5
+ **oakestra-cli** is a very basic command line tool for controlling the EdgeIO framework.
6
+
7
+ ## Installation
8
+
9
+ Use the package manager [pip](https://pip.pypa.io/en/stable/) and the .whl file in the dist directory to install
10
+ edgeiocli.
11
+
12
+ ```bash
13
+ pip install edgeiocli-0.1.0-py3-none-any.whl
14
+ ```
15
+
16
+ ## Build
17
+
18
+ Use the package [poetry](https://python-poetry.org/) to build the tool and create a new .whl file.
19
+
20
+ Updates the dependency's:
21
+ ```bash
22
+ poetry update
23
+ ```
24
+
25
+ Installs the package:
26
+ ```bash
27
+ poetry install
28
+ ```
29
+
30
+ Creates the .whl file:
31
+ ```bash
32
+ poetry build
33
+ ```
34
+
35
+ ## Usage
36
+
37
+ ```bash
38
+ edgeiocli COMMAND [ARGS] [OPTIONS]
39
+
40
+ # Try 'edgeiocli --help' for help.
41
+ ```
42
+
43
+ ## Available Commands
44
+
45
+ - `login [USERNAME]` logs the user into the system and creates access token
46
+ - `logout` logs out the user and deletes the token
47
+ - `change-password` changes the password of the user
48
+ - `application [COMMAND]`
49
+ - `create` creates a new application
50
+ - `delete [APPLICATION_ID]` deletes a application
51
+ - `list-jobs [APPLICATION_ID]` displays all jobs in the application
52
+ - `list` displays all applications of the user
53
+ - `service [COMMAND]`
54
+ - `delete [SERVICE_ID]` deletes the job
55
+ - `create [PATH]` creates a new service
56
+ - `deploy [SERVICE_ID]` tries to deploy a service
57
+ - `user [COMMAND]`
58
+ - `create --role [Admin | Application_Provider | Infrastructure_Provider]` creates a new user
59
+ - `delete [USERNAME] deletes the user`
60
+ - `list` displays all user of the system
61
+ - `set-roles [USERNAME] --role [Admin | Application_Provider | Infrastructure_Provider]`
62
+
63
+ ## Deployment
64
+ The given json file should contain only a microservice configuration, as in the example, all the application and user information will be added automatically.
65
+
66
+ ```
67
+ {
68
+ ...
69
+ "microservice_name": "service",
70
+ "microservice_namespace": "dev",
71
+ "virtualization": "docker",
72
+ "memory": 100,
73
+ ...
74
+ }
75
+
76
+ ```
77
+
78
+ ## Contributing
79
+
80
+ This is the first version of **oakestra-cli** and does not yet offer all features, also there
81
+ might be some bugs, if you find one please report it or fix it.
82
+
83
+ To add a new command create a function in the main.py file and add the `@app.command()` annotation to the function. If you want to create sub commands, create the command in the corresponding file.
File without changes
File without changes
File without changes
@@ -0,0 +1,40 @@
1
+ import argparse
2
+ from typing import Any
3
+
4
+ from oak_cli.commands.apps.main import send_sla
5
+ from oak_cli.commands.services.deployment import deploy_new_instance
6
+ from oak_cli.utils.SLAs.common import KnownSLA
7
+ from oak_cli.utils.types import Subparsers
8
+
9
+
10
+ def _aux_create_application(args: Any) -> None:
11
+ apps = send_sla(args.sla)
12
+ if args.deploy:
13
+ for app in apps:
14
+ for service_id in app["microservices"]:
15
+ deploy_new_instance(service_id)
16
+
17
+
18
+ def prepare_applications_create_argparser(applications_subparsers: Subparsers) -> None:
19
+ HELP_TEXT = "creates a new application"
20
+ applications_create_parser = applications_subparsers.add_parser(
21
+ "create",
22
+ aliases=["c"],
23
+ help=HELP_TEXT,
24
+ description=HELP_TEXT,
25
+ formatter_class=argparse.RawTextHelpFormatter,
26
+ )
27
+ applications_create_parser.add_argument(
28
+ "-d",
29
+ "--deploy",
30
+ action="store_true",
31
+ help="deploy all services of the provided application(s)",
32
+ )
33
+ applications_create_parser.add_argument(
34
+ "sla",
35
+ help="creates an application based on a KnowsSLA (if is has its own enum)",
36
+ type=KnownSLA,
37
+ choices=KnownSLA,
38
+ default=KnownSLA.DEFAULT,
39
+ )
40
+ applications_create_parser.set_defaults(func=_aux_create_application)
@@ -0,0 +1,36 @@
1
+ import argparse
2
+ from typing import Any
3
+
4
+ from oak_cli.commands.apps.main import delete_all_applications, delete_application
5
+ from oak_cli.utils.types import Subparsers
6
+
7
+ _APP_ID_HELP_TEXT = """
8
+ if a (single) application ID is provided only that one app will be deleted
9
+ if 'all' is provided every application gets deleted"""
10
+
11
+ _DELETION_HELP_TEXT = "deletes one or all applications" + _APP_ID_HELP_TEXT
12
+
13
+
14
+ def prepare_applications_deletion_argparser(
15
+ applications_subparsers: Subparsers,
16
+ ) -> None:
17
+ def aux_delete_appliacations(args: Any):
18
+ if args.all:
19
+ delete_all_applications()
20
+ else:
21
+ delete_application(args.app_id)
22
+
23
+ applications_deletion_parser = applications_subparsers.add_parser(
24
+ "delete",
25
+ aliases=["d"],
26
+ help=_DELETION_HELP_TEXT,
27
+ description=_DELETION_HELP_TEXT,
28
+ formatter_class=argparse.RawTextHelpFormatter,
29
+ )
30
+ applications_deletion_parser.add_argument(
31
+ "-a", "--all", action="store_true", help="delete all applications"
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)
@@ -0,0 +1,29 @@
1
+ import argparse
2
+ from typing import Any
3
+
4
+ from oak_cli.args_parser.apps.create import prepare_applications_create_argparser
5
+ from oak_cli.args_parser.apps.delete import prepare_applications_deletion_argparser
6
+ from oak_cli.args_parser.apps.status import prepare_applications_display_argparser
7
+ from oak_cli.utils.types import Subparsers
8
+
9
+
10
+ def prepare_applications_argparsers(subparsers: Subparsers) -> None:
11
+ applications_parser = subparsers.add_parser(
12
+ "applications",
13
+ aliases=["a"],
14
+ help="command for application(s) related activities",
15
+ formatter_class=argparse.RawTextHelpFormatter,
16
+ )
17
+
18
+ def applications_parser_print_help(_: Any) -> None:
19
+ applications_parser.print_help()
20
+
21
+ applications_parser.set_defaults(func=applications_parser_print_help)
22
+
23
+ applications_subparsers = applications_parser.add_subparsers(
24
+ dest="applications commands",
25
+ )
26
+
27
+ prepare_applications_create_argparser(applications_subparsers)
28
+ prepare_applications_display_argparser(applications_subparsers)
29
+ prepare_applications_deletion_argparser(applications_subparsers)
@@ -0,0 +1,18 @@
1
+ import argparse
2
+
3
+ from oak_cli.commands.apps.status import display_current_applications
4
+ from oak_cli.utils.types import Subparsers
5
+
6
+
7
+ def prepare_applications_display_argparser(applications_subparsers: Subparsers) -> None:
8
+ HELP_TEXT = "displays the currently available/active applications"
9
+ applications_status_parser = applications_subparsers.add_parser(
10
+ "status",
11
+ aliases=["s"],
12
+ help=HELP_TEXT,
13
+ description=HELP_TEXT,
14
+ formatter_class=argparse.RawTextHelpFormatter,
15
+ )
16
+ applications_status_parser.set_defaults(
17
+ func=lambda _: display_current_applications()
18
+ )
@@ -0,0 +1,25 @@
1
+ # PYTHON_ARGCOMPLETE_OK
2
+
3
+ import argparse
4
+
5
+ import argcomplete
6
+
7
+ from oak_cli.args_parser.apps.main import prepare_applications_argparsers
8
+ from oak_cli.args_parser.services.main import prepare_services_argparsers
9
+
10
+
11
+ def parse_arguments_and_execute() -> None:
12
+ parser = argparse.ArgumentParser()
13
+
14
+ # 'dest' & 'required' are needed to ensure correct behavior if no arguments are passed.
15
+ subparsers = parser.add_subparsers(
16
+ dest="command",
17
+ required=True,
18
+ )
19
+
20
+ prepare_applications_argparsers(subparsers)
21
+ prepare_services_argparsers(subparsers)
22
+
23
+ argcomplete.autocomplete(parser)
24
+ args = parser.parse_args()
25
+ args.func(args)
File without changes
@@ -0,0 +1,53 @@
1
+ import argparse
2
+ from typing import Any
3
+
4
+ from oak_cli.commands.services.deployment import (
5
+ deploy_new_instance,
6
+ undeploy_all_instances_of_service,
7
+ undeploy_instance,
8
+ )
9
+ from oak_cli.utils.types import Subparsers
10
+
11
+
12
+ def prepare_services_deployment_argparser(services_subparsers: Subparsers) -> None:
13
+ HELP_TEXT = "deploys a service instance"
14
+ services_deployment_parser = services_subparsers.add_parser(
15
+ "deploy",
16
+ aliases=["d"],
17
+ help=HELP_TEXT,
18
+ description=HELP_TEXT,
19
+ formatter_class=argparse.RawTextHelpFormatter,
20
+ )
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
+ )
25
+
26
+
27
+ def prepare_services_undeployment_argparser(services_subparsers: Subparsers) -> None:
28
+ def _aux_undeploy(args: Any) -> None:
29
+ if args.all:
30
+ undeploy_all_instances_of_service(args.service_id)
31
+ elif args.instancenumber:
32
+ undeploy_instance(args.service_id, args.instancenumber)
33
+ else:
34
+ undeploy_instance(args.service_id)
35
+
36
+ HELP_TEXT = "undeploys service instances"
37
+ services_undeployment_parser = services_subparsers.add_parser(
38
+ "undeploy",
39
+ aliases=["u"],
40
+ help=HELP_TEXT,
41
+ description=HELP_TEXT,
42
+ formatter_class=argparse.RawTextHelpFormatter,
43
+ )
44
+ services_undeployment_parser.add_argument("service_id", type=str)
45
+ services_undeployment_parser.add_argument(
46
+ "--instancenumber",
47
+ "-i",
48
+ type=str,
49
+ )
50
+ services_undeployment_parser.add_argument(
51
+ "-a", "--all", action="store_true", help="undeploy all instances of the service"
52
+ )
53
+ services_undeployment_parser.set_defaults(func=_aux_undeploy)
@@ -0,0 +1,31 @@
1
+ import argparse
2
+ from typing import Any
3
+
4
+ from oak_cli.args_parser.services.deployment import (
5
+ prepare_services_deployment_argparser,
6
+ prepare_services_undeployment_argparser,
7
+ )
8
+ from oak_cli.args_parser.services.status import prepare_services_display_argparser
9
+ from oak_cli.utils.types import Subparsers
10
+
11
+
12
+ def prepare_services_argparsers(subparsers: Subparsers) -> None:
13
+ services_parser = subparsers.add_parser(
14
+ "services",
15
+ aliases=["s"],
16
+ help="command for service(s) related activities",
17
+ formatter_class=argparse.RawTextHelpFormatter,
18
+ )
19
+
20
+ def services_parser_print_help(_: Any) -> None:
21
+ services_parser.print_help()
22
+
23
+ services_parser.set_defaults(func=services_parser_print_help)
24
+
25
+ services_subparsers = services_parser.add_subparsers(
26
+ dest="services commands",
27
+ )
28
+
29
+ prepare_services_display_argparser(services_subparsers)
30
+ prepare_services_deployment_argparser(services_subparsers)
31
+ prepare_services_undeployment_argparser(services_subparsers)
@@ -0,0 +1,49 @@
1
+ import argparse
2
+ from typing import Any
3
+
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
+ )
9
+ from oak_cli.utils.types import Subparsers
10
+
11
+
12
+ def _aux_display_current_services(args: Any) -> None:
13
+ if not args.serviceid:
14
+ display_all_current_services(verbose=args.verbose, app_id=args.appid)
15
+ elif args.serviceid:
16
+ service = get_single_service(args.serviceid)
17
+ display_single_service(service, verbose=args.verbose)
18
+
19
+
20
+ def prepare_services_display_argparser(services_subparsers: Subparsers) -> None:
21
+ HELP_TEXT = "displays the currently available/active service(s)"
22
+ services_status_parser = services_subparsers.add_parser(
23
+ "status",
24
+ aliases=["s"],
25
+ help=HELP_TEXT,
26
+ description=HELP_TEXT,
27
+ formatter_class=argparse.RawTextHelpFormatter,
28
+ )
29
+ services_status_parser.add_argument(
30
+ "-a",
31
+ "--all",
32
+ help="displays all services",
33
+ action="store_true",
34
+ )
35
+ services_status_parser.add_argument(
36
+ "-v",
37
+ "--verbose",
38
+ action="store_true",
39
+ )
40
+ services_status_parser.add_argument(
41
+ "--appid",
42
+ type=str,
43
+ )
44
+ services_status_parser.add_argument(
45
+ "--serviceid",
46
+ "-i",
47
+ type=str,
48
+ )
49
+ services_status_parser.set_defaults(func=_aux_display_current_services)
File without changes
@@ -0,0 +1,60 @@
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"])
@@ -0,0 +1,21 @@
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
+ )
File without changes
@@ -0,0 +1,30 @@
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"])
@@ -0,0 +1,29 @@
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
@@ -0,0 +1,75 @@
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)
@@ -0,0 +1,14 @@
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()
File without changes
@@ -0,0 +1,13 @@
1
+ {
2
+ "sla_version": "v2.0",
3
+ "customerID": "Admin",
4
+ "applications": [
5
+ {
6
+ "applicationID": "",
7
+ "application_name": "blank",
8
+ "application_namespace": "blank",
9
+ "application_desc": "Blank app without any microservices",
10
+ "microservices": []
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,13 @@
1
+ import pathlib
2
+
3
+ from oak_cli.utils.common import get_oak_cli_path
4
+ from oak_cli.utils.types import CustomEnum
5
+
6
+
7
+ def get_SLAs_path() -> pathlib.Path:
8
+ return get_oak_cli_path() / "utils" / "SLAs"
9
+
10
+
11
+ class KnownSLA(CustomEnum):
12
+ DEFAULT = "default_app_with_services"
13
+ BLANK = "blank_app_without_services"
@@ -0,0 +1,57 @@
1
+ {
2
+ "sla_version": "v2.0",
3
+ "customerID": "Admin",
4
+ "applications": [
5
+ {
6
+ "applicationID": "",
7
+ "application_name": "clientsrvr",
8
+ "application_namespace": "test",
9
+ "application_desc": "Simple demo with curl client and Nginx server",
10
+ "microservices": [
11
+ {
12
+ "microserviceID": "",
13
+ "microservice_name": "curl",
14
+ "microservice_namespace": "test",
15
+ "virtualization": "container",
16
+ "cmd": [
17
+ "sh",
18
+ "-c",
19
+ "tail -f /dev/null"
20
+ ],
21
+ "memory": 100,
22
+ "vcpus": 1,
23
+ "vgpus": 0,
24
+ "vtpus": 0,
25
+ "bandwidth_in": 0,
26
+ "bandwidth_out": 0,
27
+ "storage": 0,
28
+ "code": "docker.io/curlimages/curl:7.82.0",
29
+ "state": "",
30
+ "port": "9080",
31
+ "added_files": []
32
+ },
33
+ {
34
+ "microserviceID": "",
35
+ "microservice_name": "nginx",
36
+ "microservice_namespace": "test",
37
+ "virtualization": "container",
38
+ "cmd": [],
39
+ "memory": 100,
40
+ "vcpus": 1,
41
+ "vgpus": 0,
42
+ "vtpus": 0,
43
+ "bandwidth_in": 0,
44
+ "bandwidth_out": 0,
45
+ "storage": 0,
46
+ "code": "docker.io/library/nginx:latest",
47
+ "state": "",
48
+ "port": "6080:60/tcp",
49
+ "addresses": {
50
+ "rr_ip": "10.30.30.30"
51
+ },
52
+ "added_files": []
53
+ }
54
+ ]
55
+ }
56
+ ]
57
+ }
File without changes
File without changes
@@ -0,0 +1,31 @@
1
+ import os
2
+
3
+ import requests
4
+
5
+ from oak_cli.utils.types import CustomEnum
6
+
7
+ GITHUB_PREFIX = "https://github.com/"
8
+ SYSTEM_MANAGER_URL = f"http://{os.environ.get('SYSTEM_MANAGER_URL')}:10000"
9
+
10
+
11
+ class HttpMethod(CustomEnum):
12
+ GET = "get"
13
+ POST = "post"
14
+ PUT = "put"
15
+ PATCH = "patch"
16
+ DELETE = "delete"
17
+
18
+ def call(cls, **kwargs) -> requests.Response:
19
+ method_map = {
20
+ cls.GET: requests.get,
21
+ cls.POST: requests.post,
22
+ cls.PUT: requests.put,
23
+ cls.PATCH: requests.patch,
24
+ cls.DELETE: requests.delete,
25
+ }
26
+ method = method_map.get(cls)
27
+
28
+ if method:
29
+ return method(**kwargs)
30
+ else:
31
+ raise ValueError(f"Unsupported HTTP method: {cls.value}")
@@ -0,0 +1,33 @@
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
@@ -0,0 +1,108 @@
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
@@ -0,0 +1,16 @@
1
+ import shlex
2
+ import subprocess
3
+
4
+
5
+ def handle_argcomplete() -> None:
6
+ # Note: Ideally this would only occur once in a dedicated postinstall script
7
+ # (similar to debian packages).
8
+ # However, python packages do not seem to allow postinstall scripts.
9
+ # The argcomplete command also is not too customizable to check if it is already installed,
10
+ # because it might behave differently depending on the OS.
11
+ with open("/dev/null", "w") as devnull:
12
+ subprocess.run(
13
+ shlex.split("activate-global-python-argcomplete -y --user"),
14
+ stdout=devnull,
15
+ stderr=devnull,
16
+ )
@@ -0,0 +1,6 @@
1
+ import pathlib
2
+
3
+
4
+ def get_oak_cli_path() -> pathlib.Path:
5
+ current_file = pathlib.Path(__file__).resolve()
6
+ return current_file.parent.parent
@@ -0,0 +1,47 @@
1
+ # Reference:
2
+ # https://alexandra-zaharia.github.io/posts/make-your-own-custom-color-formatter-with-python-logging/
3
+
4
+ import logging
5
+
6
+
7
+ class CustomFormatter(logging.Formatter):
8
+ """Logging colored formatter, adapted from https://stackoverflow.com/a/56944256/3638629"""
9
+
10
+ grey = "\x1b[38;21m"
11
+ yellow = "\x1b[38;5;226m"
12
+ red = "\x1b[38;5;196m"
13
+ bold_red = "\x1b[31;1m"
14
+ blue = "\x1b[38;5;33m"
15
+ light_blue = "\x1b[38;5;45m"
16
+ reset = "\x1b[0m"
17
+
18
+ def __init__(self, fmt):
19
+ super().__init__()
20
+ self.fmt = fmt
21
+ self.FORMATS = {
22
+ logging.DEBUG: self.grey + self.fmt + self.reset,
23
+ logging.INFO: self.light_blue + self.fmt + self.reset,
24
+ logging.WARNING: self.yellow + self.fmt + self.reset,
25
+ logging.ERROR: self.red + self.fmt + self.reset,
26
+ logging.CRITICAL: self.bold_red + self.fmt + self.reset,
27
+ }
28
+
29
+ def format(self, record):
30
+ log_fmt = self.FORMATS.get(record.levelno)
31
+ formatter = logging.Formatter(log_fmt)
32
+ return formatter.format(record)
33
+
34
+
35
+ _LOGGER_NAME = "logger"
36
+
37
+ _FORMAT = "%(message)s"
38
+
39
+ logger = logging.getLogger(_LOGGER_NAME)
40
+ logger.setLevel(logging.DEBUG)
41
+
42
+ _formatter = CustomFormatter(_FORMAT)
43
+
44
+ _stream_handler = logging.StreamHandler()
45
+ _stream_handler.setFormatter(_formatter)
46
+
47
+ logger.addHandler(_stream_handler)
@@ -0,0 +1,22 @@
1
+ import argparse
2
+ import enum
3
+
4
+
5
+ class CustomEnum(enum.Enum):
6
+ def __str__(self) -> str:
7
+ return self.value
8
+
9
+
10
+ Id = str
11
+ ServiceId = Id
12
+ ApplicationId = Id
13
+
14
+ Service = dict
15
+ Application = dict
16
+
17
+ SLA = dict
18
+ DbObject = dict
19
+
20
+
21
+ # Custom type for nicer type hints
22
+ Subparsers = argparse._SubParsersAction
@@ -0,0 +1,17 @@
1
+ [tool.poetry]
2
+ name = "oak_cli"
3
+ version = "0.2.5"
4
+ description = ""
5
+ authors = ["Alexander Malyuk <malyuk.alexander1999@gmail.com>"]
6
+ readme = "README.md"
7
+
8
+ [tool.poetry.dependencies]
9
+ python = "^3.10"
10
+ argcomplete = "^3.2.3"
11
+
12
+ [build-system]
13
+ requires = ["poetry-core"]
14
+ build-backend = "poetry.core.masonry.api"
15
+
16
+ [tool.poetry.scripts]
17
+ oak = "oak_cli.main:main"