c2client 0.22__tar.gz → 0.24__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: c2client
3
- Version: 0.22
3
+ Version: 0.24
4
4
  Summary: CROC Cloud Platform - API Client
5
5
  Home-page: https://github.com/c2devel/c2-client
6
6
  Author: CROC Cloud Team
@@ -8,12 +8,20 @@ Author-email: devel@croc.ru
8
8
  Maintainer: Andrey Kulaev
9
9
  Maintainer-email: adkulaev@gmail.com
10
10
  License: GPL3
11
- Classifier: Development Status :: 4 - Beta
11
+ Platform: UNKNOWN
12
+ Classifier: Development Status :: 5 - Production/Stable
12
13
  Classifier: Environment :: Console
13
14
  Classifier: Intended Audience :: Developers
14
15
  Classifier: Intended Audience :: System Administrators
15
16
  Classifier: Operating System :: OS Independent
16
17
  Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.6
19
+ Classifier: Programming Language :: Python :: 3.7
20
+ Classifier: Programming Language :: Python :: 3.8
21
+ Classifier: Programming Language :: Python :: 3.9
22
+ Classifier: Programming Language :: Python :: 3.10
23
+ Classifier: Programming Language :: Python :: 3.11
24
+ Classifier: Programming Language :: Python :: 3.12
17
25
 
18
26
  CROC Cloud API Client
19
27
  =====================
@@ -21,7 +29,7 @@ CROC Cloud API Client
21
29
  Simple command-line utility for sending custom requests to CROC Cloud platform.
22
30
 
23
31
  **Warning: this utility is not intended for automation cases.
24
- Use https://github.com/c2devel/boto.git and python scripts instead.**
32
+ Use https://github.com/c2devel/boto3.git and python scripts instead.**
25
33
 
26
34
  Installation
27
35
  ------------
@@ -66,3 +74,5 @@ Send simple request:
66
74
 
67
75
  $ c2-ec2 RunInstances ImageId cmi-078880A0 Description "Test instance" \
68
76
  InstanceType m1.small MaxCount 1 MinCount 1 SecurityGroup.1 test
77
+
78
+
@@ -4,7 +4,7 @@ CROC Cloud API Client
4
4
  Simple command-line utility for sending custom requests to CROC Cloud platform.
5
5
 
6
6
  **Warning: this utility is not intended for automation cases.
7
- Use https://github.com/c2devel/boto.git and python scripts instead.**
7
+ Use https://github.com/c2devel/boto3.git and python scripts instead.**
8
8
 
9
9
  Installation
10
10
  ------------
@@ -0,0 +1 @@
1
+ __version__ = "0.24"
@@ -1,29 +1,20 @@
1
1
  import argparse
2
2
  import json
3
- import os
4
3
  import re
5
4
  import ssl
6
5
  from abc import abstractmethod
7
6
  from functools import wraps
8
- from typing import Dict
9
- from urllib.parse import urlparse
7
+ from typing import Any, Dict, Optional
10
8
 
11
- import boto
12
- import boto.cloudtrail.layer1
13
- import boto.ec2
14
- import boto.ec2.cloudwatch
15
9
  import boto3
16
10
  import inflection
17
- from boto.ec2.regioninfo import RegionInfo
18
11
 
19
- from c2client.utils import from_dot_notation, get_env_var, prettify_xml
12
+ from c2client.errors import InvalidMethodName
13
+ from c2client.utils import from_dot_notation, get_env_var, convert_args
20
14
 
21
15
 
22
16
  ssl._create_default_https_context = ssl._create_unverified_context
23
17
 
24
- if os.environ.get("DEBUG"):
25
- boto.set_stream_logger("c2")
26
-
27
18
 
28
19
  def exitcode(func: callable):
29
20
  """Wrapper for logging any caught exception."""
@@ -32,9 +23,6 @@ def exitcode(func: callable):
32
23
  def wrapper(*args, **kwargs):
