-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgitkup.py
320 lines (253 loc) · 9.35 KB
/
gitkup.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
#!/usr/bin/env python3
# gitlab server easy backup [ahah you got it :>]
# need token with right on API, READ_REPOSITORY
__author__ = "G Pullara"
__copyright__ = "Copyright 2018"
__credits__ = [""]
__license__ = "BSD-3clause"
__version__ = "0.3.4"
__maintainer__ = "gionniboy"
__email__ = "[email protected]"
__status__ = "Development"
import os
import sys
import re
# import signal
import argparse
from datetime import datetime
import configparser
import logging
import logging.config
import json
from subprocess import Popen, PIPE
import smtplib
from email.mime.text import MIMEText
import email.utils
import validators
import requests
from git import Repo, GitCommandError
# istanziate logger
LOGGER = logging.getLogger(__name__)
def setup_logging(filepath="logging.json", log_level=logging.INFO):
""" setup logging based on json dict
:param filepath: filename
:param type: string
"""
if not os.path.exists(filepath):
LOGGER.error('no logging config file founded.')
sys.exit('Create logging.json config file and restart.')
with open(filepath, 'r') as fileconfig:
config = json.load(fileconfig)
logging.config.dictConfig(config)
LOGGER.info('LOGGING SETUP from JSON %s', filepath)
LOGGER.debug('LOGGING OK - path %s - level %s', filepath, log_level)
return True
def read_config(default_conf="config.local.ini", mail=False):
""" setup configuration
:param default_conf: filename
:param type: string
:param mail: arg flag to mail notification
:param type: boolean
:return GITLABSERVER: gitlab server host
:return type: string
:return GITLABTOKEN: gitlab private token
:return type: string
:return MAILSERVER: mail server host
:return type: string
:return MAILPORT: mail server port
:return type: int
:return MAILACCOUNT: mail account sender
:return type: string
:return MAILPASSWORD: mail password
:return type: string
:return DESTINATIONMAIL: mail account destination
:return type: string
"""
if not os.path.exists(default_conf):
LOGGER.error('no local config file founded.')
sys.exit("Create config.local.ini from config.ini and restart.")
# load configparser with interpolation to allow dynamic config file
config = configparser.ConfigParser()
config._interpolation = configparser.ExtendedInterpolation()
config.read(default_conf)
LOGGER.info("Config file loaded %s", default_conf)
# load gitlab section
GITLABSERVER = config['GITLAB']['SERVER']
GITLABTOKEN = config['GITLAB']['TOKEN']
# load mail section
if mail == True:
MAILSERVER = config['MAIL']['SERVER']
validators.domain(MAILSERVER)
MAILPORT = config['MAIL']['PORT']
MAILACCOUNT = config['MAIL']['ACCOUNT']
validators.email(MAILACCOUNT)
MAILPASSWORD = config['MAIL']['PASSWORD']
DESTINATIONMAIL = config['MAIL']['DESTINATION']
validators.email(DESTINATIONMAIL)
LOGGER.debug('MAIL - CONFIGURATION LOADED')
return (GITLABSERVER, GITLABTOKEN, MAILSERVER, MAILPORT, MAILACCOUNT, MAILPASSWORD, DESTINATIONMAIL)
else:
return (GITLABSERVER, GITLABTOKEN)
# def signal_handler(signal, frame):
# """ trap ctrl-c signal """
# print('You pressed Ctrl+C!')
# quit()
def makedir(BACKUP_DIR):
""" magic!!!
:param BACKUP_DIR: filename
:param type: string
:return: create dir or print skip
"""
if not os.path.exists(BACKUP_DIR):
try:
os.mkdir(BACKUP_DIR)
LOGGER.info('Backup dir created: %s', BACKUP_DIR)
except PermissionError as err:
LOGGER.error(err)
sys.exit('permission error')
except IOError as err:
LOGGER.error(err)
sys.exit('IO error')
elif os.path.isfile(BACKUP_DIR):
LOGGER.error('File exists: %s', BACKUP_DIR)
sys.exit("file exists - dir can't be create")
else:
LOGGER.info("Directory exists: skip creation...")
def gitkup(BACKUP_DIR, URL, TOKEN):
""" Request GitLab API to retrieve projects list and backup
:param BACKUP_DIR: filename
:param type: string
:param URL: gitlab server domain
:param type: string
:param TOKEN: private gitlab token
:param type: string
: return: git clone or update private repositories
"""
gitlab_url = (
"https://{}/api/v4/projects?visibility=private&private_token={}".format(
URL, TOKEN))
LOGGER.info("Please wait: this operation may take a while ...")
r = requests.get(gitlab_url)
if r.status_code != 200:
# TODO: better status code handling here
r.raise_for_status()
LOGGER.error("Error, please retry")
sys.exit(0)
projects = r.json()
for project in projects:
url = project["ssh_url_to_repo"]
localPath = "{}/{}.git".format(BACKUP_DIR, project["path"])
if not os.path.exists(localPath):
LOGGER.info("Create backup for %s", localPath)
Repo.clone_from(url, localPath)
LOGGER.info("%s cloned", url)
else:
LOGGER.info("Update backup for %s", localPath)
# TODO: refactor this shit
dir_command = ['cd', localPath]
git_command = ['git', 'remote', 'update']
git_query = Popen(git_command)
backup_state = Popen(dir_command, cwd=BACKUP_DIR,
stdout=PIPE, stderr=PIPE)
backup_state.communicate()
git_query.communicate()
LOGGER.info("%s updated", url)
def sendmail(MAILSERVER, MAILPORT, MAILACCOUNT, MAILPASSWORD, DESTINATIONMAIL, GITLABSERVER):
""" send notification mail
:param MAILSERVER: mail server host
:param type: string
:param MAILPORT: mail server port
:param type: int
:param MAILACCOUNT: mail account sender
:param type: string
:param MAILPASSWORD: mail password
:param type: string
:param DESTINATIONMAIL: mail account destination
:param type: string
:param GITLABSERVER: gitlab server url
:param type: string
"""
# create email
message = MIMEText("gitkup task accomplished on {}".format(GITLABSERVER))
message.set_unixfrom(MAILACCOUNT)
message['From'] = email.utils.formataddr((MAILACCOUNT, MAILACCOUNT))
message['To'] = email.utils.formataddr(('Recipient', DESTINATIONMAIL))
message['Subject'] = "GITKUP task from {}".format(GITLABSERVER)
# TODO: make this shit ssl/tls compatible
# connect to mailserver
s = smtplib.SMTP_SSL(host=MAILSERVER, port=MAILPORT)
#s.set_debuglevel(True)
s.ehlo()
# If we can encrypt this session, do it
# print('(starting TLS)')
# s.starttls()
# s.ehlo() # reidentify ourselves over TLS connection
if s.has_extn('AUTH'):
LOGGER.debug('(logging in)')
s.login(MAILACCOUNT, MAILPASSWORD)
else:
LOGGER.debug('(no AUTH)')
quit()
# send the message via the server set up earlier.
s.sendmail(MAILACCOUNT,
[DESTINATIONMAIL],
message.as_string())
LOGGER.info("Mail sent succesfully")
del(message)
s.quit()
def str2bool(v):
"""
true/false for argparse mail flag
https://stackoverflow.com/questions/15008758/parsing-boolean-values-with-argparse
:param v:
:return: true/false or raise exception
"""
if v.lower() in ('yes', 'true', 't', 'y', '1'):
return True
elif v.lower() in ('no', 'false', 'f', 'n', '0'):
return False
else:
raise argparse.ArgumentTypeError('Boolean value expected.')
def main():
""" main """
# Start timestamp
LOGGER.info("Backup starts at: %s", datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
# Extract url from arguments
parser = argparse.ArgumentParser(
description='Fast backup private repos from gitlab server [api v4 - gitlab9.0+]. Configure before use.',
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument(
"--dest", default="./repos_backup", help="where repositories will be backup")
parser.add_argument("--mail", type=str2bool, nargs='?', const=True, default=False,
help="Activate mail notification.")
args = parser.parse_args()
BACKUP_DIR = args.dest
# unpack config
if args.mail is True:
GITLABSERVER, GITLABTOKEN, MAILSERVER, MAILPORT, MAILACCOUNT, MAILPASSWORD, DESTINATIONMAIL = read_config(mail=True)
else:
GITLABSERVER, GITLABTOKEN = read_config()
makedir(BACKUP_DIR)
gitkup(BACKUP_DIR, GITLABSERVER, GITLABTOKEN)
if args.mail:
sendmail(MAILSERVER, MAILPORT, MAILACCOUNT,
MAILPASSWORD, DESTINATIONMAIL, GITLABSERVER)
# End timestamp
LOGGER.info("Backup end at: %s", datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
if __name__ == '__main__':
try:
# initializate logging
setup_logging()
main()
except GitCommandError as err:
LOGGER.error('Git Command Error %s', err)
sys.exit("Please configure ssh identity on your ssh-agent and retry")
except KeyboardInterrupt:
print("interrupted, stopping ...")
sys.exit(42)
# except:
# signal.signal(signal.SIGINT, signal_handler)
# print('Press Ctrl+C to exit')
# print("Backup end at: {}".format(
# datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
# signal.pause()