codosdk 1.0.56__tar.gz → 1.0.57__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.
- {codosdk-1.0.56 → codosdk-1.0.57}/PKG-INFO +1 -1
- {codosdk-1.0.56 → codosdk-1.0.57}/README.md +16 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/codosdk.egg-info/PKG-INFO +1 -1
- {codosdk-1.0.56 → codosdk-1.0.57}/codosdk.egg-info/SOURCES.txt +4 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/codosdk.egg-info/top_level.txt +1 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/setup.py +2 -2
- codosdk-1.0.57/tests/test_openapi_sign.py +41 -0
- codosdk-1.0.57/websdk2/cloud/__init__.py +0 -0
- codosdk-1.0.57/websdk2/openapi_client.py +127 -0
- codosdk-1.0.57/websdk2/openapi_sign.py +120 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/LICENSE +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/codosdk.egg-info/dependency_links.txt +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/codosdk.egg-info/requires.txt +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/opssdk/__init__.py +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/opssdk/utils/__init__.py +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/pyproject.toml +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/setup.cfg +0 -0
- {codosdk-1.0.56/websdk2 → codosdk-1.0.57/tests}/__init__.py +0 -0
- {codosdk-1.0.56/websdk2/cloud → codosdk-1.0.57/websdk2}/__init__.py +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/websdk2/api_set.py +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/websdk2/apis/__init__.py +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/websdk2/apis/admin_apis.py +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/websdk2/apis/agent_apis.py +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/websdk2/apis/cmdb_apis.py +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/websdk2/apis/kerrigan_apis.py +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/websdk2/apis/mgv4_apis.py +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/websdk2/apis/notice_apis.py +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/websdk2/apis/task_apis.py +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/websdk2/application.py +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/websdk2/base_handler.py +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/websdk2/cache.py +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/websdk2/cache_context.py +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/websdk2/client.py +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/websdk2/cloud/qcloud_api.py +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/websdk2/cloud/ucloud_api.py +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/websdk2/cloud_utils.py +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/websdk2/configs.py +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/websdk2/consts.py +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/websdk2/crud_utils.py +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/websdk2/db_context.py +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/websdk2/error.py +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/websdk2/fetch_coroutine.py +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/websdk2/jwt_token.py +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/websdk2/ldap.py +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/websdk2/logger.py +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/websdk2/model_utils.py +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/websdk2/mqhelper.py +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/websdk2/program.py +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/websdk2/salt_api.py +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/websdk2/sqlalchemy_pagination.py +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/websdk2/tools.py +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/websdk2/utils/__init__.py +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/websdk2/utils/cc_crypto.py +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/websdk2/utils/date_format.py +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/websdk2/utils/pydantic_utils.py +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/websdk2/utils/pydantic_utils_v2.py +0 -0
- {codosdk-1.0.56 → codosdk-1.0.57}/websdk2/web_logs.py +0 -0
|
@@ -99,6 +99,22 @@ token = auth.encode_auth_token(
|
|
|
99
99
|
user_info = auth.decode_auth_token(token)
|
|
100
100
|
```
|
|
101
101
|
|
|
102
|
+
### 开放 API(AK/SK)
|
|
103
|
+
|
|
104
|
+
适用于 CI、集成方等编程访问。网关校验 `CODO1-HMAC-SHA256` 签名后签发短时内部 JWT。
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
from websdk2.openapi_client import OpenAPIClient
|
|
108
|
+
|
|
109
|
+
client = OpenAPIClient(
|
|
110
|
+
endpoint="https://gw.example.com",
|
|
111
|
+
access_key="your-ak",
|
|
112
|
+
secret_key="your-sk",
|
|
113
|
+
)
|
|
114
|
+
resp = client.request("GET", "/api/p/v4/biz/list/")
|
|
115
|
+
print(resp.status_code, resp.text)
|
|
116
|
+
```
|
|
117
|
+
|
|
102
118
|
### 消息队列
|
|
103
119
|
|
|
104
120
|
```python
|
|
@@ -10,6 +10,8 @@ codosdk.egg-info/requires.txt
|
|
|
10
10
|
codosdk.egg-info/top_level.txt
|
|
11
11
|
opssdk/__init__.py
|
|
12
12
|
opssdk/utils/__init__.py
|
|
13
|
+
tests/__init__.py
|
|
14
|
+
tests/test_openapi_sign.py
|
|
13
15
|
websdk2/__init__.py
|
|
14
16
|
websdk2/api_set.py
|
|
15
17
|
websdk2/application.py
|
|
@@ -29,6 +31,8 @@ websdk2/ldap.py
|
|
|
29
31
|
websdk2/logger.py
|
|
30
32
|
websdk2/model_utils.py
|
|
31
33
|
websdk2/mqhelper.py
|
|
34
|
+
websdk2/openapi_client.py
|
|
35
|
+
websdk2/openapi_sign.py
|
|
32
36
|
websdk2/program.py
|
|
33
37
|
websdk2/salt_api.py
|
|
34
38
|
websdk2/sqlalchemy_pagination.py
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
# -*-coding:utf-8-*-
|
|
3
3
|
""""
|
|
4
4
|
author : shenshuo
|
|
5
|
-
date :
|
|
5
|
+
date : 2026年07月29日
|
|
6
6
|
desc : CODO SDK
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
import sys
|
|
10
10
|
from setuptools import setup, find_packages
|
|
11
11
|
|
|
12
|
-
VERSION = '1.0.
|
|
12
|
+
VERSION = '1.0.57'
|
|
13
13
|
|
|
14
14
|
if sys.version_info < (2, 7) or (3, 0) <= sys.version_info < (3, 6):
|
|
15
15
|
print('This program requires at least Python 2.7 or 3.6 to run.')
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
from websdk2.openapi_sign import build_auth_headers, sign
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_get_vector():
|
|
7
|
+
ak = "codoAKTEST0001"
|
|
8
|
+
sk = "testSecretKeyForOpenAPISign0001"
|
|
9
|
+
ts = "1700000000"
|
|
10
|
+
nonce = "nonce-fixed-001"
|
|
11
|
+
headers = {
|
|
12
|
+
"x-codo-access-key": ak,
|
|
13
|
+
"x-codo-timestamp": ts,
|
|
14
|
+
"x-codo-nonce": nonce,
|
|
15
|
+
}
|
|
16
|
+
sig = sign(sk, "GET", "/api/p/v4/biz/list/", {}, headers, b"", ts)
|
|
17
|
+
assert sig == "ce94e413a8f752ed75a7c129c184f14892d8b24f30222607994276174339e07f"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_post_vector():
|
|
21
|
+
ak = "codoAKTEST0001"
|
|
22
|
+
sk = "testSecretKeyForOpenAPISign0001"
|
|
23
|
+
ts = "1700000000"
|
|
24
|
+
nonce = "nonce-fixed-001"
|
|
25
|
+
headers = {
|
|
26
|
+
"content-type": "application/json",
|
|
27
|
+
"x-codo-access-key": ak,
|
|
28
|
+
"x-codo-timestamp": ts,
|
|
29
|
+
"x-codo-nonce": nonce,
|
|
30
|
+
}
|
|
31
|
+
body = b'{"page":1}'
|
|
32
|
+
sig = sign(sk, "POST", "/api/p/v4/user/", {}, headers, body, ts)
|
|
33
|
+
assert sig == "4a2adef69ff58b92bc99c04474811e78082a5e3f5675a3c1d26e19ed3e7dc163"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_build_auth_headers_keys():
|
|
37
|
+
h = build_auth_headers(
|
|
38
|
+
"ak", "sk", "GET", "/api/x/", query=None, body=None, timestamp=1700000000, nonce="n1"
|
|
39
|
+
)
|
|
40
|
+
assert "X-Codo-Access-Key" in h
|
|
41
|
+
assert "X-Codo-Signature" in h
|
|
File without changes
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""开放 API 客户端:AK/SK 签名请求 CODO 网关。"""
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Any, Dict, Optional
|
|
7
|
+
from urllib.parse import urlencode, urlparse
|
|
8
|
+
import requests
|
|
9
|
+
from .configs import configs
|
|
10
|
+
from .consts import const
|
|
11
|
+
from .openapi_sign import build_auth_headers
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class OpenAPIClient:
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
access_key: str,
|
|
20
|
+
secret_key: str,
|
|
21
|
+
endpoint: str = None,
|
|
22
|
+
request_timeout: int = 10,
|
|
23
|
+
):
|
|
24
|
+
self.access_key = access_key
|
|
25
|
+
self.secret_key = secret_key
|
|
26
|
+
self.request_timeout = request_timeout
|
|
27
|
+
if endpoint:
|
|
28
|
+
self.endpoint = endpoint.rstrip("/")
|
|
29
|
+
else:
|
|
30
|
+
self.endpoint = (configs.get(const.WEBSITE_API_GW_URL) or "http://gw.opendevops.cn").rstrip(
|
|
31
|
+
"/"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def _path_and_query(self, url: str, params: Optional[dict]):
|
|
35
|
+
if url.startswith("http://") or url.startswith("https://"):
|
|
36
|
+
parsed = urlparse(url)
|
|
37
|
+
path = parsed.path or "/"
|
|
38
|
+
# merge query from url
|
|
39
|
+
q = {}
|
|
40
|
+
if parsed.query:
|
|
41
|
+
from urllib.parse import parse_qs
|
|
42
|
+
|
|
43
|
+
for k, vs in parse_qs(parsed.query, keep_blank_values=True).items():
|
|
44
|
+
q[k] = vs[0] if len(vs) == 1 else vs
|
|
45
|
+
if params:
|
|
46
|
+
q.update(params)
|
|
47
|
+
return path, q, f"{parsed.scheme}://{parsed.netloc}"
|
|
48
|
+
path = url if url.startswith("/") else "/" + url
|
|
49
|
+
return path, params or {}, self.endpoint
|
|
50
|
+
|
|
51
|
+
def request(
|
|
52
|
+
self,
|
|
53
|
+
method: str,
|
|
54
|
+
url: str,
|
|
55
|
+
params: dict = None,
|
|
56
|
+
data: Any = None,
|
|
57
|
+
json_body: Any = None,
|
|
58
|
+
headers: dict = None,
|
|
59
|
+
) -> requests.Response:
|
|
60
|
+
path, query, base = self._path_and_query(url, params)
|
|
61
|
+
method_u = method.upper()
|
|
62
|
+
body_bytes = None
|
|
63
|
+
content_type = None
|
|
64
|
+
if json_body is not None:
|
|
65
|
+
body_bytes = json.dumps(json_body, ensure_ascii=False, separators=(",", ":")).encode(
|
|
66
|
+
"utf-8"
|
|
67
|
+
)
|
|
68
|
+
content_type = "application/json"
|
|
69
|
+
elif data is not None:
|
|
70
|
+
if isinstance(data, (dict, list)):
|
|
71
|
+
body_bytes = json.dumps(data, ensure_ascii=False, separators=(",", ":")).encode(
|
|
72
|
+
"utf-8"
|
|
73
|
+
)
|
|
74
|
+
content_type = "application/json"
|
|
75
|
+
elif isinstance(data, str):
|
|
76
|
+
body_bytes = data.encode("utf-8")
|
|
77
|
+
else:
|
|
78
|
+
body_bytes = data
|
|
79
|
+
|
|
80
|
+
auth_headers = build_auth_headers(
|
|
81
|
+
self.access_key,
|
|
82
|
+
self.secret_key,
|
|
83
|
+
method_u,
|
|
84
|
+
path,
|
|
85
|
+
query=query,
|
|
86
|
+
body=body_bytes,
|
|
87
|
+
content_type=content_type,
|
|
88
|
+
)
|
|
89
|
+
final_headers = dict(headers or {})
|
|
90
|
+
final_headers.update(auth_headers)
|
|
91
|
+
|
|
92
|
+
full_url = f"{base}{path}"
|
|
93
|
+
if query:
|
|
94
|
+
full_url = f"{full_url}?{urlencode(query, doseq=True)}"
|
|
95
|
+
|
|
96
|
+
return requests.request(
|
|
97
|
+
method_u,
|
|
98
|
+
full_url,
|
|
99
|
+
headers=final_headers,
|
|
100
|
+
data=body_bytes,
|
|
101
|
+
timeout=self.request_timeout,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
def with_params_data_url(self, **kwargs):
|
|
105
|
+
url = kwargs.get("url")
|
|
106
|
+
method = kwargs.get("method") or "GET"
|
|
107
|
+
params = kwargs.get("params")
|
|
108
|
+
body = kwargs.get("body")
|
|
109
|
+
req_json = kwargs.get("json")
|
|
110
|
+
if method.upper() in ("GET", "DELETE", "HEAD"):
|
|
111
|
+
return dict(method=method, url=url, params=params)
|
|
112
|
+
return dict(method=method, url=url, params=params, body=body, json=req_json)
|
|
113
|
+
|
|
114
|
+
def do_action(self, **kwargs) -> str:
|
|
115
|
+
return self.do_action_v2(**kwargs).text
|
|
116
|
+
|
|
117
|
+
def do_action_v2(self, **kwargs) -> requests.Response:
|
|
118
|
+
return self.do_action_v3(**kwargs)
|
|
119
|
+
|
|
120
|
+
def do_action_v3(self, **kwargs) -> requests.Response:
|
|
121
|
+
method = kwargs.get("method") or "GET"
|
|
122
|
+
url = kwargs.get("url")
|
|
123
|
+
params = kwargs.get("params")
|
|
124
|
+
if kwargs.get("json") is not None:
|
|
125
|
+
return self.request(method, url, params=params, json_body=kwargs.get("json"))
|
|
126
|
+
body = kwargs.get("body")
|
|
127
|
+
return self.request(method, url, params=params, data=body)
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""CODO1-HMAC-SHA256 签名(与网关 / Go SDK 对齐)。"""
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import hmac
|
|
7
|
+
import secrets
|
|
8
|
+
import time
|
|
9
|
+
from typing import Dict, Mapping, Optional, Union
|
|
10
|
+
from urllib.parse import quote
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def sha256_hex(data: Union[bytes, str, None]) -> str:
|
|
14
|
+
if data is None:
|
|
15
|
+
data = b""
|
|
16
|
+
if isinstance(data, str):
|
|
17
|
+
data = data.encode("utf-8")
|
|
18
|
+
return hashlib.sha256(data).hexdigest()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def hmac_sha256_hex(key: str, msg: str) -> str:
|
|
22
|
+
return hmac.new(key.encode("utf-8"), msg.encode("utf-8"), hashlib.sha256).hexdigest()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def canonical_query(params: Optional[Mapping] = None) -> str:
|
|
26
|
+
if not params:
|
|
27
|
+
return ""
|
|
28
|
+
items = []
|
|
29
|
+
for k in sorted(params.keys()):
|
|
30
|
+
v = params[k]
|
|
31
|
+
if isinstance(v, (list, tuple)):
|
|
32
|
+
for vv in sorted(str(x) for x in v):
|
|
33
|
+
items.append(f"{quote(str(k), safe='-_.~')}={quote(str(vv), safe='-_.~')}")
|
|
34
|
+
elif v is None:
|
|
35
|
+
continue
|
|
36
|
+
else:
|
|
37
|
+
items.append(f"{quote(str(k), safe='-_.~')}={quote(str(v), safe='-_.~')}")
|
|
38
|
+
return "&".join(items)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def build_canonical_request(
|
|
42
|
+
method: str,
|
|
43
|
+
uri: str,
|
|
44
|
+
query: Optional[Mapping],
|
|
45
|
+
headers: Mapping[str, str],
|
|
46
|
+
body: Union[bytes, str, None],
|
|
47
|
+
) -> str:
|
|
48
|
+
if body is None:
|
|
49
|
+
body_b = b""
|
|
50
|
+
elif isinstance(body, str):
|
|
51
|
+
body_b = body.encode("utf-8")
|
|
52
|
+
else:
|
|
53
|
+
body_b = body
|
|
54
|
+
normalized = {str(k).lower(): str(v).strip() for k, v in headers.items()}
|
|
55
|
+
names = sorted(normalized.keys())
|
|
56
|
+
canonical_headers = "".join(f"{n}:{normalized[n]}\n" for n in names)
|
|
57
|
+
signed_headers = ";".join(names)
|
|
58
|
+
return "\n".join(
|
|
59
|
+
[
|
|
60
|
+
method.upper(),
|
|
61
|
+
uri,
|
|
62
|
+
canonical_query(query),
|
|
63
|
+
canonical_headers,
|
|
64
|
+
signed_headers,
|
|
65
|
+
sha256_hex(body_b),
|
|
66
|
+
]
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def build_string_to_sign(timestamp: Union[str, int], canonical_request: str) -> str:
|
|
71
|
+
return "\n".join(
|
|
72
|
+
["CODO1-HMAC-SHA256", str(timestamp), sha256_hex(canonical_request.encode("utf-8"))]
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def sign(
|
|
77
|
+
secret_key: str,
|
|
78
|
+
method: str,
|
|
79
|
+
uri: str,
|
|
80
|
+
query: Optional[Mapping],
|
|
81
|
+
headers: Mapping[str, str],
|
|
82
|
+
body: Union[bytes, str, None],
|
|
83
|
+
timestamp: Union[str, int],
|
|
84
|
+
) -> str:
|
|
85
|
+
cr = build_canonical_request(method, uri, query, headers, body)
|
|
86
|
+
sts = build_string_to_sign(timestamp, cr)
|
|
87
|
+
return hmac_sha256_hex(secret_key, sts)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def build_auth_headers(
|
|
91
|
+
access_key: str,
|
|
92
|
+
secret_key: str,
|
|
93
|
+
method: str,
|
|
94
|
+
uri: str,
|
|
95
|
+
query: Optional[Mapping] = None,
|
|
96
|
+
body: Union[bytes, str, None] = None,
|
|
97
|
+
content_type: Optional[str] = None,
|
|
98
|
+
timestamp: Optional[Union[str, int]] = None,
|
|
99
|
+
nonce: Optional[str] = None,
|
|
100
|
+
) -> Dict[str, str]:
|
|
101
|
+
ts = str(timestamp if timestamp is not None else int(time.time()))
|
|
102
|
+
n = nonce or secrets.token_hex(8)
|
|
103
|
+
sign_headers = {
|
|
104
|
+
"x-codo-access-key": access_key,
|
|
105
|
+
"x-codo-timestamp": ts,
|
|
106
|
+
"x-codo-nonce": n,
|
|
107
|
+
}
|
|
108
|
+
body_b = b"" if body is None else (body.encode("utf-8") if isinstance(body, str) else body)
|
|
109
|
+
if content_type and body_b:
|
|
110
|
+
sign_headers["content-type"] = content_type
|
|
111
|
+
signature = sign(secret_key, method, uri, query, sign_headers, body_b, ts)
|
|
112
|
+
out = {
|
|
113
|
+
"X-Codo-Access-Key": access_key,
|
|
114
|
+
"X-Codo-Timestamp": ts,
|
|
115
|
+
"X-Codo-Nonce": n,
|
|
116
|
+
"X-Codo-Signature": signature,
|
|
117
|
+
}
|
|
118
|
+
if content_type:
|
|
119
|
+
out["Content-Type"] = content_type
|
|
120
|
+
return out
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|