33
24
  try:
34
25
  func(*args, **kwargs)
35
- except boto.exception.BotoServerError as error:
36
- error_code = error.error_code or error.body.get("__type")
37
- return f"{error_code}: {error.message}"
38
26
  except Exception as e:
39
27
  return e
40
28
  return wrapper
@@ -94,38 +82,6 @@ class BaseClient:
94
82
  print(response)
95
83
 
96
84
 
97
- class C2ClientLegacy(BaseClient):
98
-
99
- connection_class: type
100
-
101
- @classmethod
102
- def get_client(cls, verify: bool):
103
- """Return boto connection."""
104
-
105
- if not boto.config.has_section("Boto"):
106
- boto.config.add_section("Boto")
107
- boto.config.set("Boto", "is_secure", "True")
108
- boto.config.set("Boto", "num_retries", "0")
109
- boto.config.set("Boto", "https_validate_certificates", str(verify))
110
-
111
- parsed_endpoint = urlparse(get_env_var(cls.url_key))
112
-
113
- return cls.connection_class(
114
- port=parsed_endpoint.port,
115
- path=parsed_endpoint.path,
116
- region=RegionInfo(name=parsed_endpoint.hostname, endpoint=parsed_endpoint.hostname),
117
- is_secure=False,
118
- )
119
-
120
- @classmethod
121
- def make_request(cls, method: str, arguments: dict, verify: bool):
122
-
123
- connection = cls.get_client(verify)
124
- response = connection.make_request(method, arguments)
125
-
126
- return prettify_xml(response.read())
127
-
128
-
129
85
  class C2Client(BaseClient):
130
86
 
131
87
  @classmethod
@@ -148,72 +104,82 @@ class C2Client(BaseClient):
148
104
  )
149
105
 
150
106
  @classmethod
151
- def is_conversion_needed(cls, argument_name: str) -> bool:
152
- """Check whether type conversion is needed for argument."""
153
-
154
- return True
155
-
156
- @classmethod
157
- def make_request(cls, method: str, arguments: dict, verify: bool):
107
+ def make_request(cls, method: str, arguments: Optional[Dict], verify: bool) -> str:
158
108
 
159
109
  client = cls.get_client(verify)
160
110
 
161
- for key, value in arguments.items():
162
- if not cls.is_conversion_needed(key):
163
- continue
164
- if value.isdigit():
165
- arguments[key] = int(value)
166
- elif value.lower() == "true":
167
- arguments[key] = True
168
- elif value.lower() == "false":
169
- arguments[key] = False
111
+ if not hasattr(client, inflection.underscore(method)):
112
+ raise InvalidMethodName(method)
113
+
114
+ if arguments:
115
+ arguments = cls.convert_fields_names(arguments)
116
+ shape = client.meta.service_model.operation_model(method).input_shape
117
+ arguments = convert_args(from_dot_notation(arguments), shape)
170
118
 
171
- result = getattr(client, inflection.underscore(method))(**from_dot_notation(arguments))
119
+ result = getattr(client, inflection.underscore(method))(**arguments)
172
120
 
173
121
  result.pop("ResponseMetadata", None)
174
122
 
175
123
  # default=str is required for serializing Datetime objects
176
124
  return json.dumps(result, indent=4, default=str)
177
125
 
126
+ @staticmethod
127
+ def convert_fields_names(arguments: dict) -> Dict[str, Any]:
128
+ """Convert field names as in the documentation."""
129
+
130
+ return arguments
131
+
178
132
 
179
- class EC2Client(C2ClientLegacy):
133
+ class EC2Client(C2Client):
180
134
 
181
135
  url_key = "EC2_URL"
182
136
  client_name = "ec2"
183
137
 
184
- connection_class = boto.ec2.EC2Connection
138
+ @staticmethod
139
+ def convert_fields_names(arguments: dict) -> Dict[str, Any]:
140
+ """Convert field names as in the documentation."""
185
141
 
