c2cciutils 1.7.4.dev128__tar.gz → 1.7.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.
Potentially problematic release.
This version of c2cciutils might be problematic. Click here for more details.
- {c2cciutils-1.7.4.dev128 → c2cciutils-1.7.5}/PKG-INFO +1 -4
- {c2cciutils-1.7.4.dev128 → c2cciutils-1.7.5}/c2cciutils/publish.py +0 -227
- {c2cciutils-1.7.4.dev128 → c2cciutils-1.7.5}/c2cciutils/scripts/publish.py +0 -26
- {c2cciutils-1.7.4.dev128 → c2cciutils-1.7.5}/pyproject.toml +1 -5
- {c2cciutils-1.7.4.dev128 → c2cciutils-1.7.5}/LICENSE +0 -0
- {c2cciutils-1.7.4.dev128 → c2cciutils-1.7.5}/README.md +0 -0
- {c2cciutils-1.7.4.dev128 → c2cciutils-1.7.5}/c2cciutils/__init__.py +0 -0
- {c2cciutils-1.7.4.dev128 → c2cciutils-1.7.5}/c2cciutils/applications-versions.yaml +0 -0
- {c2cciutils-1.7.4.dev128 → c2cciutils-1.7.5}/c2cciutils/applications.yaml +0 -0
- {c2cciutils-1.7.4.dev128 → c2cciutils-1.7.5}/c2cciutils/applications_definition.py +0 -0
- {c2cciutils-1.7.4.dev128 → c2cciutils-1.7.5}/c2cciutils/branches.graphql +0 -0
- {c2cciutils-1.7.4.dev128 → c2cciutils-1.7.5}/c2cciutils/commits.graphql +0 -0
- {c2cciutils-1.7.4.dev128 → c2cciutils-1.7.5}/c2cciutils/configuration.py +0 -0
- {c2cciutils-1.7.4.dev128 → c2cciutils-1.7.5}/c2cciutils/default_branch.graphql +0 -0
- {c2cciutils-1.7.4.dev128 → c2cciutils-1.7.5}/c2cciutils/env.py +0 -0
- {c2cciutils-1.7.4.dev128 → c2cciutils-1.7.5}/c2cciutils/lib/docker.py +0 -0
- {c2cciutils-1.7.4.dev128 → c2cciutils-1.7.5}/c2cciutils/lib/oidc.py +0 -0
- {c2cciutils-1.7.4.dev128 → c2cciutils-1.7.5}/c2cciutils/package-lock.json +0 -0
- {c2cciutils-1.7.4.dev128 → c2cciutils-1.7.5}/c2cciutils/package.json +0 -0
- {c2cciutils-1.7.4.dev128 → c2cciutils-1.7.5}/c2cciutils/schema-applications.json +0 -0
- {c2cciutils-1.7.4.dev128 → c2cciutils-1.7.5}/c2cciutils/schema.json +0 -0
- {c2cciutils-1.7.4.dev128 → c2cciutils-1.7.5}/c2cciutils/scripts/__init__.py +0 -0
- {c2cciutils-1.7.4.dev128 → c2cciutils-1.7.5}/c2cciutils/scripts/clean.py +0 -0
- {c2cciutils-1.7.4.dev128 → c2cciutils-1.7.5}/c2cciutils/scripts/docker_logs.py +0 -0
- {c2cciutils-1.7.4.dev128 → c2cciutils-1.7.5}/c2cciutils/scripts/docker_versions_gen.py +0 -0
- {c2cciutils-1.7.4.dev128 → c2cciutils-1.7.5}/c2cciutils/scripts/download_applications.py +0 -0
- {c2cciutils-1.7.4.dev128 → c2cciutils-1.7.5}/c2cciutils/scripts/env.py +0 -0
- {c2cciutils-1.7.4.dev128 → c2cciutils-1.7.5}/c2cciutils/scripts/k8s/__init__.py +0 -0
- {c2cciutils-1.7.4.dev128 → c2cciutils-1.7.5}/c2cciutils/scripts/k8s/db.py +0 -0
- {c2cciutils-1.7.4.dev128 → c2cciutils-1.7.5}/c2cciutils/scripts/k8s/install.py +0 -0
- {c2cciutils-1.7.4.dev128 → c2cciutils-1.7.5}/c2cciutils/scripts/k8s/logs.py +0 -0
- {c2cciutils-1.7.4.dev128 → c2cciutils-1.7.5}/c2cciutils/scripts/k8s/wait.py +0 -0
- {c2cciutils-1.7.4.dev128 → c2cciutils-1.7.5}/c2cciutils/scripts/main.py +0 -0
- {c2cciutils-1.7.4.dev128 → c2cciutils-1.7.5}/c2cciutils/scripts/pin_pipenv.py +0 -0
- {c2cciutils-1.7.4.dev128 → c2cciutils-1.7.5}/c2cciutils/scripts/trigger_image_update.py +0 -0
- {c2cciutils-1.7.4.dev128 → c2cciutils-1.7.5}/c2cciutils/scripts/version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: c2cciutils
|
|
3
|
-
Version: 1.7.
|
|
3
|
+
Version: 1.7.5
|
|
4
4
|
Summary: Common utilities for Camptocamp CI
|
|
5
5
|
Home-page: https://github.com/camptocamp/c2cciutils
|
|
6
6
|
License: FreeBSD
|
|
@@ -31,9 +31,6 @@ Provides-Extra: version
|
|
|
31
31
|
Requires-Dist: PyYAML (>=6.0.0,<7.0.0)
|
|
32
32
|
Requires-Dist: debian-inspector (>=31.0.0,<32.0.0)
|
|
33
33
|
Requires-Dist: defusedxml (>=0.0.0,<1.0.0)
|
|
34
|
-
Requires-Dist: google-api-python-client (>=2.0.0,<3.0.0) ; extra == "publish"
|
|
35
|
-
Requires-Dist: google-auth-httplib2 (>=0.0.0,<1.0.0) ; extra == "publish"
|
|
36
|
-
Requires-Dist: google-auth-oauthlib (>=1.0.0,<2.0.0) ; extra == "publish"
|
|
37
34
|
Requires-Dist: id (>=1.0.0,<2.0.0) ; extra == "publish"
|
|
38
35
|
Requires-Dist: multi-repo-automation (>=1.0.0,<2.0.0) ; extra == "version"
|
|
39
36
|
Requires-Dist: requests (>=2.0.0,<3.0.0)
|
|
@@ -2,246 +2,19 @@
|
|
|
2
2
|
The publishing functions.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
import argparse
|
|
6
5
|
import datetime
|
|
7
6
|
import glob
|
|
8
7
|
import os
|
|
9
|
-
import pickle # nosec
|
|
10
8
|
import re
|
|
11
9
|
import subprocess # nosec
|
|
12
10
|
import sys
|
|
13
11
|
import tomllib
|
|
14
|
-
import uuid
|
|
15
|
-
from typing import Optional
|
|
16
12
|
|
|
17
13
|
import ruamel.yaml
|
|
18
|
-
from google.auth.transport.requests import Request
|
|
19
|
-
from google.oauth2.credentials import Credentials
|
|
20
|
-
from google_auth_oauthlib.flow import InstalledAppFlow
|
|
21
|
-
from googleapiclient.discovery import build
|
|
22
14
|
|
|
23
15
|
import c2cciutils.configuration
|
|
24
16
|
|
|
25
17
|
|
|
26
|
-
class GoogleCalendar:
|
|
27
|
-
"""
|
|
28
|
-
Interact with the Google Calendar API.
|
|
29
|
-
"""
|
|
30
|
-
|
|
31
|
-
# pylint: disable=too-many-instance-attributes
|
|
32
|
-
def __init__(self) -> None:
|
|
33
|
-
"""
|
|
34
|
-
Initialize.
|
|
35
|
-
"""
|
|
36
|
-
self.scopes = ["https://www.googleapis.com/auth/calendar"] # in fact it is better to hard-code this
|
|
37
|
-
self.credentials_pickle_file = os.environ.get("TMP_CREDS_FILE", f"/tmp/{uuid.uuid4()}.pickle")
|
|
38
|
-
self.credentials_json_file = os.environ.get(
|
|
39
|
-
"GOOGLE_CREDS_JSON_FILE", "~/google-credentials-c2cibot.json"
|
|
40
|
-
) # used to refresh the refresh_token or to initialize the credentials the first time
|
|
41
|
-
self.calendar_id = os.environ.get(
|
|
42
|
-
"GOOGLE_CALENDAR_ID", c2cciutils.gopass("gs/ci/google_calendar/calendarId")
|
|
43
|
-
)
|
|
44
|
-
self.token = os.environ.get("GOOGLE_TOKEN", c2cciutils.gopass("gs/ci/google_calendar/token"))
|
|
45
|
-
self.token_uri = os.environ.get(
|
|
46
|
-
"GOOGLE_TOKEN_URI", c2cciutils.gopass("gs/ci/google_calendar/token_uri")
|
|
47
|
-
)
|
|
48
|
-
self.refresh_token = os.environ.get(
|
|
49
|
-
"GOOGLE_REFRESH_TOKEN",
|
|
50
|
-
c2cciutils.gopass("gs/ci/google_calendar/refresh_token"),
|
|
51
|
-
)
|
|
52
|
-
self.client_id = os.environ.get(
|
|
53
|
-
"GOOGLE_CLIENT_ID", c2cciutils.gopass("gs/ci/google_calendar/client_id")
|
|
54
|
-
)
|
|
55
|
-
self.client_secret = os.environ.get(
|
|
56
|
-
"GOOGLE_CLIENT_SECRET",
|
|
57
|
-
c2cciutils.gopass("gs/ci/google_calendar/client_secret"),
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
self.creds: Credentials = self.init_calendar_service()
|
|
61
|
-
self._update_creds()
|
|
62
|
-
self.service = build("calendar", "v3", credentials=self.creds)
|
|
63
|
-
|
|
64
|
-
def init_calendar_service(self) -> Credentials: # type: ignore
|
|
65
|
-
"""
|
|
66
|
-
Initialize the calendar service.
|
|
67
|
-
"""
|
|
68
|
-
# The file token pickle stores the user's access and refresh tokens, and is
|
|
69
|
-
# created automatically when the authorization flow completes for the first
|
|
70
|
-
# time.
|
|
71
|
-
if os.path.exists(self.credentials_pickle_file):
|
|
72
|
-
with open(self.credentials_pickle_file, "rb") as token:
|
|
73
|
-
creds = pickle.load(token) # nosec
|
|
74
|
-
# If there are no (valid) credentials available, let the user log in.
|
|
75
|
-
if not creds or not creds.valid: # pylint: disable=possibly-used-before-assignment
|
|
76
|
-
if creds and creds.expired and creds.refresh_token:
|
|
77
|
-
creds.refresh(Request()) # type: ignore
|
|
78
|
-
else:
|
|
79
|
-
if self.token:
|
|
80
|
-
creds = Credentials( # type: ignore
|
|
81
|
-
self.token,
|
|
82
|
-
refresh_token=self.refresh_token,
|
|
83
|
-
token_uri=self.token_uri,
|
|
84
|
-
client_id=self.client_id,
|
|
85
|
-
client_secret=self.client_secret,
|
|
86
|
-
scopes=self.scopes,
|
|
87
|
-
)
|
|
88
|
-
else:
|
|
89
|
-
flow = InstalledAppFlow.from_client_secrets_file(self.credentials_json_file, self.scopes)
|
|
90
|
-
creds = flow.run_local_server(port=0)
|
|
91
|
-
self.refresh_token = creds
|
|
92
|
-
|
|
93
|
-
# Save the credentials for the next run
|
|
94
|
-
with open(self.credentials_pickle_file, "wb") as token:
|
|
95
|
-
pickle.dump(creds, token)
|
|
96
|
-
|
|
97
|
-
def _update_creds(self) -> None:
|
|
98
|
-
"""
|
|
99
|
-
Update the credentials.
|
|
100
|
-
"""
|
|
101
|
-
self.client_id = self.creds.client_id
|
|
102
|
-
self.client_secret = self.creds.client_secret
|
|
103
|
-
self.token = self.creds.token
|
|
104
|
-
self.token_uri = self.creds.token_uri
|
|
105
|
-
self.refresh_token = self.creds.refresh_token
|
|
106
|
-
|
|
107
|
-
def print_all_calendars(self) -> None:
|
|
108
|
-
"""
|
|
109
|
-
Print all calendar events.
|
|
110
|
-
"""
|
|
111
|
-
# list all the calendars that the user has access to.
|
|
112
|
-
# used to debug credentials
|
|
113
|
-
print("Getting list of calendars")
|
|
114
|
-
calendars_result = self.service.calendarList().list().execute()
|
|
115
|
-
|
|
116
|
-
calendars = calendars_result.get("items", [])
|
|
117
|
-
|
|
118
|
-
if not calendars:
|
|
119
|
-
print("::error::No calendars found.")
|
|
120
|
-
for calendar in calendars:
|
|
121
|
-
summary = calendar["summary"]
|
|
122
|
-
event_id = calendar["id"]
|
|
123
|
-
primary = "Primary" if calendar.get("primary") else ""
|
|
124
|
-
print(f"{summary}\t{event_id}\t{primary}")
|
|
125
|
-
|
|
126
|
-
def print_latest_events(self, time_min: Optional[datetime.datetime] = None) -> None:
|
|
127
|
-
"""
|
|
128
|
-
Print latest events.
|
|
129
|
-
|
|
130
|
-
Arguments:
|
|
131
|
-
time_min: The time to be considered.
|
|
132
|
-
"""
|
|
133
|
-
now = datetime.datetime.utcnow()
|
|
134
|
-
if not time_min:
|
|
135
|
-
time_min = datetime.datetime.utcnow() - datetime.timedelta(days=30)
|
|
136
|
-
events_result = (
|
|
137
|
-
self.service.events()
|
|
138
|
-
.list(
|
|
139
|
-
calendarId=self.calendar_id,
|
|
140
|
-
timeMin=time_min.isoformat() + "Z",
|
|
141
|
-
timeMax=now.isoformat() + "Z",
|
|
142
|
-
singleEvents=True,
|
|
143
|
-
orderBy="startTime",
|
|
144
|
-
)
|
|
145
|
-
.execute()
|
|
146
|
-
)
|
|
147
|
-
events = events_result.get("items", [])
|
|
148
|
-
|
|
149
|
-
if not events:
|
|
150
|
-
print("::error::No upcoming events found.")
|
|
151
|
-
for event in events:
|
|
152
|
-
start = event["start"].get("dateTime", event["start"].get("date"))
|
|
153
|
-
print(start, event["summary"])
|
|
154
|
-
|
|
155
|
-
def create_event(
|
|
156
|
-
self,
|
|
157
|
-
summary: str = f"dummy/image:{datetime.datetime.now().isoformat()}",
|
|
158
|
-
description: str = "description",
|
|
159
|
-
) -> None:
|
|
160
|
-
"""
|
|
161
|
-
Create a calendar event.
|
|
162
|
-
|
|
163
|
-
Arguments:
|
|
164
|
-
summary: The event summary
|
|
165
|
-
description: The event description
|
|
166
|
-
"""
|
|
167
|
-
now = datetime.datetime.now()
|
|
168
|
-
start = now.isoformat()
|
|
169
|
-
end = (now + datetime.timedelta(minutes=15)).isoformat()
|
|
170
|
-
body = {
|
|
171
|
-
"summary": summary,
|
|
172
|
-
"description": description,
|
|
173
|
-
"start": {"dateTime": start, "timeZone": "Europe/Zurich"},
|
|
174
|
-
"end": {"dateTime": end, "timeZone": "Europe/Zurich"},
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
event_result = self.service.events().insert(calendarId=self.calendar_id, body=body).execute()
|
|
178
|
-
print(f"Created event with id: {event_result['id']}")
|
|
179
|
-
|
|
180
|
-
def save_credentials_to_gopass(self) -> None:
|
|
181
|
-
"""
|
|
182
|
-
Save the calendar credentials to gopass.
|
|
183
|
-
"""
|
|
184
|
-
objects_to_save = {
|
|
185
|
-
"gs/ci/google_calendar/calendarId": self.calendar_id,
|
|
186
|
-
"gs/ci/google_calendar/token": self.token,
|
|
187
|
-
"gs/ci/google_calendar/token_uri": self.token_uri,
|
|
188
|
-
"gs/ci/google_calendar/refresh_token": self.refresh_token,
|
|
189
|
-
"gs/ci/google_calendar/client_id": self.client_id,
|
|
190
|
-
"gs/ci/google_calendar/client_secret": self.client_secret,
|
|
191
|
-
}
|
|
192
|
-
for key, secret in objects_to_save.items():
|
|
193
|
-
assert secret is not None
|
|
194
|
-
c2cciutils.gopass_put(secret, key)
|
|
195
|
-
|
|
196
|
-
def __del__(self) -> None:
|
|
197
|
-
"""
|
|
198
|
-
Delete the credentials file.
|
|
199
|
-
"""
|
|
200
|
-
if os.path.exists(self.credentials_pickle_file):
|
|
201
|
-
os.remove(self.credentials_pickle_file)
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
def main_calendar() -> None:
|
|
205
|
-
"""
|
|
206
|
-
Run the calendar main function.
|
|
207
|
-
"""
|
|
208
|
-
parser = argparse.ArgumentParser(
|
|
209
|
-
description="Interact with google API for the Docker publishing calendar"
|
|
210
|
-
)
|
|
211
|
-
parser.add_argument(
|
|
212
|
-
"--refresh-gopass-credentials",
|
|
213
|
-
action="store_true",
|
|
214
|
-
help="Refresh the credentials in gopass using google API",
|
|
215
|
-
)
|
|
216
|
-
parser.add_argument(
|
|
217
|
-
"--show-events-since",
|
|
218
|
-
help="show the calendar events since a date in 'YYYY-mm-dd' format",
|
|
219
|
-
type=lambda s: datetime.datetime.strptime(s, "%Y-%m-%d"),
|
|
220
|
-
)
|
|
221
|
-
parser.add_argument(
|
|
222
|
-
"--create-test-event",
|
|
223
|
-
action="store_true",
|
|
224
|
-
help="Create a dummy event to check that the calendar settings are correct",
|
|
225
|
-
)
|
|
226
|
-
args = parser.parse_args()
|
|
227
|
-
|
|
228
|
-
if args.show_events_since or args.refresh_gopass_credentials or args.create_test_event:
|
|
229
|
-
google_calendar = GoogleCalendar()
|
|
230
|
-
else:
|
|
231
|
-
parser.print_help()
|
|
232
|
-
|
|
233
|
-
if args.show_events_since:
|
|
234
|
-
google_calendar.print_latest_events( # pylint: disable=possibly-used-before-assignment
|
|
235
|
-
args.show_events_since
|
|
236
|
-
)
|
|
237
|
-
|
|
238
|
-
if args.refresh_gopass_credentials:
|
|
239
|
-
google_calendar.save_credentials_to_gopass()
|
|
240
|
-
|
|
241
|
-
if args.create_test_event:
|
|
242
|
-
google_calendar.create_event()
|
|
243
|
-
|
|
244
|
-
|
|
245
18
|
def pip(
|
|
246
19
|
package: c2cciutils.configuration.PublishPypiPackage, version: str, version_type: str, publish: bool
|
|
247
20
|
) -> bool:
|
|
@@ -23,7 +23,6 @@ import c2cciutils.lib.docker
|
|
|
23
23
|
import c2cciutils.lib.oidc
|
|
24
24
|
import c2cciutils.publish
|
|
25
25
|
import c2cciutils.scripts.download_applications
|
|
26
|
-
from c2cciutils.publish import GoogleCalendar
|
|
27
26
|
from c2cciutils.scripts.trigger_image_update import dispatch
|
|
28
27
|
|
|
29
28
|
|
|
@@ -185,13 +184,6 @@ def main() -> None:
|
|
|
185
184
|
else:
|
|
186
185
|
success &= c2cciutils.publish.pip(package, version, version_type, publish)
|
|
187
186
|
|
|
188
|
-
google_calendar = None
|
|
189
|
-
google_calendar_publish = config.get("publish", {}).get("google_calendar", False) is not False
|
|
190
|
-
google_calendar_config = cast(
|
|
191
|
-
c2cciutils.configuration.PublishGoogleCalendarConfig,
|
|
192
|
-
config.get("publish", {}).get("google_calendar", {}),
|
|
193
|
-
)
|
|
194
|
-
|
|
195
187
|
docker_config = cast(
|
|
196
188
|
c2cciutils.configuration.PublishDockerConfig,
|
|
197
189
|
config.get("publish", {}).get("docker", {}) if config.get("publish", {}).get("docker", False) else {},
|
|
@@ -311,24 +303,6 @@ def main() -> None:
|
|
|
311
303
|
conf, name, image_conf, tag_src, tags, images_full
|
|
312
304
|
)
|
|
313
305
|
|
|
314
|
-
if google_calendar_publish:
|
|
315
|
-
if version_type in google_calendar_config.get(
|
|
316
|
-
"on", c2cciutils.configuration.PUBLISH_GOOGLE_CALENDAR_ON_DEFAULT
|
|
317
|
-
):
|
|
318
|
-
if not google_calendar:
|
|
319
|
-
google_calendar = GoogleCalendar()
|
|
320
|
-
summary = f"{image_conf['name']}:{', '.join(tags_calendar)}"
|
|
321
|
-
description = "\n".join(
|
|
322
|
-
[
|
|
323
|
-
f"Published the image {image_conf['name']}",
|
|
324
|
-
f"Published on: {', '.join(docker_config['repository'].keys())}",
|
|
325
|
-
f"With tags: {', '.join(tags_calendar)}",
|
|
326
|
-
f"For version type: {version_type}",
|
|
327
|
-
]
|
|
328
|
-
)
|
|
329
|
-
|
|
330
|
-
google_calendar.create_event(summary, description)
|
|
331
|
-
|
|
332
306
|
if args.dry_run:
|
|
333
307
|
sys.exit(0)
|
|
334
308
|
|
|
@@ -19,7 +19,7 @@ strict = true
|
|
|
19
19
|
|
|
20
20
|
[tool.poetry]
|
|
21
21
|
name = "c2cciutils"
|
|
22
|
-
version = "1.7.
|
|
22
|
+
version = "1.7.5"
|
|
23
23
|
description = "Common utilities for Camptocamp CI"
|
|
24
24
|
readme = "README.md"
|
|
25
25
|
authors = ["Camptocamp <info@camptocamp.com>"]
|
|
@@ -51,7 +51,6 @@ c2cciutils-publish = "c2cciutils.scripts.publish:main"
|
|
|
51
51
|
c2cciutils-version = "c2cciutils.scripts.version:main"
|
|
52
52
|
c2cciutils-clean = "c2cciutils.scripts.clean:main"
|
|
53
53
|
c2cciutils-checks = "c2cciutils.scripts.env:main"
|
|
54
|
-
c2cciutils-google-calendar = "c2cciutils.publish:main_calendar"
|
|
55
54
|
c2cciutils-k8s-install = "c2cciutils.scripts.k8s.install:main"
|
|
56
55
|
c2cciutils-k8s-db = "c2cciutils.scripts.k8s.db:main"
|
|
57
56
|
c2cciutils-k8s-wait = "c2cciutils.scripts.k8s.wait:main"
|
|
@@ -65,9 +64,6 @@ c2cciutils-docker-versions-gen = "c2cciutils.scripts.docker_versions_gen:main"
|
|
|
65
64
|
[tool.poetry.dependencies]
|
|
66
65
|
python = ">=3.9,<4.0"
|
|
67
66
|
requests = "2.32.5"
|
|
68
|
-
google-api-python-client = { version = "2.148.0", optional = true }
|
|
69
|
-
google-auth-httplib2 = { version = "0.2.0", optional = true }
|
|
70
|
-
google-auth-oauthlib = { version = "1.2.2", optional = true }
|
|
71
67
|
"ruamel.yaml" = "0.18.15"
|
|
72
68
|
defusedxml = "0.7.1"
|
|
73
69
|
twine = { version = "5.1.1", optional = true }
|
|
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
|