falcon-toolkit 3.4.0__tar.gz → 3.4.2__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.
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/PKG-INFO +1 -1
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/falcon_toolkit/__init__.py +2 -1
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/falcon_toolkit/common/auth.py +3 -1
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/falcon_toolkit/common/auth_backends/default.py +1 -0
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/falcon_toolkit/common/auth_backends/public_mssp.py +40 -20
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/falcon_toolkit/common/auth_backends/public_single_cid.py +7 -5
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/falcon_toolkit/common/auth_backends/utils.py +4 -3
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/falcon_toolkit/common/cli.py +15 -12
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/falcon_toolkit/common/config.py +5 -6
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/falcon_toolkit/common/console_utils.py +6 -5
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/falcon_toolkit/common/constants.py +3 -2
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/falcon_toolkit/common/logging_config.py +5 -4
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/falcon_toolkit/common/meta.py +1 -3
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/falcon_toolkit/common/namespace.py +1 -3
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/falcon_toolkit/common/utils.py +4 -4
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/falcon_toolkit/containment/cli.py +62 -52
- falcon_toolkit-3.4.2/falcon_toolkit/containment/perform_containment.py +156 -0
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/falcon_toolkit/falcon.py +45 -31
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/falcon_toolkit/hosts/cli.py +27 -25
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/falcon_toolkit/hosts/host_search.py +48 -51
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/falcon_toolkit/maintenance_token/cli.py +73 -56
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/falcon_toolkit/maintenance_token/device_tokens.py +15 -10
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/falcon_toolkit/policies/cli.py +31 -33
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/falcon_toolkit/policies/constants.py +1 -0
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/falcon_toolkit/policies/container.py +8 -8
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/falcon_toolkit/policies/describe.py +16 -13
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/falcon_toolkit/shell/cli.py +58 -47
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/falcon_toolkit/shell/cmd_generators/reg.py +13 -12
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/falcon_toolkit/shell/parsers.py +234 -247
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/falcon_toolkit/shell/prompt.py +124 -155
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/falcon_toolkit/shell/refresh.py +1 -0
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/falcon_toolkit/shell/utils.py +2 -1
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/pyproject.toml +2 -1
- falcon_toolkit-3.4.0/falcon_toolkit/containment/perform_containment.py +0 -112
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/LICENSE.md +0 -0
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/README.md +0 -0
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/falcon_toolkit/common/__init__.py +0 -0
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/falcon_toolkit/common/auth_backends/__init__.py +0 -0
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/falcon_toolkit/containment/__init__.py +0 -0
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/falcon_toolkit/hosts/__init__.py +0 -0
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/falcon_toolkit/maintenance_token/__init__.py +0 -0
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/falcon_toolkit/policies/__init__.py +0 -0
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/falcon_toolkit/shell/__init__.py +0 -0
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/falcon_toolkit/shell/cmd_generators/__init__.py +0 -0
- {falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/falcon_toolkit/shell/cmd_generators/common.py +0 -0
|
@@ -5,6 +5,7 @@ Each authentication backend has to be able to return a Caracara Client object, w
|
|
|
5
5
|
hold a FalconPy authentication object. It will be possible to generate more of these backends for
|
|
6
6
|
other purposes, such as for authentication against alternative API gateways.
|
|
7
7
|
"""
|
|
8
|
+
|
|
8
9
|
from __future__ import annotations
|
|
9
10
|
from abc import (
|
|
10
11
|
abstractmethod,
|
|
@@ -17,6 +18,7 @@ from typing import (
|
|
|
17
18
|
|
|
18
19
|
if TYPE_CHECKING:
|
|
19
20
|
from caracara import Client
|
|
21
|
+
from click import Context
|
|
20
22
|
|
|
21
23
|
|
|
22
24
|
class AuthBackend(ABC):
|
|
@@ -50,7 +52,7 @@ class AuthBackend(ABC):
|
|
|
50
52
|
"""
|
|
51
53
|
|
|
52
54
|
@abstractmethod
|
|
53
|
-
def authenticate(self) -> Client:
|
|
55
|
+
def authenticate(self, ctx: Context) -> Client:
|
|
54
56
|
"""Return a complete OAuth2 object, ready for use with FalconPy."""
|
|
55
57
|
|
|
56
58
|
@abstractmethod
|
{falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/falcon_toolkit/common/auth_backends/default.py
RENAMED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
This file configures the default authentication backends.
|
|
4
4
|
"""
|
|
5
|
+
|
|
5
6
|
from falcon_toolkit.common.auth_backends.public_mssp import PublicCloudFlightControlParentCIDBackend
|
|
6
7
|
from falcon_toolkit.common.auth_backends.public_single_cid import PublicCloudSingleCIDBackend
|
|
7
8
|
|
{falcon_toolkit-3.4.0 → falcon_toolkit-3.4.2}/falcon_toolkit/common/auth_backends/public_mssp.py
RENAMED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
This authentication backend can take public cloud API keys (US-1, US-2, EU-1), and will ask the
|
|
4
4
|
user which child CID to authenticate against.
|
|
5
5
|
"""
|
|
6
|
-
import os
|
|
7
6
|
|
|
8
7
|
from typing import Dict, Optional
|
|
9
8
|
|
|
9
|
+
import click
|
|
10
10
|
import keyring
|
|
11
11
|
|
|
12
12
|
from caracara import Client
|
|
@@ -93,15 +93,17 @@ class PublicCloudFlightControlParentCIDBackend(AuthBackend):
|
|
|
93
93
|
keyring.
|
|
94
94
|
"""
|
|
95
95
|
config: Dict[str, object] = {}
|
|
96
|
-
config[
|
|
97
|
-
config[
|
|
98
|
-
config[
|
|
99
|
-
config[
|
|
96
|
+
config["client_id"] = self.client_id
|
|
97
|
+
config["cloud_name"] = self.cloud_name
|
|
98
|
+
config["ssl_verify"] = self.ssl_verify
|
|
99
|
+
config["proxy"] = self.proxy
|
|
100
100
|
|
|
101
101
|
return config
|
|
102
102
|
|
|
103
|
-
def authenticate(self) -> Client:
|
|
103
|
+
def authenticate(self, ctx: click.Context) -> Client:
|
|
104
104
|
"""Log the Toolkit into Falcon using the settings and keys configured at instance setup."""
|
|
105
|
+
chosen_cid_str = ctx.obj["cid"]
|
|
106
|
+
|
|
105
107
|
parent_client = Client(
|
|
106
108
|
client_id=self.client_id,
|
|
107
109
|
client_secret=self.client_secret,
|
|
@@ -110,21 +112,39 @@ class PublicCloudFlightControlParentCIDBackend(AuthBackend):
|
|
|
110
112
|
proxy=self.proxy,
|
|
111
113
|
)
|
|
112
114
|
child_cids = parent_client.flight_control.get_child_cids()
|
|
113
|
-
chosen_cid_str = os.environ.get("FALCON_MSSP_CHILD_CID")
|
|
114
|
-
if chosen_cid_str and chosen_cid_str.lower() in child_cids:
|
|
115
|
-
chosen_cid = parent_client.flight_control.get_child_cid_data(
|
|
116
|
-
cids=[chosen_cid_str]
|
|
117
|
-
)[chosen_cid_str]
|
|
118
|
-
else:
|
|
119
|
-
child_cids_data = parent_client.flight_control.get_child_cid_data(cids=child_cids)
|
|
120
|
-
chosen_cid_str = choose_cid(
|
|
121
|
-
cids=child_cids_data,
|
|
122
|
-
prompt_text="MSSP Child CID Search"
|
|
123
|
-
)
|
|
124
|
-
chosen_cid = child_cids_data[chosen_cid_str]
|
|
125
115
|
|
|
126
|
-
|
|
127
|
-
|
|
116
|
+
if chosen_cid_str and chosen_cid_str in child_cids:
|
|
117
|
+
click.echo(
|
|
118
|
+
click.style("Valid member CID ", fg="blue")
|
|
119
|
+
+ click.style(chosen_cid_str, fg="blue", bold=True)
|
|
120
|
+
+ click.style(" provided. Skipping CID selection.", fg="blue")
|
|
121
|
+
)
|
|
122
|
+
elif chosen_cid_str:
|
|
123
|
+
click.echo(click.style("An invalid CID was provided at the command line.", fg="red"))
|
|
124
|
+
click.echo("Please search for an alternative CID:")
|
|
125
|
+
# Blank out a bad value
|
|
126
|
+
chosen_cid_str = None
|
|
127
|
+
|
|
128
|
+
if not chosen_cid_str:
|
|
129
|
+
if chosen_cid_str and chosen_cid_str.lower() in child_cids:
|
|
130
|
+
chosen_cid = parent_client.flight_control.get_child_cid_data(
|
|
131
|
+
cids=[chosen_cid_str],
|
|
132
|
+
)[chosen_cid_str]
|
|
133
|
+
else:
|
|
134
|
+
child_cids_data = parent_client.flight_control.get_child_cid_data(cids=child_cids)
|
|
135
|
+
if not child_cids_data:
|
|
136
|
+
raise RuntimeError(
|
|
137
|
+
"No child CIDs accessible. Please check your API credentials."
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
chosen_cid_str = choose_cid(
|
|
141
|
+
cids=child_cids_data,
|
|
142
|
+
prompt_text="MSSP Child CID Search",
|
|
143
|
+
)
|
|
144
|
+
chosen_cid = child_cids_data[chosen_cid_str]
|
|
145
|
+
|
|
146
|
+
chosen_cid_name = chosen_cid["name"]
|
|
147
|
+
print(f"Connecting to {chosen_cid_name}")
|
|
128
148
|
|
|
129
149
|
client = Client(
|
|
130
150
|
client_id=self.client_id,
|
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
This authentication backend can take public cloud API keys (US-1, US-2, EU-1), and will return
|
|
4
4
|
an OAuth2 object suitable for authenticating with FalconPy.
|
|
5
5
|
"""
|
|
6
|
+
|
|
6
7
|
from typing import Dict, Optional
|
|
7
8
|
|
|
9
|
+
import click
|
|
8
10
|
import keyring
|
|
9
11
|
|
|
10
12
|
from caracara import Client
|
|
@@ -81,14 +83,14 @@ class PublicCloudSingleCIDBackend(AuthBackend):
|
|
|
81
83
|
keyring.
|
|
82
84
|
"""
|
|
83
85
|
config: Dict[str, object] = {}
|
|
84
|
-
config[
|
|
85
|
-
config[
|
|
86
|
-
config[
|
|
87
|
-
config[
|
|
86
|
+
config["client_id"] = self.client_id
|
|
87
|
+
config["cloud_name"] = self.cloud_name
|
|
88
|
+
config["ssl_verify"] = self.ssl_verify
|
|
89
|
+
config["proxy"] = self.proxy
|
|
88
90
|
|
|
89
91
|
return config
|
|
90
92
|
|
|
91
|
-
def authenticate(self) -> Client:
|
|
93
|
+
def authenticate(self, ctx: click.Context) -> Client:
|
|
92
94
|
"""Log the Toolkit into Falcon using the settings and keys configured at instance setup."""
|
|
93
95
|
client = Client(
|
|
94
96
|
client_id=self.client_id,
|
|
@@ -8,6 +8,7 @@ This file provides:
|
|
|
8
8
|
- A cloud selection function to allow a user to choose a cloud via Prompt Toolkit
|
|
9
9
|
- Advanced options configuration for overriding cloud, TLS validation, etc.
|
|
10
10
|
"""
|
|
11
|
+
|
|
11
12
|
from typing import (
|
|
12
13
|
Dict,
|
|
13
14
|
List,
|
|
@@ -56,8 +57,8 @@ class AdvancedOptionsType(NamedTuple):
|
|
|
56
57
|
def advanced_options_wizard() -> AdvancedOptionsType:
|
|
57
58
|
"""Define advanced connection options and return an AdvancedOptionsType."""
|
|
58
59
|
advanced_options_input = fancy_input("Do you want to configure more options? [y/n]: ")
|
|
59
|
-
if advanced_options_input not in (
|
|
60
|
-
return AdvancedOptionsType(
|
|
60
|
+
if advanced_options_input not in ("y", "Y"):
|
|
61
|
+
return AdvancedOptionsType("auto", True, None)
|
|
61
62
|
|
|
62
63
|
cloud_name = cloud_choice()
|
|
63
64
|
|
|
@@ -76,7 +77,7 @@ def advanced_options_wizard() -> AdvancedOptionsType:
|
|
|
76
77
|
proxy_url_input = fancy_input("HTTPS proxy URL (leave blank if not needed): ", loop=False)
|
|
77
78
|
|
|
78
79
|
if proxy_url_input:
|
|
79
|
-
proxy_dict = {
|
|
80
|
+
proxy_dict = {"https", proxy_url_input}
|
|
80
81
|
|
|
81
82
|
advanced_options_result = AdvancedOptionsType(cloud_name, ssl_verify, proxy_dict)
|
|
82
83
|
return advanced_options_result
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Falcon Toolkit: Common CLI Functions."""
|
|
2
|
+
|
|
2
3
|
import sys
|
|
3
4
|
|
|
4
5
|
from typing import List
|
|
@@ -13,25 +14,27 @@ from falcon_toolkit.common.config import FalconToolkitConfig
|
|
|
13
14
|
|
|
14
15
|
def get_instance(ctx: click.Context):
|
|
15
16
|
"""Load a specified Falcon instance configuration ready for use."""
|
|
16
|
-
config: FalconToolkitConfig = ctx.obj[
|
|
17
|
-
profile_name: str = ctx.obj[
|
|
17
|
+
config: FalconToolkitConfig = ctx.obj["config"]
|
|
18
|
+
profile_name: str = ctx.obj["profile_name"]
|
|
18
19
|
|
|
19
20
|
if profile_name in config.instances:
|
|
20
21
|
profile = config.instances[profile_name]
|
|
21
22
|
elif not profile_name and len(config.instances) == 1:
|
|
22
23
|
profile = list(config.instances.values())[0]
|
|
23
24
|
elif not config.instances:
|
|
24
|
-
click.echo(click.style("No profiles are configured. Please set one up first.", fg=
|
|
25
|
+
click.echo(click.style("No profiles are configured. Please set one up first.", fg="red"))
|
|
25
26
|
sys.exit(1)
|
|
26
27
|
elif not profile_name:
|
|
27
|
-
click.echo(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
click.echo(
|
|
29
|
+
click.style(
|
|
30
|
+
"Multiple profiles are configured, so you must use the -p/--profile option "
|
|
31
|
+
"to choose a profile to execute this tool with.",
|
|
32
|
+
fg="red",
|
|
33
|
+
)
|
|
34
|
+
)
|
|
32
35
|
sys.exit(1)
|
|
33
36
|
else:
|
|
34
|
-
click.echo(click.style(f"The profile {profile_name} does not exist.", fg=
|
|
37
|
+
click.echo(click.style(f"The profile {profile_name} does not exist.", fg="red"))
|
|
35
38
|
sys.exit(1)
|
|
36
39
|
|
|
37
40
|
return profile
|
|
@@ -39,13 +42,13 @@ def get_instance(ctx: click.Context):
|
|
|
39
42
|
|
|
40
43
|
def parse_cli_filters(filter_kv_strings: List[str], client: Client) -> FalconFilter:
|
|
41
44
|
"""Parse consecutive chains of -f filters into a FalconFilter object for later use."""
|
|
42
|
-
filters = client.FalconFilter(dialect=
|
|
45
|
+
filters = client.FalconFilter(dialect="hosts")
|
|
43
46
|
for filter_kv_string in filter_kv_strings:
|
|
44
|
-
if
|
|
47
|
+
if "=" not in filter_kv_string:
|
|
45
48
|
raise ValueError("Filter key=value string is in the wrong format")
|
|
46
49
|
first_equals = filter_kv_string.index("=")
|
|
47
50
|
filter_key = filter_kv_string[0:first_equals]
|
|
48
|
-
filter_value = filter_kv_string[first_equals + 1:]
|
|
51
|
+
filter_value = filter_kv_string[first_equals + 1 :]
|
|
49
52
|
filters.create_new_filter_from_kv_string(filter_key, filter_value)
|
|
50
53
|
|
|
51
54
|
return filters
|
|
@@ -4,6 +4,7 @@ Each instance configuration contains the logic required to log in to Falcon.
|
|
|
4
4
|
These configurations are stored in a wider config file stored within the configuration
|
|
5
5
|
path (usually ~/FalconToolkit/FalconToolkit.json).
|
|
6
6
|
"""
|
|
7
|
+
|
|
7
8
|
from __future__ import annotations
|
|
8
9
|
import importlib.util
|
|
9
10
|
import json
|
|
@@ -63,9 +64,7 @@ class FalconInstanceConfig:
|
|
|
63
64
|
break
|
|
64
65
|
|
|
65
66
|
if not matching_auth_backend:
|
|
66
|
-
raise ValueError(
|
|
67
|
-
f"Auth backend {auth_backend_name} is not loaded or does not exist"
|
|
68
|
-
)
|
|
67
|
+
raise ValueError(f"Auth backend {auth_backend_name} is not loaded or does not exist")
|
|
69
68
|
|
|
70
69
|
auth_backend_config = auth.get("backend_config")
|
|
71
70
|
if not auth_backend_config:
|
|
@@ -81,7 +80,7 @@ class FalconInstanceConfig:
|
|
|
81
80
|
"auth": {
|
|
82
81
|
"backend_name": self.auth_backend.simple_name,
|
|
83
82
|
"backend_config": self.auth_backend.dump_config(),
|
|
84
|
-
}
|
|
83
|
+
},
|
|
85
84
|
}
|
|
86
85
|
|
|
87
86
|
|
|
@@ -113,7 +112,7 @@ class FalconToolkitConfig:
|
|
|
113
112
|
"""Load a Falcon Toolkit configuration file from disk."""
|
|
114
113
|
self.config_file_path = os.path.join(config_path, CONFIG_FILENAME)
|
|
115
114
|
if os.path.exists(self.config_file_path):
|
|
116
|
-
with open(self.config_file_path,
|
|
115
|
+
with open(self.config_file_path, "rb") as config_file_handle:
|
|
117
116
|
config_data = json.load(config_file_handle)
|
|
118
117
|
else:
|
|
119
118
|
config_data = {}
|
|
@@ -139,7 +138,7 @@ class FalconToolkitConfig:
|
|
|
139
138
|
"auth_backends": self.additional_auth_backend_paths,
|
|
140
139
|
"instances": [x.dump_config() for x in list(self.instances.values())],
|
|
141
140
|
}
|
|
142
|
-
with open(self.config_file_path,
|
|
141
|
+
with open(self.config_file_path, "w", encoding="utf8") as config_file_handle:
|
|
143
142
|
json.dump(config_data, config_file_handle, sort_keys=True, indent=4)
|
|
144
143
|
|
|
145
144
|
def add_instance(self):
|
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
This file contains tools to help with displaying data on the console in a user-friendly,
|
|
4
4
|
colourful and/or helpful way.
|
|
5
5
|
"""
|
|
6
|
+
|
|
6
7
|
import platform
|
|
7
8
|
|
|
8
9
|
|
|
9
|
-
ESC =
|
|
10
|
-
OSC = ESC +
|
|
11
|
-
ST = ESC +
|
|
10
|
+
ESC = "\033"
|
|
11
|
+
OSC = ESC + "]"
|
|
12
|
+
ST = ESC + "\\"
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
def build_hyperlink(target: str, text: str, link_id: str = None):
|
|
@@ -17,7 +18,7 @@ def build_hyperlink(target: str, text: str, link_id: str = None):
|
|
|
17
18
|
id_str = "id=" + link_id
|
|
18
19
|
else:
|
|
19
20
|
id_str = ""
|
|
20
|
-
return f
|
|
21
|
+
return f"{OSC}8;{id_str};{target}{ST}{text}{OSC}8;;{ST}"
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
def build_file_hyperlink(file_path: str, text: str, link_id: str = None):
|
|
@@ -27,7 +28,7 @@ def build_file_hyperlink(file_path: str, text: str, link_id: str = None):
|
|
|
27
28
|
else:
|
|
28
29
|
hostname = platform.node()
|
|
29
30
|
|
|
30
|
-
if file_path.startswith(
|
|
31
|
+
if file_path.startswith("/"):
|
|
31
32
|
file_path = file_path[1:]
|
|
32
33
|
|
|
33
34
|
uri_file_path = f"file://{hostname}/{file_path}"
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Falcon Toolkit: Common Constants."""
|
|
2
|
+
|
|
2
3
|
import os
|
|
3
4
|
|
|
4
5
|
import platformdirs
|
|
@@ -16,6 +17,6 @@ OLD_DEFAULT_CONFIG_DIR = os.path.expanduser("~/FalconToolkit")
|
|
|
16
17
|
|
|
17
18
|
# Logging
|
|
18
19
|
LOG_CONSOLE_FORMATTER = "%(message)s"
|
|
19
|
-
LOG_FILE_DATE_FORMAT =
|
|
20
|
-
LOG_FILE_OUTPUT_FORMAT =
|
|
20
|
+
LOG_FILE_DATE_FORMAT = "%Y-%m-%dT%H:%M:%S%z"
|
|
21
|
+
LOG_FILE_OUTPUT_FORMAT = "%(asctime)s %(name)-24s %(levelname)-8s %(message)s"
|
|
21
22
|
LOG_SUB_DIR = "logs"
|
|
@@ -4,6 +4,7 @@ The toolkit provides extremely detailed logging. This class configures the globa
|
|
|
4
4
|
capture logs from both the toolkit itself, as well as Caracara behind the scenes.
|
|
5
5
|
All logs are compressed using gzip to keep file sizes down.
|
|
6
6
|
"""
|
|
7
|
+
|
|
7
8
|
import datetime
|
|
8
9
|
import gzip
|
|
9
10
|
import logging
|
|
@@ -48,7 +49,7 @@ def configure_logger(
|
|
|
48
49
|
# Gzip compress the main log file on the fly via a memory stream.
|
|
49
50
|
# This is required to avoid the gzip file from being corrupted if the
|
|
50
51
|
# the script ends prematurely (e.g, it crashes).
|
|
51
|
-
log_output_file = gzip.open(log_filepath, mode=
|
|
52
|
+
log_output_file = gzip.open(log_filepath, mode="wt", encoding="utf-8")
|
|
52
53
|
stream_handler = logging.StreamHandler(log_output_file)
|
|
53
54
|
memory_handler = logging.handlers.MemoryHandler(
|
|
54
55
|
capacity=1024,
|
|
@@ -60,7 +61,7 @@ def configure_logger(
|
|
|
60
61
|
datefmt=LOG_FILE_DATE_FORMAT,
|
|
61
62
|
format=LOG_FILE_OUTPUT_FORMAT,
|
|
62
63
|
handlers=[memory_handler],
|
|
63
|
-
encoding=
|
|
64
|
+
encoding="utf-8",
|
|
64
65
|
)
|
|
65
66
|
else:
|
|
66
67
|
logging.basicConfig(
|
|
@@ -68,14 +69,14 @@ def configure_logger(
|
|
|
68
69
|
format=LOG_FILE_OUTPUT_FORMAT,
|
|
69
70
|
datefmt=LOG_FILE_DATE_FORMAT,
|
|
70
71
|
filename=log_filepath,
|
|
71
|
-
filemode=
|
|
72
|
+
filemode="wt",
|
|
72
73
|
)
|
|
73
74
|
|
|
74
75
|
console = logging.StreamHandler()
|
|
75
76
|
console.setLevel(level=log_level)
|
|
76
77
|
console_formatter = logging.Formatter(LOG_CONSOLE_FORMATTER)
|
|
77
78
|
console.setFormatter(console_formatter)
|
|
78
|
-
logging.getLogger(
|
|
79
|
+
logging.getLogger("").addHandler(console)
|
|
79
80
|
|
|
80
81
|
logging.debug("Logger configured")
|
|
81
82
|
return log_filepath_base
|
|
@@ -6,6 +6,4 @@ This is where all functions and variable setting that are based on application m
|
|
|
6
6
|
import pkg_resources
|
|
7
7
|
|
|
8
8
|
# Derive the version via pkg_resources, which is populated based on the version in pyproject.toml
|
|
9
|
-
__version__ = pkg_resources.get_distribution(
|
|
10
|
-
__name__.split(".", maxsplit=1)[0]
|
|
11
|
-
).version
|
|
9
|
+
__version__ = pkg_resources.get_distribution(__name__.split(".", maxsplit=1)[0]).version
|
|
@@ -38,9 +38,7 @@ class FalconRecursiveNamespace:
|
|
|
38
38
|
|
|
39
39
|
def __setitem__(self, key, value):
|
|
40
40
|
"""Assign a key to a value in the namespace."""
|
|
41
|
-
self.__init__(
|
|
42
|
-
** {key: value}
|
|
43
|
-
)
|
|
41
|
+
self.__init__(**{key: value})
|
|
44
42
|
|
|
45
43
|
def __delitem__(self, key):
|
|
46
44
|
"""Delete an item from the namespace."""
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
This file is a catch-all for small code snippets that can be shared across the various sub-modules
|
|
4
4
|
of the application.
|
|
5
5
|
"""
|
|
6
|
+
|
|
6
7
|
import os
|
|
7
8
|
|
|
8
9
|
from typing import Dict, Iterable
|
|
@@ -21,8 +22,7 @@ from falcon_toolkit.common.constants import LOG_SUB_DIR
|
|
|
21
22
|
def fancy_input(prompt_text: str, loop: bool = True):
|
|
22
23
|
"""Request user input (with colour). Optionally loop until the input is not blank."""
|
|
23
24
|
inputted = False
|
|
24
|
-
colour_prompt = Style.BRIGHT + Fore.BLUE +
|
|
25
|
-
prompt_text + Fore.RESET + Style.RESET_ALL
|
|
25
|
+
colour_prompt = Style.BRIGHT + Fore.BLUE + prompt_text + Fore.RESET + Style.RESET_ALL
|
|
26
26
|
|
|
27
27
|
while not inputted:
|
|
28
28
|
data = input(colour_prompt)
|
|
@@ -62,11 +62,11 @@ def filename_safe_string(unsafe_string: str) -> str:
|
|
|
62
62
|
This function is heavily inspired by https://stackoverflow.com/a/7406369.
|
|
63
63
|
"""
|
|
64
64
|
safe_string = "".join(
|
|
65
|
-
[c for c in unsafe_string if c.isalpha() or c.isdigit() or c ==
|
|
65
|
+
[c for c in unsafe_string if c.isalpha() or c.isdigit() or c == " "]
|
|
66
66
|
).rstrip()
|
|
67
67
|
|
|
68
68
|
# Replace spaces with underscores to match the general format of the log filename
|
|
69
|
-
clean_string = safe_string.replace(
|
|
69
|
+
clean_string = safe_string.replace(" ", "_")
|
|
70
70
|
|
|
71
71
|
return clean_string
|
|
72
72
|
|