ecs_files_composer 0.9.5__py3-none-any.whl
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.
- ecs_files_composer/__init__.py +8 -0
- ecs_files_composer/aws_mgmt.py +246 -0
- ecs_files_composer/certbot_aws_store.py +47 -0
- ecs_files_composer/certificates_mgmt.py +116 -0
- ecs_files_composer/cli.py +132 -0
- ecs_files_composer/common.py +57 -0
- ecs_files_composer/ecs_files_composer.py +153 -0
- ecs_files_composer/envsubst.py +45 -0
- ecs_files_composer/files_mgmt.py +285 -0
- ecs_files_composer/input.py +213 -0
- ecs_files_composer/jinja2_filters/__init__.py +179 -0
- ecs_files_composer-0.9.5.dist-info/LICENSE +373 -0
- ecs_files_composer-0.9.5.dist-info/METADATA +116 -0
- ecs_files_composer-0.9.5.dist-info/RECORD +16 -0
- ecs_files_composer-0.9.5.dist-info/WHEEL +4 -0
- ecs_files_composer-0.9.5.dist-info/entry_points.txt +4 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
# Copyright 2020-2022 John Mille<john@compose-x.io>
|
|
3
|
+
|
|
4
|
+
"""AWS module."""
|
|
5
|
+
|
|
6
|
+
import re
|
|
7
|
+
|
|
8
|
+
import boto3
|
|
9
|
+
from boto3.session import Session
|
|
10
|
+
from botocore.exceptions import ClientError
|
|
11
|
+
|
|
12
|
+
from ecs_files_composer import input
|
|
13
|
+
from ecs_files_composer.common import LOG
|
|
14
|
+
from ecs_files_composer.envsubst import expandvars
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def create_session_from_creds(tmp_creds: dict, region: str = None):
|
|
18
|
+
"""
|
|
19
|
+
Function to easily convert the AssumeRole reply into a boto3 session
|
|
20
|
+
:param tmp_creds:
|
|
21
|
+
:return:
|
|
22
|
+
:rtype boto3.session.Session
|
|
23
|
+
"""
|
|
24
|
+
creds = tmp_creds["Credentials"]
|
|
25
|
+
params = {
|
|
26
|
+
"aws_access_key_id": creds["AccessKeyId"],
|
|
27
|
+
"aws_secret_access_key": creds["SecretAccessKey"],
|
|
28
|
+
"aws_session_token": creds["SessionToken"],
|
|
29
|
+
}
|
|
30
|
+
if region:
|
|
31
|
+
params["region_name"] = region
|
|
32
|
+
return boto3.session.Session(**params)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def set_session_from_iam_object(iam_config_object, source_session: Session = None):
|
|
36
|
+
"""
|
|
37
|
+
Function to define the client session based on config input
|
|
38
|
+
|
|
39
|
+
:param ecs_files_composer.input.IamOverrideDef iam_config_object:
|
|
40
|
+
:param source_session:
|
|
41
|
+
:return: boto session
|
|
42
|
+
:rtype: boto3.session.Session
|
|
43
|
+
"""
|
|
44
|
+
if source_session is None:
|
|
45
|
+
source_session = boto3.Session()
|
|
46
|
+
if not iam_config_object.access_key_id and not iam_config_object.secret_access_key:
|
|
47
|
+
params = {
|
|
48
|
+
"RoleArn": iam_config_object.role_arn,
|
|
49
|
+
"RoleSessionName": f"{iam_config_object.session_name}@AwsResourceHandlerInit",
|
|
50
|
+
}
|
|
51
|
+
if iam_config_object.external_id:
|
|
52
|
+
params["ExternalId"] = iam_config_object.external_id
|
|
53
|
+
tmp_creds = source_session.client("sts").assume_role(**params)
|
|
54
|
+
client_session = create_session_from_creds(
|
|
55
|
+
tmp_creds, region=iam_config_object.region_name
|
|
56
|
+
)
|
|
57
|
+
else:
|
|
58
|
+
client_session = boto3.session.Session(
|
|
59
|
+
aws_access_key_id=iam_config_object.access_key_id,
|
|
60
|
+
aws_secret_access_key=iam_config_object.secret_access_key,
|
|
61
|
+
aws_session_token=iam_config_object.session_token
|
|
62
|
+
if iam_config_object.session_token
|
|
63
|
+
else None,
|
|
64
|
+
)
|
|
65
|
+
return client_session
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class AwsResourceHandler:
|
|
69
|
+
"""
|
|
70
|
+
Class to handle all AWS related credentials init.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
def __init__(
|
|
74
|
+
self,
|
|
75
|
+
role_arn=None,
|
|
76
|
+
external_id=None,
|
|
77
|
+
region=None,
|
|
78
|
+
iam_config_object=None,
|
|
79
|
+
client_session_override=None,
|
|
80
|
+
):
|
|
81
|
+
"""
|
|
82
|
+
:param str role_arn:
|
|
83
|
+
:param str external_id:
|
|
84
|
+
:param str region:
|
|
85
|
+
:param ecs_files_composer.input.IamOverrideDef iam_config_object:
|
|
86
|
+
"""
|
|
87
|
+
self.session = Session()
|
|
88
|
+
self.client_session = Session()
|
|
89
|
+
if client_session_override:
|
|
90
|
+
self.client_session = client_session_override
|
|
91
|
+
elif not client_session_override and (role_arn or iam_config_object):
|
|
92
|
+
if role_arn and not iam_config_object:
|
|
93
|
+
params = {
|
|
94
|
+
"RoleArn": role_arn,
|
|
95
|
+
"RoleSessionName": "EcsConfigComposer@AwsResourceHandlerInit",
|
|
96
|
+
}
|
|
97
|
+
if external_id:
|
|
98
|
+
params["ExternalId"] = external_id
|
|
99
|
+
tmp_creds = self.session.client("sts").assume_role(**params)
|
|
100
|
+
self.client_session = create_session_from_creds(
|
|
101
|
+
tmp_creds, region=region
|
|
102
|
+
)
|
|
103
|
+
elif (
|
|
104
|
+
iam_config_object
|
|
105
|
+
and hasattr(iam_config_object, "role_arn")
|
|
106
|
+
and iam_config_object.role_arn
|
|
107
|
+
):
|
|
108
|
+
self.client_session = set_session_from_iam_object(
|
|
109
|
+
iam_config_object, self.session
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class S3Fetcher(AwsResourceHandler):
|
|
114
|
+
"""
|
|
115
|
+
Class to handle S3 actions
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
bucket_re = re.compile(r"^s3://(?P<bucket>[a-zA-Z\d\-.]+)/(?P<key>[\S]+)$")
|
|
119
|
+
compose_x_re = re.compile(r"^(?P<bucket>[a-zA-Z\d\-.]+)::(?P<key>[\S]+)$")
|
|
120
|
+
|
|
121
|
+
def __init__(
|
|
122
|
+
self,
|
|
123
|
+
role_arn=None,
|
|
124
|
+
external_id=None,
|
|
125
|
+
region=None,
|
|
126
|
+
iam_config_object=None,
|
|
127
|
+
client_session_override=None,
|
|
128
|
+
):
|
|
129
|
+
super().__init__(
|
|
130
|
+
role_arn, external_id, region, iam_config_object, client_session_override
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def client(self):
|
|
135
|
+
return self.client_session.client("s3")
|
|
136
|
+
|
|
137
|
+
def get_content(
|
|
138
|
+
self,
|
|
139
|
+
s3_uri: str = None,
|
|
140
|
+
s3_bucket: str = None,
|
|
141
|
+
s3_key: str = None,
|
|
142
|
+
composex_uri: str = None,
|
|
143
|
+
):
|
|
144
|
+
"""
|
|
145
|
+
Retrieves a file in a temp dir and returns content
|
|
146
|
+
|
|
147
|
+
:return: The Stream Body for the file, allowing to do various things
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
if s3_uri and self.bucket_re.match(s3_uri):
|
|
151
|
+
s3_bucket = self.bucket_re.match(s3_uri).group("bucket")
|
|
152
|
+
s3_key = self.bucket_re.match(s3_uri).group("key")
|
|
153
|
+
elif composex_uri and self.compose_x_re.match(composex_uri):
|
|
154
|
+
s3_bucket = self.compose_x_re.match(composex_uri).group("bucket")
|
|
155
|
+
s3_key = self.compose_x_re.match(composex_uri).group("key")
|
|
156
|
+
try:
|
|
157
|
+
file_r = self.client.get_object(Bucket=s3_bucket, Key=s3_key)
|
|
158
|
+
file_content = file_r["Body"]
|
|
159
|
+
return file_content
|
|
160
|
+
except self.client.exceptions.NoSuchKey:
|
|
161
|
+
LOG.error(f"Failed to download the file {s3_key} from bucket {s3_bucket}")
|
|
162
|
+
raise
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class SsmFetcher(AwsResourceHandler):
|
|
166
|
+
"""
|
|
167
|
+
Class to handle SSM actions
|
|
168
|
+
"""
|
|
169
|
+
|
|
170
|
+
arn_re = re.compile(
|
|
171
|
+
r"^arn:aws(?:-[a-z]+)?:ssm:[\S]+:[\d]{12}:parameter(?P<name>/[\S]+)$"
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
def __init__(
|
|
175
|
+
self,
|
|
176
|
+
role_arn=None,
|
|
177
|
+
external_id=None,
|
|
178
|
+
region=None,
|
|
179
|
+
iam_config_object=None,
|
|
180
|
+
client_session_override=None,
|
|
181
|
+
):
|
|
182
|
+
super().__init__(
|
|
183
|
+
role_arn, external_id, region, iam_config_object, client_session_override
|
|
184
|
+
)
|
|
185
|
+
self.client = self.client_session.client("ssm")
|
|
186
|
+
|
|
187
|
+
def get_content(self, parameter_name):
|
|
188
|
+
"""
|
|
189
|
+
Import the Content of a given parameter
|
|
190
|
+
If the parameter name is a valid ARN, parses and uses the name from ARN
|
|
191
|
+
|
|
192
|
+
:param parameter_name:
|
|
193
|
+
:return:
|
|
194
|
+
"""
|
|
195
|
+
if self.arn_re.match(parameter_name):
|
|
196
|
+
parameter_name = self.arn_re.match(parameter_name).group("name")
|
|
197
|
+
try:
|
|
198
|
+
parameter = self.client.get_parameter(
|
|
199
|
+
Name=parameter_name, WithDecryption=True
|
|
200
|
+
)
|
|
201
|
+
return parameter["Parameter"]["Value"]
|
|
202
|
+
except self.client.exceptions:
|
|
203
|
+
raise
|
|
204
|
+
except ClientError:
|
|
205
|
+
raise
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class SecretFetcher(AwsResourceHandler):
|
|
209
|
+
"""
|
|
210
|
+
Class to handle Secret Manager actions
|
|
211
|
+
"""
|
|
212
|
+
|
|
213
|
+
def __init__(
|
|
214
|
+
self,
|
|
215
|
+
role_arn=None,
|
|
216
|
+
external_id=None,
|
|
217
|
+
region=None,
|
|
218
|
+
iam_config_object=None,
|
|
219
|
+
client_session_override=None,
|
|
220
|
+
):
|
|
221
|
+
super().__init__(
|
|
222
|
+
role_arn, external_id, region, iam_config_object, client_session_override
|
|
223
|
+
)
|
|
224
|
+
self.client = self.client_session.client("secretsmanager")
|
|
225
|
+
|
|
226
|
+
def get_content(self, secret):
|
|
227
|
+
"""
|
|
228
|
+
Import the Content of a given parameter
|
|
229
|
+
|
|
230
|
+
:param input.SecretDef secret:
|
|
231
|
+
:return:
|
|
232
|
+
"""
|
|
233
|
+
secret_id = expandvars(secret.secret_id)
|
|
234
|
+
params = {"SecretId": secret_id}
|
|
235
|
+
LOG.debug(f"Retrieving secretsmanager://{secret_id}")
|
|
236
|
+
if secret.version_id:
|
|
237
|
+
params["VersionId"] = secret.version_id
|
|
238
|
+
if secret.version_stage:
|
|
239
|
+
params["VersionStage"] = secret.version_stage
|
|
240
|
+
try:
|
|
241
|
+
parameter = self.client.get_secret_value(**params)
|
|
242
|
+
return parameter["SecretString"]
|
|
243
|
+
except self.client.exceptions:
|
|
244
|
+
raise
|
|
245
|
+
except ClientError:
|
|
246
|
+
raise
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
# Copyright 2020-2022 John Mille<john@compose-x.io>
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Manages certbot certificates download from certbot-aws-store
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import TYPE_CHECKING
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from .input import Model
|
|
14
|
+
|
|
15
|
+
from os import makedirs, path
|
|
16
|
+
|
|
17
|
+
from certbot_aws_store.certificate import AcmeCertificate
|
|
18
|
+
|
|
19
|
+
from ecs_files_composer.common import LOG
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def handle_certbot_store_certificates(job: Model) -> None:
|
|
23
|
+
"""
|
|
24
|
+
Pulls certificates from certbot-aws-store to local filesystem
|
|
25
|
+
If the path does not exist, creates a new directory for download.
|
|
26
|
+
"""
|
|
27
|
+
if not job.certbot_store:
|
|
28
|
+
return
|
|
29
|
+
for _hostname, _definition in job.certbot_store.items():
|
|
30
|
+
certificate = AcmeCertificate(
|
|
31
|
+
_hostname,
|
|
32
|
+
None,
|
|
33
|
+
table_name=_definition.table_name if _definition.table_name else None,
|
|
34
|
+
region_name=_definition.table_region_name
|
|
35
|
+
if _definition.table_region_name
|
|
36
|
+
else None,
|
|
37
|
+
)
|
|
38
|
+
if not path.exists(_definition.storage_path):
|
|
39
|
+
makedirs(_definition.storage_path, exist_ok=True)
|
|
40
|
+
try:
|
|
41
|
+
certificate.pull(_definition.storage_path)
|
|
42
|
+
LOG.info("Successfully pulled certificates for %s", _hostname)
|
|
43
|
+
except Exception as error:
|
|
44
|
+
LOG.exception(error)
|
|
45
|
+
LOG.error(
|
|
46
|
+
"Failed to download certificate from certbot-aws-store", _hostname
|
|
47
|
+
)
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
# Copyright 2020-2022 John Mille<john@compose-x.io>
|
|
3
|
+
|
|
4
|
+
import pathlib
|
|
5
|
+
import socket
|
|
6
|
+
from os import path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from OpenSSL import crypto
|
|
10
|
+
|
|
11
|
+
from ecs_files_composer.files_mgmt import File
|
|
12
|
+
from ecs_files_composer.input import X509CertDef
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class X509Certificate(X509CertDef):
|
|
16
|
+
"""
|
|
17
|
+
Class to wrap actions around a new X509 certificate
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, **data: Any):
|
|
21
|
+
super().__init__(**data)
|
|
22
|
+
self.key = None
|
|
23
|
+
self.cert = None
|
|
24
|
+
self.key_content = None
|
|
25
|
+
self.cert_content = None
|
|
26
|
+
self.cert_file = None
|
|
27
|
+
self.key_file = None
|
|
28
|
+
self.cert_file_path = None
|
|
29
|
+
self.key_file_path = None
|
|
30
|
+
|
|
31
|
+
def init_cert_paths(self):
|
|
32
|
+
self.cert_file_path = path.abspath(f"{self.dir_path}/{self.cert_file_name}")
|
|
33
|
+
self.key_file_path = path.abspath(f"{self.dir_path}/{self.key_file_name}")
|
|
34
|
+
print(f"Creating {self.dir_path} folder")
|
|
35
|
+
dir_path = pathlib.Path(path.abspath(self.dir_path))
|
|
36
|
+
dir_path.mkdir(parents=True, exist_ok=True)
|
|
37
|
+
|
|
38
|
+
def generate_key(self):
|
|
39
|
+
self.key = crypto.PKey()
|
|
40
|
+
self.key.generate_key(crypto.TYPE_RSA, 4096)
|
|
41
|
+
|
|
42
|
+
def set_common_name(self):
|
|
43
|
+
if self.common_name is None:
|
|
44
|
+
self.common_name = socket.gethostname()
|
|
45
|
+
|
|
46
|
+
def generate_cert(self):
|
|
47
|
+
if not self.common_name:
|
|
48
|
+
self.set_common_name()
|
|
49
|
+
self.cert = crypto.X509()
|
|
50
|
+
self.cert.get_subject().C = self.country_name
|
|
51
|
+
self.cert.get_subject().ST = self.state_or_province_name
|
|
52
|
+
self.cert.get_subject().L = self.locality_name
|
|
53
|
+
self.cert.get_subject().O = self.organization_name
|
|
54
|
+
self.cert.get_subject().OU = self.organization_unit_name
|
|
55
|
+
self.cert.get_subject().CN = self.common_name
|
|
56
|
+
self.cert.get_subject().emailAddress = self.email_address
|
|
57
|
+
self.cert.set_serial_number(0)
|
|
58
|
+
self.cert.gmtime_adj_notBefore(0)
|
|
59
|
+
self.cert.gmtime_adj_notAfter(int(self.validity_end_in_seconds))
|
|
60
|
+
self.cert.set_issuer(self.cert.get_subject())
|
|
61
|
+
self.cert.set_pubkey(self.key)
|
|
62
|
+
self.cert.sign(self.key, "sha512")
|
|
63
|
+
|
|
64
|
+
def generate_cert_content(self):
|
|
65
|
+
if not self.key:
|
|
66
|
+
self.generate_key()
|
|
67
|
+
if not self.cert:
|
|
68
|
+
self.generate_cert()
|
|
69
|
+
self.cert_content = crypto.dump_certificate(
|
|
70
|
+
crypto.FILETYPE_PEM, self.cert
|
|
71
|
+
).decode("utf-8")
|
|
72
|
+
self.key_content = crypto.dump_privatekey(crypto.FILETYPE_PEM, self.key).decode(
|
|
73
|
+
"utf-8"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def set_cert_files(self):
|
|
77
|
+
if not self.cert_content or not self.key_content:
|
|
78
|
+
self.generate_cert_content()
|
|
79
|
+
self.key_file = File().parse_obj(
|
|
80
|
+
{
|
|
81
|
+
"content": self.key_content,
|
|
82
|
+
"path": self.key_file_path,
|
|
83
|
+
"mode": "0600",
|
|
84
|
+
"owner": self.owner,
|
|
85
|
+
"group": self.group,
|
|
86
|
+
}
|
|
87
|
+
)
|
|
88
|
+
self.cert_file = File().parse_obj(
|
|
89
|
+
{
|
|
90
|
+
"content": self.cert_content,
|
|
91
|
+
"path": self.cert_file_path,
|
|
92
|
+
"mode": "0600",
|
|
93
|
+
"owner": self.owner,
|
|
94
|
+
"group": self.group,
|
|
95
|
+
}
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def process_x509_certs(job):
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
:param ecs_files_composer.input.Model job:
|
|
103
|
+
:return:
|
|
104
|
+
"""
|
|
105
|
+
if not hasattr(job.certificates, "x509") or not job.certificates.x509:
|
|
106
|
+
return
|
|
107
|
+
for cert_path, cert_def in job.certificates.x509.items():
|
|
108
|
+
cert_obj = X509Certificate(
|
|
109
|
+
**(cert_def.dict(by_alias=True)),
|
|
110
|
+
)
|
|
111
|
+
cert_obj.dir_path = cert_path
|
|
112
|
+
cert_obj.init_cert_paths()
|
|
113
|
+
cert_obj.set_cert_files()
|
|
114
|
+
job.certificates.x509[cert_path] = cert_obj
|
|
115
|
+
job.files[cert_obj.cert_file.path] = cert_obj.cert_file
|
|
116
|
+
job.files[cert_obj.key_file_path] = cert_obj.key_file
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
# Copyright 2020-2022 John Mille<john@compose-x.io>
|
|
3
|
+
|
|
4
|
+
"""Console script for ecs_files_composer."""
|
|
5
|
+
import argparse
|
|
6
|
+
import sys
|
|
7
|
+
from os import environ
|
|
8
|
+
|
|
9
|
+
from ecs_files_composer.common import LOG
|
|
10
|
+
from ecs_files_composer.ecs_files_composer import init_config, start_jobs
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def main():
|
|
14
|
+
"""Console script for ecs_files_composer."""
|
|
15
|
+
parser = argparse.ArgumentParser()
|
|
16
|
+
options = parser.add_mutually_exclusive_group()
|
|
17
|
+
options.add_argument(
|
|
18
|
+
"-f",
|
|
19
|
+
"--from-file",
|
|
20
|
+
help="Configuration for execution from a file",
|
|
21
|
+
type=str,
|
|
22
|
+
required=False,
|
|
23
|
+
dest="file_path",
|
|
24
|
+
)
|
|
25
|
+
options.add_argument(
|
|
26
|
+
"-e",
|
|
27
|
+
"--from-env-var",
|
|
28
|
+
dest="env_var",
|
|
29
|
+
required=False,
|
|
30
|
+
help="Configuration for execution is in an environment variable",
|
|
31
|
+
)
|
|
32
|
+
options.add_argument(
|
|
33
|
+
"--from-ssm",
|
|
34
|
+
dest="ssm_config",
|
|
35
|
+
help="Configuration for execution is in an SSM Parameter",
|
|
36
|
+
required=False,
|
|
37
|
+
)
|
|
38
|
+
options.add_argument(
|
|
39
|
+
"--from-s3",
|
|
40
|
+
dest="s3_config",
|
|
41
|
+
required=False,
|
|
42
|
+
help="Configuration for execution is in an S3",
|
|
43
|
+
)
|
|
44
|
+
options.add_argument(
|
|
45
|
+
"--from-secrets",
|
|
46
|
+
dest="secret_config",
|
|
47
|
+
required=False,
|
|
48
|
+
help="Configuration for execution is in an AWS Secrets Manager",
|
|
49
|
+
)
|
|
50
|
+
parser.add_argument(
|
|
51
|
+
"--role-arn",
|
|
52
|
+
help="The Role ARN to use for the configuration initialization",
|
|
53
|
+
required=False,
|
|
54
|
+
)
|
|
55
|
+
parser.add_argument(
|
|
56
|
+
"--decode-base64",
|
|
57
|
+
help="Whether the source config is in base64 encoded",
|
|
58
|
+
action="store_true",
|
|
59
|
+
required=False,
|
|
60
|
+
default=False,
|
|
61
|
+
)
|
|
62
|
+
parser.add_argument(
|
|
63
|
+
"--context",
|
|
64
|
+
help="Indicate which context to use (valid: jinja2|plain). Default is jinja2",
|
|
65
|
+
required=False,
|
|
66
|
+
default="jinja2",
|
|
67
|
+
)
|
|
68
|
+
parser.add_argument(
|
|
69
|
+
"--override-init-folder",
|
|
70
|
+
dest="init_folder",
|
|
71
|
+
required=False,
|
|
72
|
+
type=str,
|
|
73
|
+
default="",
|
|
74
|
+
)
|
|
75
|
+
parser.add_argument("_", nargs="*")
|
|
76
|
+
args = parser.parse_args()
|
|
77
|
+
print("Arguments: " + str(args._))
|
|
78
|
+
if not (
|
|
79
|
+
args.env_var or args.ssm_config or args.s3_config or args.file_path
|
|
80
|
+
) and environ.get("ECS_CONFIG_CONTENT", None):
|
|
81
|
+
LOG.info("Using default env variable ECS_CONFIG_CONTENT")
|
|
82
|
+
config = init_config(
|
|
83
|
+
env_var="ECS_CONFIG_CONTENT",
|
|
84
|
+
decode_base64=bool(environ.get("DECODE_BASE64", False)),
|
|
85
|
+
context=environ.get("context", "jinja2"),
|
|
86
|
+
)
|
|
87
|
+
elif args.env_var:
|
|
88
|
+
config = init_config(
|
|
89
|
+
env_var=args.env_var,
|
|
90
|
+
decode_base64=args.decode_base64,
|
|
91
|
+
context=args.context,
|
|
92
|
+
override_folder=args.init_folder,
|
|
93
|
+
)
|
|
94
|
+
elif args.file_path:
|
|
95
|
+
config = init_config(
|
|
96
|
+
file_path=args.file_path,
|
|
97
|
+
decode_base64=args.decode_base64,
|
|
98
|
+
context=args.context,
|
|
99
|
+
override_folder=args.init_folder,
|
|
100
|
+
)
|
|
101
|
+
elif args.ssm_config:
|
|
102
|
+
config = init_config(
|
|
103
|
+
ssm_parameter=args.ssm_config,
|
|
104
|
+
decode_base64=args.decode_base64,
|
|
105
|
+
context=args.context,
|
|
106
|
+
override_folder=args.init_folder,
|
|
107
|
+
)
|
|
108
|
+
elif args.s3_config:
|
|
109
|
+
config = init_config(
|
|
110
|
+
s3_config=args.s3_config,
|
|
111
|
+
decode_base64=args.decode_base64,
|
|
112
|
+
context=args.context,
|
|
113
|
+
override_folder=args.init_folder,
|
|
114
|
+
)
|
|
115
|
+
elif args.secret_config:
|
|
116
|
+
config = init_config(
|
|
117
|
+
secret_config=args.secret_config,
|
|
118
|
+
decode_base64=args.decode_base64,
|
|
119
|
+
context=args.context,
|
|
120
|
+
override_folder=args.init_folder,
|
|
121
|
+
)
|
|
122
|
+
else:
|
|
123
|
+
raise parser.error(
|
|
124
|
+
"You must specify where the execution configuration comes from or set ECS_CONFIG_CONTENT."
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
start_jobs(config)
|
|
128
|
+
return 0
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
if __name__ == "__main__":
|
|
132
|
+
sys.exit(main()) # pragma: no cover
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
# Copyright 2020-2022 John Mille<john@compose-x.io>
|
|
3
|
+
|
|
4
|
+
import logging as logthings
|
|
5
|
+
import sys
|
|
6
|
+
from os import environ
|
|
7
|
+
|
|
8
|
+
from compose_x_common.compose_x_common import keyisset
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def setup_logging():
|
|
12
|
+
"""Function to setup logging for ECS ComposeX.
|
|
13
|
+
In case this is used in a Lambda function, removes the AWS Lambda default log handler
|
|
14
|
+
|
|
15
|
+
:returns: the_logger
|
|
16
|
+
:rtype: Logger
|
|
17
|
+
"""
|
|
18
|
+
level = environ.get("LOGLEVEL")
|
|
19
|
+
default_level = True
|
|
20
|
+
formats = {
|
|
21
|
+
"INFO": logthings.Formatter(
|
|
22
|
+
"%(asctime)s [%(levelname)s], %(message)s",
|
|
23
|
+
"%Y-%m-%d %H:%M:%S",
|
|
24
|
+
),
|
|
25
|
+
"DEBUG": logthings.Formatter(
|
|
26
|
+
"%(asctime)s [%(levelname)s], %(filename)s.%(lineno)d , %(funcName)s, %(message)s",
|
|
27
|
+
"%Y-%m-%d %H:%M:%S",
|
|
28
|
+
),
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if level is not None and isinstance(level, str):
|
|
32
|
+
print("SETTING TO", level.upper())
|
|
33
|
+
logthings.basicConfig(level=level.upper())
|
|
34
|
+
default_level = False
|
|
35
|
+
else:
|
|
36
|
+
logthings.basicConfig(level="INFO")
|
|
37
|
+
|
|
38
|
+
root_logger = logthings.getLogger()
|
|
39
|
+
for h in root_logger.handlers:
|
|
40
|
+
root_logger.removeHandler(h)
|
|
41
|
+
the_logger = logthings.getLogger("EcsComposeX")
|
|
42
|
+
|
|
43
|
+
if not the_logger.handlers:
|
|
44
|
+
if default_level:
|
|
45
|
+
formatter = formats["INFO"]
|
|
46
|
+
elif keyisset(level.upper(), formats):
|
|
47
|
+
formatter = formats[level.upper()]
|
|
48
|
+
else:
|
|
49
|
+
formatter = formats["DEBUG"]
|
|
50
|
+
handler = logthings.StreamHandler(sys.stdout)
|
|
51
|
+
handler.setFormatter(formatter)
|
|
52
|
+
the_logger.addHandler(handler)
|
|
53
|
+
|
|
54
|
+
return the_logger
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
LOG = setup_logging()
|