142
+ tag_pattern = r"Filter\.\d+\.Value"
186
143
 
187
- class CWClient(C2ClientLegacy):
144
+ new_arguments = {}
145
+ filters = {}
146
+ for key, value in arguments.items():
147
+ new_key = key
148
+ if re.fullmatch(tag_pattern, key):
149
+ key = ".".join(key.split(".")[:3])
188
150
 
189
- url_key = "AWS_CLOUDWATCH_URL"
190
- client_name = "cw"
151
+ if key in filters:
152
+ filters[key] += 1
153
+ else:
154
+ filters[key] = 1
191
155
 
192
- connection_class = boto.ec2.cloudwatch.CloudWatchConnection
156
+ new_key = f"{key}.{filters[key]}"
193
157
 
158
+ new_arguments[new_key] = value
194
159
 
195
- class CTClient(C2ClientLegacy):
160
+ return new_arguments
196
161
 
197
- url_key = "AWS_CLOUDTRAIL_URL"
198
- client_name = "ct"
199
162
 
200
- connection_class = boto.cloudtrail.layer1.CloudTrailConnection
163
+ class CWClient(C2Client):
201
164
 
202
- @classmethod
203
- def make_request(cls, method: str, arguments: dict, verify: bool):
165
+ url_key = "AWS_CLOUDWATCH_URL"
166
+ client_name = "cloudwatch"
167
+
168
+ @staticmethod
169
+ def convert_fields_names(arguments: dict) -> Dict[str, Any]:
170
+ """Convert field names as in the documentation."""
204
171
 
205
- connection = cls.get_client(verify)
172
+ new_arguments = {}
173
+ for key, value in arguments.items():
174
+ new_key = key.replace(".member.", ".")
175
+ new_arguments[new_key] = value
176
+ return new_arguments
206
177
 
207
- if "MaxResults" in arguments:
208
- arguments["MaxResults"] = int(arguments["MaxResults"])
209
- if "StartTime" in arguments:
210
- arguments["StartTime"] = int(arguments["StartTime"])
211
- if "EndTime" in arguments:
212
- arguments["EndTime"] = int(arguments["EndTime"])
213
178
 
214
- response = connection.make_request(method, json.dumps(from_dot_notation(arguments)))
179
+ class CTClient(C2Client):
215
180
 
216
- return json.dumps(response, indent=4, sort_keys=True)
181
+ url_key = "AWS_CLOUDTRAIL_URL"
182
+ client_name = "cloudtrail"
217
183
 
218
184
 
219
185
  class ASClient(C2Client):
@@ -221,18 +187,6 @@ class ASClient(C2Client):
221
187
  url_key = "AUTO_SCALING_URL"
222
188
  client_name = "autoscaling"
223
189
 
224
- @classmethod
225
- def is_conversion_needed(cls, argument_name: str) -> bool:
226
- """Check whether type conversion is needed for argument."""
227
-
228
- patterns = (
229
- r"Filters\.\d+\.Values\.\d+",
230
- )
231
- for pattern in patterns:
232
- if re.fullmatch(pattern, argument_name):
233
- return False
234
- return True
235
-
236
190
 
237
191
  class BSClient(C2Client):
238
192
 
@@ -10,3 +10,13 @@ class EnvironmentVariableError(Exception):
10
10
  class MalformedParametersError(Exception):
11
11
  def __init__(self):
12
12
  super(MalformedParametersError, self).__init__("Malformed parameters.")
