capun 0.0.30 → 0.0.31
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/capun/setup.rb +29 -2
- data/lib/capun/version.rb +1 -1
- data/lib/generators/capun/stage_generator.rb +16 -1
- data/lib/generators/capun/templates/backup.sh.erb +35 -0
- data/lib/generators/capun/templates/drivesink.py +336 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c9cad418ca5b3b3cbbcab859022a574e1f549892
|
4
|
+
data.tar.gz: 92424bad80df1c7957fc4085fd196db137bd5bae
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ce502e94d0245afc0d2de1a6e41223049b13a67bf6c53d50849cc8dacd5523363eb221dd8c34d8272c5af12ade0ad416dff745cdd825636b9a5ad945bc9e27cc
|
7
|
+
data.tar.gz: 25e78ac4a8917753230dedd420dabae1b96e7682a36c9e283d7a17f88b3a2d597e45c00483218a9962ecafc73eb3fb7d4b29c593f2c7d69511efd05fe7786d81
|
data/lib/capun/setup.rb
CHANGED
@@ -33,6 +33,10 @@ set :std_uploads, [
|
|
33
33
|
# unicorn.config.rb
|
34
34
|
{what: "config/deploy/unicorn.config.rb.erb", where: '#{shared_path}/config/unicorn.config.rb', upload: true, overwrite: true},
|
35
35
|
# database.yml
|
36
|
+
{what: "config/deploy/backup.sh.erb", where: '#{shared_path}/backup.sh', upload: true, overwrite: true},
|
37
|
+
# backup.sh.erb
|
38
|
+
{what: "config/deploy/drivesink.py", where: '#{shared_path}/drivesink.py', upload: true, overwrite: true},
|
39
|
+
# backup.sh.erb
|
36
40
|
{what: "config/deploy/database.yml.erb", where: '#{shared_path}/config/database.yml', upload: true, overwrite: true},
|
37
41
|
# jenkins' config.xml
|
38
42
|
{what: "config/deploy/jenkins.config.xml.erb", where: '/var/lib/jenkins/jobs/#{fetch(:application)}/config.xml', upload: -> { !!fetch(:addJenkins) }, overwrite: false},
|
@@ -49,6 +53,16 @@ set :std_symlinks, [
|
|
49
53
|
{what: "application.yml", where: '#{release_path}/config/application.yml'},
|
50
54
|
{what: "newrelic.yml", where: '#{release_path}/config/newrelic.yml'}
|
51
55
|
]
|
56
|
+
namespace :backup do
|
57
|
+
desc 'Backup application'
|
58
|
+
task :exec do
|
59
|
+
on roles(:app) do
|
60
|
+
if fetch(:useBackups)
|
61
|
+
execute "sudo /home/#{fetch(:user)}/apps/#{fetch(:application)}/shared/backup.sh"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
52
66
|
|
53
67
|
namespace :predeploy do
|
54
68
|
namespace :install do
|
@@ -67,10 +81,10 @@ before 'deploy', 'predeploy:install:rvm_ruby'
|
|
67
81
|
|
68
82
|
namespace :deploy do
|
69
83
|
|
70
|
-
desc 'Kills
|
84
|
+
desc 'Kills unicorn processes'
|
71
85
|
task :kill_me do
|
72
86
|
on roles(:app) do
|
73
|
-
execute "
|
87
|
+
execute "cd /home/#{fetch(:user)}/apps/#{fetch(:application)}/shared/tmp/pids; for line in $(ls | grep unicorn); do kill -15 $(sudo cat $line) || true ; done;"
|
74
88
|
end
|
75
89
|
end
|
76
90
|
before :deploy, 'deploy:kill_me'
|
@@ -175,6 +189,18 @@ namespace :deploy do
|
|
175
189
|
end
|
176
190
|
end
|
177
191
|
|
192
|
+
desc 'Update cron backup task'
|
193
|
+
task :update_cron do
|
194
|
+
if fetch(:useBackups)
|
195
|
+
on roles(:app) do
|
196
|
+
execute :chmod, "+x #{shared_path}/backup.sh"
|
197
|
+
info "making backup.sh executable"
|
198
|
+
execute :sudo, :ln, "-nfs", "#{shared_path}/backup.sh /etc/cron.#{fetch(:backupTime)}/backup-#{fetch(:application).gsub(/\./, '-')}"
|
199
|
+
info "Create symbolic link for backup"
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
178
204
|
end
|
179
205
|
|
180
206
|
before "deploy:updating", "deploy:make_dirs"
|
@@ -185,4 +211,5 @@ after "deploy:publishing", "deploy:set_up_jenkins"
|
|
185
211
|
after "deploy:publishing", "deploy:prepare_logrotate"
|
186
212
|
after "deploy:publishing", "deploy:restart_nginx"
|
187
213
|
after "deploy:publishing", "deploy:restart_logstash"
|
214
|
+
after "deploy:publishing", "deploy:update_cron"
|
188
215
|
after "deploy:publishing", "unicorn:legacy_restart"
|
data/lib/capun/version.rb
CHANGED
@@ -22,12 +22,14 @@ module Capun
|
|
22
22
|
end
|
23
23
|
@addELK = ask("Would you like to add ELK-compatible logging? [Y/n]").capitalize == 'Y'
|
24
24
|
@addlogrotate = ask("Would you like to add logrotate configuration to stage? [Y/n]").capitalize == 'Y'
|
25
|
+
@useBackups = ask("Would you like to add amazon backup system? [Y/n]").capitalize == 'Y'
|
25
26
|
end
|
26
27
|
|
27
28
|
def add_stage
|
28
29
|
template "stage.rb.erb", "config/deploy/#{singular_name}.rb"
|
29
30
|
end
|
30
31
|
|
32
|
+
|
31
33
|
def copy_env_file
|
32
34
|
copy_file Rails.root.join('config', 'environments', 'production.rb'), "config/environments/#{singular_name}.rb"
|
33
35
|
end
|
@@ -85,6 +87,19 @@ module Capun
|
|
85
87
|
end
|
86
88
|
end
|
87
89
|
|
90
|
+
def useBackups
|
91
|
+
if @useBackups
|
92
|
+
append_to_file "config/deploy/#{singular_name}.rb", "#backup_system\n" +
|
93
|
+
"set :useBackups, true\n" +
|
94
|
+
"set :backupTime, \"daily\" # available hourly, daily, monthly, weekly\n" +
|
95
|
+
"set :backupFolders, %w{public/system} #recursive\n" +
|
96
|
+
"#set :slack_hook, [hook]\n" +
|
97
|
+
"#set :slack_channel, [channel] #must be specified"
|
98
|
+
copy_file "backup.sh.erb", "config/deploy/backup.sh.erb"
|
99
|
+
copy_file "drivesink.py", "config/deploy/drivesink.py"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
88
103
|
def add_jenkins
|
89
104
|
if @addJenkins
|
90
105
|
copy_file "jenkins.config.xml.erb", "config/deploy/jenkins.config.xml.erb"
|
@@ -105,4 +120,4 @@ module Capun
|
|
105
120
|
end
|
106
121
|
end
|
107
122
|
end
|
108
|
-
end
|
123
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
cd /home/<%= fetch(:user) %>/apps/<%= fetch(:application) %>/shared
|
4
|
+
curdate=$(date +"%m-%d-%y")
|
5
|
+
mkdir -p "backuping/version-$curdate"
|
6
|
+
|
7
|
+
zip -u -0 -r -s 1000 "backuping/version-$curdate/assets.zip" <% fetch(:backupFolders).each do |folder|%> <%= folder + ' ' %> <% end %>
|
8
|
+
|
9
|
+
adapter=$(cat config/database.yml | grep adapter | sed s/adapter://g | xargs)
|
10
|
+
if [ "$adapter" = "sqlite3" ]; then
|
11
|
+
DATA_BASE_TYPE="SQLite3"
|
12
|
+
cp "<%=fetch(:stage)%>.sqlite3" "backuping/version-$curdate/database.sqlite3"
|
13
|
+
fi
|
14
|
+
if [ "$adapter" = "mysql2" ]; then
|
15
|
+
DATA_BASE_TYPE="MySQL"
|
16
|
+
username=$(cat config/database.yml | grep username | sed s/username://g | xargs)
|
17
|
+
password=$(cat config/database.yml | grep password | sed s/password://g | xargs)
|
18
|
+
database=$(cat config/database.yml | grep database | sed s/database://g | xargs)
|
19
|
+
mysqldump --user="$username" --password="$password" $database > "backuping/version-$curdate/database.dump"
|
20
|
+
fi
|
21
|
+
|
22
|
+
python drivesink.py upload "backuping" <%= fetch(:backupDestinationFolder) || "backups/firstdedic-server/" + fetch(:application) %>
|
23
|
+
|
24
|
+
if [ $? -eq 0 ]; then
|
25
|
+
SIZE=$(du -sh backuping | awk '{print $1}')
|
26
|
+
curl -X POST -H 'Content-type: application/json' \
|
27
|
+
--data "{'attachments': [{'mrkdwn_in': ['text'],'text': 'Успешо создан бэкап для *<%= fetch(:application) %>*\n<% fetch(:backupFolders).each do |folder|%><%= "-" +folder + '\n' %><% end %>Тип базы данных: *$DATA_BASE_TYPE*\nРазмер *$SIZE*','color': '#3AA3E3', 'title': 'Amazon Backup System'}]<%=", \'channel\': \'#{fetch(:slack_channel)}\' " if !fetch(:slack_channel).nil? %>}" \
|
28
|
+
<%= fetch(:slack_hook) %>
|
29
|
+
else
|
30
|
+
curl -X POST -H 'Content-type: application/json' \
|
31
|
+
--data "{'attachments': [{'mrkdwn_in': ['text'],'text': 'Произошла ошибка при создании бэкапа для *<%= fetch(:application) %>','color': '#FF0000', 'title': 'Amazon Backup System'}]<%=", \'channel\': \'#{fetch(:slack_channel)}\' " if !fetch(:slack_channel).nil? %>}" \
|
32
|
+
<%= fetch(:slack_hook) %>
|
33
|
+
fi
|
34
|
+
|
35
|
+
rm -rf backuping
|
@@ -0,0 +1,336 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
#--*-- coding: utf-8 --*--
|
3
|
+
|
4
|
+
import argparse
|
5
|
+
import hashlib
|
6
|
+
import json
|
7
|
+
import logging
|
8
|
+
import os
|
9
|
+
import requests
|
10
|
+
import requests_toolbelt
|
11
|
+
import sys
|
12
|
+
import uuid
|
13
|
+
import mimetypes
|
14
|
+
import inspect
|
15
|
+
|
16
|
+
class CloudNode(object):
|
17
|
+
def __init__(self, node):
|
18
|
+
self.node = node
|
19
|
+
self._children_fetched = False
|
20
|
+
|
21
|
+
def children(self):
|
22
|
+
if not self._children_fetched:
|
23
|
+
nodes = DriveSink.instance().request_metadata(
|
24
|
+
"%%snodes/%s/children" % self.node["id"])
|
25
|
+
self._children = {n["name"]: CloudNode(n) for n in nodes["data"]}
|
26
|
+
self._children_fetched = True
|
27
|
+
return self._children
|
28
|
+
|
29
|
+
def child(self, name, create=False):
|
30
|
+
node = self.children().get(name)
|
31
|
+
if not node and create:
|
32
|
+
node = self._make_child_folder(name)
|
33
|
+
return node
|
34
|
+
|
35
|
+
def upload_child_file(self, name, local_path, existing_node=None):
|
36
|
+
logging.info("Uploading %s to %s", local_path, self.node["name"])
|
37
|
+
mime_type = _get_mimetype(name)
|
38
|
+
m = requests_toolbelt.MultipartEncoder([
|
39
|
+
("metadata", json.dumps({
|
40
|
+
"name": name, # Здесь можно задать имя файлу, которое будет у него в облаке
|
41
|
+
"kind": "FILE",
|
42
|
+
"parents": [self.node["id"]]
|
43
|
+
})),
|
44
|
+
("content", (name, open(local_path, "rb"), mime_type))])
|
45
|
+
if existing_node:
|
46
|
+
"""
|
47
|
+
# TODO: this is under-documented and currently 500s on Amazon's side
|
48
|
+
node = CloudNode(DriveSink.instance().request_content(
|
49
|
+
"%%snodes/%s/content" % existing_node.node["id"],
|
50
|
+
method="put", data=m, headers={"Content-Type": m.content_type}))
|
51
|
+
"""
|
52
|
+
old_info = DriveSink.instance().request_metadata(
|
53
|
+
"%%s/trash/%s" % existing_node.node["id"], method="put")
|
54
|
+
node = CloudNode(DriveSink.instance().request_content(
|
55
|
+
"%snodes", method="post", data=m,
|
56
|
+
headers={"Content-Type": m.content_type}))
|
57
|
+
self._children[name] = node
|
58
|
+
|
59
|
+
def download_file(self, local_path):
|
60
|
+
logging.info("Downloading %s into %s", self.node["name"], local_path)
|
61
|
+
req = DriveSink.instance().request_content(
|
62
|
+
"%%snodes/%s/content" % self.node["id"], method="get", stream=True,
|
63
|
+
decode=False)
|
64
|
+
if req.status_code != 200:
|
65
|
+
logging.error("Unable to download file: %r", req.text)
|
66
|
+
sys.exit(1)
|
67
|
+
with open(local_path, "wb") as f:
|
68
|
+
for chunk in req.iter_content():
|
69
|
+
f.write(chunk)
|
70
|
+
|
71
|
+
def differs(self, local_path):
|
72
|
+
|
73
|
+
# Этот закомментированный блок проверяет загружаемые файлы на дубликатность... как-то так
|
74
|
+
#
|
75
|
+
#return (not os.path.exists(local_path) or
|
76
|
+
# self.node["contentProperties"]["size"] !=
|
77
|
+
# os.path.getsize(local_path) or
|
78
|
+
# self.node["contentProperties"]["md5"] !=
|
79
|
+
# self._md5sum(local_path))
|
80
|
+
|
81
|
+
return True
|
82
|
+
|
83
|
+
|
84
|
+
def _md5sum(self, filename, blocksize=65536):
|
85
|
+
md5 = hashlib.md5()
|
86
|
+
with open(filename, "rb") as f:
|
87
|
+
for block in iter(lambda: f.read(blocksize), ""):
|
88
|
+
md5.update(block)
|
89
|
+
return md5.hexdigest()
|
90
|
+
|
91
|
+
def _make_child_folder(self, name):
|
92
|
+
logging.info(
|
93
|
+
"Creating remote folder %s in %s", name, self.node["name"])
|
94
|
+
node = CloudNode(
|
95
|
+
DriveSink.instance().request_metadata("%snodes", {
|
96
|
+
"kind": "FOLDER",
|
97
|
+
"name": name,
|
98
|
+
"parents": [self.node["id"]]}))
|
99
|
+
self._children[name] = node
|
100
|
+
return node
|
101
|
+
|
102
|
+
|
103
|
+
class DriveSink(object):
|
104
|
+
def __init__(self, args):
|
105
|
+
if not args:
|
106
|
+
logging.error("Never initialized")
|
107
|
+
sys.exit(1)
|
108
|
+
self.args = args
|
109
|
+
self.config = None
|
110
|
+
|
111
|
+
@classmethod
|
112
|
+
def instance(cls, args=None):
|
113
|
+
if not hasattr(cls, "_instance"):
|
114
|
+
cls._instance = cls(args)
|
115
|
+
return cls._instance
|
116
|
+
|
117
|
+
def upload(self, source, destination):
|
118
|
+
remote_node = self.node_at_path(
|
119
|
+
self.get_root(), destination, create_missing=True)
|
120
|
+
for dirpath, dirnames, filenames in os.walk(source):
|
121
|
+
relative = dirpath[len(source):]
|
122
|
+
current_dir = self.node_at_path(
|
123
|
+
remote_node, relative, create_missing=True)
|
124
|
+
if not current_dir:
|
125
|
+
logging.error("Could not create missing node")
|
126
|
+
sys.exit(1)
|
127
|
+
for dirname in dirnames:
|
128
|
+
logging.info(dirnames)
|
129
|
+
current_dir.child(dirname, create=True)
|
130
|
+
for filename in filenames:
|
131
|
+
local_path = os.path.join(dirpath, filename)
|
132
|
+
node = current_dir.child(filename)
|
133
|
+
if (not node or node.differs(
|
134
|
+
local_path)) and self.filter_file(filename):
|
135
|
+
current_dir.upload_child_file(filename, local_path, node)
|
136
|
+
|
137
|
+
def upload_file(self, source, destination):
|
138
|
+
'''
|
139
|
+
funkcija poluchaet polnyj put' k fajlu, soderzhashhij imja i rasshirenie fajla (primer: /home/ubuntu/upl/index.html)
|
140
|
+
i razbivaet ego na put' k fajlu (dirpath=/home/ubuntu/upl/) i nazvanie fajla (filename=index.html)
|
141
|
+
|
142
|
+
zatem dejstvuet po analogii s funkciej "upload"
|
143
|
+
'''
|
144
|
+
remote_node = self.node_at_path(
|
145
|
+
self.get_root(), destination, create_missing=True)
|
146
|
+
|
147
|
+
dirpath = os.path.split(source)[0]
|
148
|
+
filename = os.path.split(source)[1]
|
149
|
+
|
150
|
+
relative = dirpath[len(dirpath):]
|
151
|
+
current_dir = self.node_at_path(
|
152
|
+
remote_node, relative, create_missing=True)
|
153
|
+
|
154
|
+
local_path = os.path.join(dirpath, filename)
|
155
|
+
node = current_dir.child(filename)
|
156
|
+
current_dir.upload_child_file(filename, local_path, node)
|
157
|
+
|
158
|
+
def download(self, source, destination):
|
159
|
+
to_download = [(self.node_at_path(self.get_root(), source),
|
160
|
+
self.join_path(destination, create_missing=True))]
|
161
|
+
while len(to_download):
|
162
|
+
node, path = to_download.pop(0)
|
163
|
+
for name, child in node.children().iteritems():
|
164
|
+
if child.node["kind"] == "FOLDER":
|
165
|
+
to_download.append((child, self.join_path(
|
166
|
+
child.node["name"], path, create_missing=True)))
|
167
|
+
elif child.node["kind"] == "FILE":
|
168
|
+
local_path = os.path.join(path, child.node["name"])
|
169
|
+
if child.differs(local_path):
|
170
|
+
child.download_file(local_path)
|
171
|
+
|
172
|
+
def filter_file(self, filename):
|
173
|
+
_, extension = os.path.splitext(filename)
|
174
|
+
extension = extension.lstrip(".").lower()
|
175
|
+
|
176
|
+
# Этот закомментированный блок проверяет расширения файлов и если они есть в Allowed, то он их загружает в облако.
|
177
|
+
# Если нужна фильтрация файлов, то раскомментируйте этот блок, а строку "return extension" - закомментируйте
|
178
|
+
#
|
179
|
+
# allowed = self.args.extensions
|
180
|
+
# if not allowed:
|
181
|
+
# # Not all tested to be free
|
182
|
+
# allowed = (
|
183
|
+
# "apng,arw,bmp,cr2,crw,dng,emf,gif,jfif,jpe,jpeg,jpg,mef,nef,"
|
184
|
+
# "orf,pcx,png,psd,raf,ras,srw,swf,tga,tif,tiff,wmf")
|
185
|
+
# return extension in allowed.split(",")
|
186
|
+
|
187
|
+
return extension
|
188
|
+
|
189
|
+
def get_root(self):
|
190
|
+
nodes = self.request_metadata("%snodes?filters=isRoot:true")
|
191
|
+
if nodes["count"] != 1:
|
192
|
+
logging.error("Could not find root")
|
193
|
+
sys.exit(1)
|
194
|
+
return CloudNode(nodes["data"][0])
|
195
|
+
|
196
|
+
def node_at_path(self, root, path, create_missing=False):
|
197
|
+
parts = filter(None, path.split("/"))
|
198
|
+
node = root
|
199
|
+
while len(parts):
|
200
|
+
node = node.child(parts.pop(0), create=create_missing)
|
201
|
+
if not node:
|
202
|
+
return None
|
203
|
+
return node
|
204
|
+
|
205
|
+
def join_path(self, destination, root="/", create_missing=True):
|
206
|
+
directory = os.path.join(root, destination)
|
207
|
+
if not os.path.exists(directory):
|
208
|
+
if create_missing:
|
209
|
+
os.makedirs(directory)
|
210
|
+
else:
|
211
|
+
return None
|
212
|
+
if not os.path.isdir(directory):
|
213
|
+
logging.error("%s is not a directory", directory)
|
214
|
+
sys.exit(1)
|
215
|
+
return directory
|
216
|
+
|
217
|
+
def _config_file(self):
|
218
|
+
config_filename = self.args.config or os.environ.get(
|
219
|
+
"DRIVESINK", None)
|
220
|
+
if not config_filename:
|
221
|
+
config_filename = os.path.join(
|
222
|
+
os.path.expanduser("~"), ".drivesink")
|
223
|
+
return config_filename
|
224
|
+
|
225
|
+
def _config(self):
|
226
|
+
if not self.config:
|
227
|
+
config_filename = self._config_file()
|
228
|
+
try:
|
229
|
+
self.config = json.loads(open(config_filename, "r").read())
|
230
|
+
except:
|
231
|
+
print "%s/config to get your tokens" % self.args.drivesink
|
232
|
+
sys.exit(1)
|
233
|
+
return self.config
|
234
|
+
|
235
|
+
def request_metadata(self, path, json_data=None, **kwargs):
|
236
|
+
args = {}
|
237
|
+
if json_data:
|
238
|
+
args["method"] = "post"
|
239
|
+
args["data"] = json.dumps(json_data)
|
240
|
+
else:
|
241
|
+
args["method"] = "get"
|
242
|
+
|
243
|
+
args.update(kwargs)
|
244
|
+
|
245
|
+
return self._request(
|
246
|
+
path % self._config()["metadataUrl"], **args)
|
247
|
+
|
248
|
+
def request_content(self, path, **kwargs):
|
249
|
+
# приписка '?suppress=deduplication' дает возможность добавлять одинаковые файлы в разные папки.
|
250
|
+
# если имена файлов одинаковы, то они замещаются при этом хэш-сумма не учитывается.
|
251
|
+
return self._request(
|
252
|
+
(path + '?suppress=deduplication') % self._config()["contentUrl"], **kwargs)
|
253
|
+
|
254
|
+
def _request(self, url, refresh=True, decode=True, **kwargs):
|
255
|
+
headers = {
|
256
|
+
"Authorization": "Bearer %s" % self._config()["access_token"],
|
257
|
+
}
|
258
|
+
headers.update(kwargs.pop("headers", {}))
|
259
|
+
req = requests.request(url=url, headers=headers, **kwargs)
|
260
|
+
|
261
|
+
if req.status_code == 401 and refresh:
|
262
|
+
# Have to proxy to get the client id and secret
|
263
|
+
req = requests.post("%s/refresh" % self.args.drivesink, data={
|
264
|
+
"refresh_token": self._config()["refresh_token"],
|
265
|
+
})
|
266
|
+
if req.status_code != 200:
|
267
|
+
try:
|
268
|
+
response = req.json()
|
269
|
+
logging.error("Got Amazon code %s: %s",
|
270
|
+
response["code"], response["message"])
|
271
|
+
sys.exit(1)
|
272
|
+
except Exception:
|
273
|
+
pass
|
274
|
+
req.raise_for_status()
|
275
|
+
try:
|
276
|
+
new_config = req.json()
|
277
|
+
except:
|
278
|
+
logging.error("Could not refresh: %r", req.text)
|
279
|
+
raise
|
280
|
+
self.config.update(new_config)
|
281
|
+
with open(self._config_file(), "w") as f:
|
282
|
+
f.write(json.dumps(self.config, sort_keys=True, indent=4))
|
283
|
+
return self._request(url, refresh=False, decode=decode, **kwargs)
|
284
|
+
if req.status_code != 200:
|
285
|
+
try:
|
286
|
+
response = req.json()
|
287
|
+
logging.error("Got Amazon code %s: %s",
|
288
|
+
response["code"], response["message"])
|
289
|
+
sys.exit(1)
|
290
|
+
except Exception:
|
291
|
+
pass
|
292
|
+
# Здесь можно организовать уведомление об успешной загрузке - код ответа 200
|
293
|
+
if req.status_code == 200:
|
294
|
+
logging.info('Отправка данных: <' + inspect.stack()[1][3] + '> Код ответа: 200. ОК.')
|
295
|
+
|
296
|
+
req.raise_for_status()
|
297
|
+
if decode:
|
298
|
+
return req.json()
|
299
|
+
return req
|
300
|
+
|
301
|
+
def _get_mimetype(file_name = ''):
|
302
|
+
mt = mimetypes.guess_type(file_name)[0]
|
303
|
+
return mt if mt else 'application/octet-stream'
|
304
|
+
|
305
|
+
def main():
|
306
|
+
parser = argparse.ArgumentParser(
|
307
|
+
description="Amazon Cloud Drive synchronization tool")
|
308
|
+
parser.add_argument("command", choices=["upload", "download"],
|
309
|
+
help="Commands: 'upload' or 'download'")
|
310
|
+
parser.add_argument("source", help="The source directory")
|
311
|
+
parser.add_argument("destination", help="The destination directory")
|
312
|
+
parser.add_argument("-e", "--extensions",
|
313
|
+
help="File extensions to upload, images by default")
|
314
|
+
parser.add_argument("-c", "--config", help="The config file")
|
315
|
+
parser.add_argument("-d", "--drivesink", help="Drivesink URL",
|
316
|
+
default="https://drivesink.appspot.com")
|
317
|
+
args = parser.parse_args()
|
318
|
+
|
319
|
+
drivesink = DriveSink.instance(args)
|
320
|
+
|
321
|
+
if args.command == "upload":
|
322
|
+
if(not os.path.isfile(args.source)):
|
323
|
+
drivesink.upload(args.source, args.destination)
|
324
|
+
else:
|
325
|
+
drivesink.upload_file(args.source, args.destination)
|
326
|
+
elif args.command == "download":
|
327
|
+
drivesink.download(args.source, args.destination)
|
328
|
+
|
329
|
+
logging.basicConfig(
|
330
|
+
format = "%(levelname) -10s %(module)s:%(lineno)s %(funcName)s %(message)s",
|
331
|
+
level = logging.DEBUG
|
332
|
+
)
|
333
|
+
logging.getLogger("requests").setLevel(logging.WARNING)
|
334
|
+
|
335
|
+
if __name__ == "__main__":
|
336
|
+
main()
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: capun
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.31
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ivan Zamylin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-08-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -129,9 +129,11 @@ files:
|
|
129
129
|
- lib/generators/capun/stage_generator.rb
|
130
130
|
- lib/generators/capun/templates/Capfile
|
131
131
|
- lib/generators/capun/templates/append_info.excerpt
|
132
|
+
- lib/generators/capun/templates/backup.sh.erb
|
132
133
|
- lib/generators/capun/templates/basic_authenticatable.rb.erb
|
133
134
|
- lib/generators/capun/templates/database.yml.erb
|
134
135
|
- lib/generators/capun/templates/deploy.rb.erb
|
136
|
+
- lib/generators/capun/templates/drivesink.py
|
135
137
|
- lib/generators/capun/templates/jenkins.config.xml.erb
|
136
138
|
- lib/generators/capun/templates/lograge_env_config.excerpt
|
137
139
|
- lib/generators/capun/templates/lograge_initializer.rb
|