13
+
14
+
15
+ class InvalidParameterName(Exception):
16
+ def __init__(self, parameter_name: str) -> None:
17
+ super().__init__(f"Parameter with name '{parameter_name}' was not found.")
18
+
19
+
20
+ class InvalidMethodName(Exception):
21
+ def __init__(self, method_name: str) -> None:
22
+ super().__init__(f"Not found method by name: {method_name}.")
@@ -0,0 +1,134 @@
1
+ import dataclasses
2
+ import os
3
+ from typing import Any, Dict
4
+
5
+ from botocore.model import ListShape, StructureShape, Shape
6
+
7
+ from c2client.errors import EnvironmentVariableError, MalformedParametersError, InvalidParameterName
8
+
9
+
10
+ @dataclasses.dataclass
11
+ class Parameter:
12
+ """API parameter info."""
13
+
14
+ name: str
15
+ shape: Shape
16
+
17
+
18
+ def from_dot_notation(source):
19
+ """Convert a incoming query to a request dictionary.
20
+ For example::
21
+ 1. {"Action": ["Action"], "Param": ["Value"]}
22
+ 2. {"Action": ["Action"], "Param.2": ["Value2"], "Param.1": ["Value1"]}
23
+ 3. {"Action": ["Action"], "Param.SubParam": ["Value"]}
24
+ would result in the params dict::
25
+ 1. {"Action": "Action", "Param": "Value"}
26
+ 2. {"Action": "Action", "Param": ["Value1", "Value2"]}
27
+ 3. {"Action": "Action", "Param": { "SubParam": "Value"}}
28
+ :type query: dict
29
+ :param query: This is dictionary, returned by '_get_query()'.
30
+ """
31
+
32
+ result = {"result": {}}
33
+ for key, value in sorted(source.items()):
34
+ try:
35
+ _process_tokens(key.split("."), value, result, "result")
36
+ except Exception:
37
+ raise MalformedParametersError
38
+ return result["result"]
39
+
40
+
41
+ def _process_tokens(tokens, value, parent, index):
42
+ key, rest = tokens[0], tokens[1:]
43
+ if key.isdigit():
44
+ key = int(key) - 1
45
+
46
+ if not isinstance(key, int):
47
+ parent[index].setdefault(key, {})
48
+ elif isinstance(parent[index], dict) and not parent[index]:
49
+ parent[index] = [{}]
50
+ elif isinstance(parent[index], list) and len(parent[index]) == key:
51
+ parent[index].append({})
52
+ elif not (isinstance(parent[index], list) and len(parent[index]) > key):
53
+ raise MalformedParametersError
54
+
55
+ if rest:
56
+ _process_tokens(rest, value, parent[index], key)
57
+ else:
58
+ parent[index][key] = value
59
+
60
+
61
+ def get_env_var(name: str) -> Any:
62
+ """Return env_var by it's name or raises EnvironmentError."""
63
+
64
+ env_var = os.environ.get(name)
65
+ if env_var is None:
66
+ raise EnvironmentVariableError(name)
67
+ return env_var
68
+
69
+
70
+ def collect_param_shapes(shape: Shape) -> Dict[str, Parameter]:
71
+ """Collect a dict of API schema parameters by their name or serialization name."""
72
+
73
+ param_shapes = {}
74
+ for member_name in shape.members:
75
+ param_shapes[member_name.lower()] = Parameter(
76
+ name=member_name, shape=shape.members[member_name])
77
+ if shape.members[member_name].serialization.get("name"):
78
+ param_shapes[shape.members[member_name].serialization.get("name").lower()] = Parameter(
79
+ name=member_name, shape=shape.members[member_name])
80
+
81
+ return param_shapes
82
+
83
+
84
+ def convert_args(params: Any, shape: Shape):
85
+ """Convert values in the params dictionary to the types expected by shape."""
86
+
87
+ if not isinstance(shape, StructureShape):
88
+ return convert_arg(value=params, shape=shape)
89
+
90
+ converted_params = {}
91
+ param_shapes = collect_param_shapes(shape)
92
+
93
+ for param_name, param_value in params.items():
94
+ parameter = param_shapes.get(param_name.lower())
95
+
96
+ if not parameter:
97
+ raise InvalidParameterName(param_name)
98
+
99
+ if parameter.shape:
100
+ converted_params[parameter.name] = convert_arg(param_value, parameter.shape)
101
+ else:
102
+ converted_params[parameter.name] = param_value
103
+
104
+ return converted_params
105
+
106
+
107
+ def convert_arg(value: Any, shape: Shape):
108
+ """Convert an individual value to the type expected by shape."""
109
+
110
+ if isinstance(shape, ListShape):
111
+ if not isinstance(value, list):
112
+ raise ValueError(f"Expected list for {shape.name}, got {type(value).__name__}")
113
+ return [convert_arg(v, shape.member) for v in value]
114
+
115
+ elif isinstance(shape, StructureShape):
116
+ if not isinstance(value, dict):
117
+ raise ValueError(f"Expected dict for {shape.name}, got {type(value).__name__}")
118
+ return convert_args(value, shape)
119
+
120
+ elif shape.type_name == "string":
121
+ return str(value)
122
+
123
+ elif shape.type_name == "integer" or shape.type_name == "long":
124
+ return int(value)
125
+
126
+ elif shape.type_name == "float" or shape.type_name == "double":
127
+ return float(value)
128
+
129
+ elif shape.type_name == "boolean":
130
+ if isinstance(value, str) and value.lower() == "false":
131
+ return False
132
+ return bool(value)
133
+ else:
134
+ return value
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: c2client
3
- Version: 0.22
3
+ Version: 0.24
4
4
  Summary: CROC Cloud Platform - API Client
5
5
  Home-page: https://github.com/c2devel/c2-client
6
6
  Author: CROC Cloud Team
@@ -8,12 +8,20 @@ Author-email: devel@croc.ru
8
8
  Maintainer: Andrey Kulaev
9
9
  Maintainer-email: adkulaev@gmail.com
10
10
  License: GPL3
11
- Classifier: Development Status :: 4 - Beta
11
+ Platform: UNKNOWN
12
+ Classifier: Development Status :: 5 - Production/Stable
12
13
  Classifier: Environment :: Console
13
14
  Classifier: Intended Audience :: Developers
14
15
  Classifier: Intended Audience :: System Administrators
15
16
  Classifier: Operating System :: OS Independent
16
17
  Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.6
19
+ Classifier: Programming Language :: Python :: 3.7
20
+ Classifier: Programming Language :: Python :: 3.8
21
+ Classifier: Programming Language :: Python :: 3.9
22
+ Classifier: Programming Language :: Python :: 3.10
23
+ Classifier: Programming Language :: Python :: 3.11
24
+ Classifier: Programming Language :: Python :: 3.12
17
25
 
18
26
  CROC Cloud API Client
19
27
  =====================
@@ -21,7 +29,7 @@ CROC Cloud API Client
21
29
  Simple command-line utility for sending custom requests to CROC Cloud platform.
22
30
 
23
31
  **Warning: this utility is not intended for automation cases.
24
- Use https://github.com/c2devel/boto.git and python scripts instead.**
32
+ Use https://github.com/c2devel/boto3.git and python scripts instead.**
25
33
 
26
34
  Installation
27
35
  ------------
@@ -66,3 +74,5 @@ Send simple request:
66
74
 
67
75
  $ c2-ec2 RunInstances ImageId cmi-078880A0 Description "Test instance" \
68
76
  InstanceType m1.small MaxCount 1 MinCount 1 SecurityGroup.1 test
77
+
78
+
@@ -13,3 +13,4 @@ c2-iam = c2client.clients:IAMClient.execute
13
13
  c2-paas = c2client.clients:PaasClient.execute
14
14
  c2-route53 = c2client.clients:Route53Client.execute
15
15
  c2rc-convert = c2client.c2rc_convert:main
16
+
@@ -1,5 +1,3 @@
1
- boto
2
1
  boto3
3
2
  botocore
4
3
  inflection==0.3.1
5
- lxml
@@ -14,11 +14,9 @@ def get_description():
14
14
 
15
15
 
16
16
  install_requires = [
17
- "boto",
18
17
  "boto3",
19
18
  "botocore",
20
19
  "inflection==0.3.1",
21
- "lxml",
22
20
  ]
23
21
 
24
22
  entrypoints = [
@@ -49,12 +47,19 @@ setup(
49
47
  maintainer="Andrey Kulaev",
50
48
  maintainer_email="adkulaev@gmail.com",
51
49
  classifiers=[
52
- "Development Status :: 4 - Beta",
50
+ "Development Status :: 5 - Production/Stable",
53
51
  "Environment :: Console",
54
52
  "Intended Audience :: Developers",
55
53
  "Intended Audience :: System Administrators",
56
54
  "Operating System :: OS Independent",
57
55
  "Programming Language :: Python :: 3",
56
+ "Programming Language :: Python :: 3.6",
57
+ "Programming Language :: Python :: 3.7",
58
+ "Programming Language :: Python :: 3.8",
59
+ "Programming Language :: Python :: 3.9",
60
+ "Programming Language :: Python :: 3.10",
61
+ "Programming Language :: Python :: 3.11",
62
+ "Programming Language :: Python :: 3.12",
58
63
  ],
59
64
  install_requires=install_requires,
60
65
  packages=find_packages(),
@@ -1 +0,0 @@
1
- __version__ = "0.22"
@@ -1,64 +0,0 @@
1
- import os
2
-
3
- from lxml import etree
4
-
5
- from c2client.errors import EnvironmentVariableError, MalformedParametersError
6
-
7
-
8
- def prettify_xml(string):
9
- """Returns prettified XML string."""
10
-
11
- parser = etree.XMLParser(remove_blank_text=True)
12
- tree = etree.fromstring(string, parser)
13
- return etree.tostring(tree, pretty_print=True, encoding="unicode")
14
-
15
-
16
- def from_dot_notation(source):
17
- """Converts a incoming query to a request dictionary.
18
- For example::
19
- 1. {"Action": ["Action"], "Param": ["Value"]}
20
- 2. {"Action": ["Action"], "Param.2": ["Value2"], "Param.1": ["Value1"]}
21
- 3. {"Action": ["Action"], "Param.SubParam": ["Value"]}
22
- would result in the params dict::
23
- 1. {"Action": "Action", "Param": "Value"}
24
- 2. {"Action": "Action", "Param": ["Value1", "Value2"]}
25
- 3. {"Action": "Action", "Param": { "SubParam": "Value"}}
26
- :type query: dict
27
- :param query: This is dictionary, returned by '_get_query()'.
28
- """
29
- result = {"result": {}}
30
- for key, value in sorted(source.items()):
31
- try:
32
- _process_tokens(key.split("."), value, result, "result")
33
- except Exception:
34
- raise MalformedParametersError
35
- return result["result"]
36
-
37
-
38
- def _process_tokens(tokens, value, parent, index):
39
- key, rest = tokens[0], tokens[1:]
40
- if key.isdigit():
41
- key = int(key) - 1
42
-
43
- if not isinstance(key, int):
44
- parent[index].setdefault(key, {})
45
- elif isinstance(parent[index], dict) and not parent[index]:
46
- parent[index] = [{}]
47
- elif isinstance(parent[index], list) and len(parent[index]) == key:
48
- parent[index].append({})
49
- elif not (isinstance(parent[index], list) and len(parent[index]) > key):
50
- raise MalformedParametersError
51
-
52
- if rest:
53
- _process_tokens(rest, value, parent[index], key)
54
- else:
55
- parent[index][key] = value
56
-
57
-
58
- def get_env_var(name):
59
- """Returns env_var by it's name or raises EnvironmentError."""
60
-
61
- env_var = os.environ.get(name)
62
- if env_var is None:
63
- raise EnvironmentVariableError(name)
64
- return env_var
File without changes
File without changes
File without changes