Merge lp:~user3942934/duplicity/pydrive into lp:~duplicity-team/duplicity/0.7-series

Proposed by westerngateguard
Status: Merged
Merged at revision: 1054
Proposed branch: lp:~user3942934/duplicity/pydrive
Merge into: lp:~duplicity-team/duplicity/0.7-series
Diff against target: 202 lines (+149/-2)
4 files modified
bin/duplicity.1 (+40/-1)
duplicity/backend.py (+1/-1)
duplicity/backends/pydrivebackend.py (+107/-0)
duplicity/commandline.py (+1/-0)
To merge this branch: bzr merge lp:~user3942934/duplicity/pydrive
Reviewer Review Type Date Requested Status
duplicity-team Pending
Review via email: mp+246258@code.launchpad.net

Description of the change

Hello,

Currently duplicity uses gdocs backend for Google Drive backups.
gdocs uses deprecated API and don't allow backups for managed Google accounts.
(see https://bugs.launchpad.net/duplicity/+bug/1315684)

I added pydrive backend that solves both of those problems. I published it also on GitHub: https://github.com/westerngateguard/duplicity-pydrive-backend.

It works fine on my Debian (Wheezy) machine.

To post a comment you must log in.
Revision history for this message
Michael Terry (mterry) wrote :

I think you accidentally deleted some azure documentation from the man page?

Also, you use a keyfile specified by the URL. But all of the other backends tend to use environment variables for that sort of thing. Is there a reason you went with a file?

Revision history for this message
Kenneth Loafman (kenneth-loafman) wrote :

I agree with Michael on this. We should stay with environment variables
until we can go to a backup level configuration file.

On Tue, Jan 13, 2015 at 7:54 AM, Michael Terry <email address hidden>
wrote:

> I think you accidentally deleted some azure documentation from the man
> page?
>
> Also, you use a keyfile specified by the URL. But all of the other
> backends tend to use environment variables for that sort of thing. Is
> there a reason you went with a file?
> --
> https://code.launchpad.net/~user3942934/duplicity/duplicity/+merge/246258
> You are subscribed to branch lp:duplicity.
>

lp:~user3942934/duplicity/pydrive updated
1052. By Yigal Asnis <email address hidden>

Changed passing keyfile via query in URL to passing the key by environment variable. Fixed manpage accordingly. Restored Azure info in manpage that was somehow mistakenly deleted.

Revision history for this message
westerngateguard (user3942934) wrote :

Do I need resubmit the merging proposal or the commit message is enough?

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bin/duplicity.1'
2--- bin/duplicity.1 2015-01-10 19:38:59 +0000
3+++ bin/duplicity.1 2015-01-14 11:12:41 +0000
4@@ -149,7 +149,15 @@
5 .br
6 (also see
7 .BR "A NOTE ON SSL CERTIFICATE VERIFICATION" ).
8-
9+.TP
10+.BR "pydrive backend"
11+.B PyDrive -- a wrapper library of google-api-python-client
12+- https://pypi.python.org/pypi/PyDrive
13+.br
14+(also see
15+.BR "A NOTE ON PYDRIVE BACKEND"
16+) below.
17+.br
18 .SH DESCRIPTION
19 Duplicity incrementally backs up files and folders into
20 tar-format volumes encrypted with GnuPG and places them to a
21@@ -1277,6 +1285,16 @@
22 .B alternatively
23 try lftp+webdav[s]://
24 .RE
25+.PP
26+.BR "pydrive"
27+.PP
28+.RS
29+pydrive://<service account' email address>@developer.gserviceaccount.com/some_dir
30+.PP
31+See also
32+.B "A NOTE ON PYDRIVE BACKEND"
33+below.
34+.RE
35
36 .SH TIME FORMATS
37 duplicity uses time strings in two places. Firstly, many of the files
38@@ -1859,6 +1877,27 @@
39 .B SWIFT_AUTHVERSION
40 is unspecified, it will default to version 1.
41
42+.SH A NOTE ON PYDRIVE BACKEND
43+The pydrive backend requires Python PyDrive package to be installed on the system. See
44+.B REQUIREMENTS
45+above.
46+
47+You need to create a service account in "Google developers console" at https://console.developers.google.com
48+
49+Make sure Drive API is enabled.
50+
51+The email address of the account will be used as part of URL. See
52+.B URL FORMAT
53+above.
54+
55+Download the .p12 key file of the account and convert it to the .pem format:
56+.br
57+openssl pkcs12 -in XXX.p12 -nodes -nocerts > pydriveprivatekey.pem
58+
59+The content of .pem file should be passed to
60+.BR GOOGLE_DRIVE_ACCOUNT_KEY
61+environment variable for authentification.
62+
63 .SH A NOTE ON SYMMETRIC ENCRYPTION AND SIGNING
64 Signing and symmetrically encrypt at the same time with the gpg binary on the
65 command line, as used within duplicity, is a specifically challenging issue.
66
67=== modified file 'duplicity/backend.py'
68--- duplicity/backend.py 2014-12-17 10:35:11 +0000
69+++ duplicity/backend.py 2015-01-14 11:12:41 +0000
70@@ -251,7 +251,7 @@
71 pu = urlparse.urlparse(url_string)
72 except Exception:
73 raise InvalidBackendURL("Syntax error in: %s" % url_string)
74-
75+
76 try:
77 self.scheme = pu.scheme
78 except Exception:
79
80=== added file 'duplicity/backends/pydrivebackend.py'
81--- duplicity/backends/pydrivebackend.py 1970-01-01 00:00:00 +0000
82+++ duplicity/backends/pydrivebackend.py 2015-01-14 11:12:41 +0000
83@@ -0,0 +1,107 @@
84+# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
85+#
86+# Copyright 2015 Yigal Asnis
87+#
88+# This file is free software; you can redistribute it and/or modify it
89+# under the terms of the GNU General Public License as published by the
90+# Free Software Foundation; either version 2 of the License, or (at your
91+# option) any later version.
92+#
93+# It is distributed in the hope that it will be useful, but
94+# WITHOUT ANY WARRANTY; without even the implied warranty of
95+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
96+# General Public License for more details.
97+#
98+# You should have received a copy of the GNU General Public License
99+# along with duplicity; if not, write to the Free Software Foundation,
100+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
101+
102+import string
103+import os
104+
105+import duplicity.backend
106+from duplicity.errors import BackendException
107+
108+class PyDriveBackend(duplicity.backend.Backend):
109+ """Connect to remote store using PyDrive API"""
110+
111+ def __init__(self, parsed_url):
112+ duplicity.backend.Backend.__init__(self, parsed_url)
113+ try:
114+ global pydrive
115+ import httplib2
116+ from apiclient.discovery import build
117+ from oauth2client.client import SignedJwtAssertionCredentials
118+ from pydrive.auth import GoogleAuth
119+ from pydrive.drive import GoogleDrive
120+ except ImportError:
121+ raise BackendException('PyDrive backend requires PyDrive installation'
122+ 'Please read the manpage to fix.')
123+
124+ if 'GOOGLE_DRIVE_ACCOUNT_KEY' not in os.environ:
125+ raise BackendException('GOOGLE_DRIVE_ACCOUNT_KEY environment variable not set. Please read the manpage to fix.')
126+ account_key = os.environ['GOOGLE_DRIVE_ACCOUNT_KEY']
127+
128+ credentials = SignedJwtAssertionCredentials(parsed_url.username + '@' + parsed_url.hostname, account_key, scope='https://www.googleapis.com/auth/drive')
129+ credentials.authorize(httplib2.Http())
130+ gauth = GoogleAuth()
131+ gauth.credentials = credentials
132+ self.drive = GoogleDrive(gauth)
133+
134+ # Dirty way to find root folder id
135+ file_list = self.drive.ListFile({'q': "'Root' in parents"}).GetList()
136+ if file_list:
137+ parent_folder_id = file_list[0]['parents'][0]['id']
138+ else:
139+ file_in_root = self.drive.CreateFile({'title': 'i_am_in_root'})
140+ file_in_root.Upload()
141+ parent_folder_id = file_in_root['parents'][0]['id']
142+
143+ # Fetch destination folder entry and create hierarchy if required.
144+ folder_names = string.split(parsed_url.path, '/')
145+ for folder_name in folder_names:
146+ if not folder_name:
147+ continue
148+ file_list = self.drive.ListFile({'q': "'" + parent_folder_id + "' in parents"}).GetList()
149+ folder = next((item for item in file_list if item['title'] == folder_name and item['mimeType'] == 'application/vnd.google-apps.folder'), None)
150+ if folder is None:
151+ folder = self.drive.CreateFile({'title': folder_name, 'mimeType': "application/vnd.google-apps.folder", 'parents': [{'id': parent_folder_id}]})
152+ folder.Upload()
153+ parent_folder_id = folder['id']
154+ self.folder = parent_folder_id
155+
156+ def FilesList(self):
157+ return self.drive.ListFile({'q': "'" + self.folder + "' in parents"}).GetList()
158+
159+ def id_by_name(self, filename):
160+ try:
161+ return next(item for item in self.FilesList() if item['title'] == filename)['id']
162+ except:
163+ return ''
164+
165+ def _put(self, source_path, remote_filename):
166+ drive_file = self.drive.CreateFile({'title': remote_filename, 'parents': [{"kind": "drive#fileLink", "id": self.folder}]})
167+ drive_file.SetContentFile(source_path.name)
168+ drive_file.Upload()
169+
170+ def _get(self, remote_filename, local_path):
171+ drive_file = self.drive.CreateFile({'id': self.id_by_name(remote_filename)})
172+ drive_file.GetContentFile(local_path.name)
173+
174+ def _list(self):
175+ return [item['title'] for item in self.FilesList()]
176+
177+ def _delete(self, filename):
178+ file_id = self.id_by_name(filename)
179+ drive_file = self.drive.CreateFile({'id': file_id})
180+ drive_file.auth.service.files().delete(fileId=drive_file['id']).execute()
181+
182+ def _query(self, filename):
183+ try:
184+ size = int((item for item in self.FilesList() if item['title'] == filename).next()['fileSize'])
185+ except:
186+ size = -1
187+ return {'size': size}
188+
189+duplicity.backend.register_backend('pydrive', PyDriveBackend)
190+duplicity.backend.uses_netloc.extend([ 'pydrive' ])
191
192=== modified file 'duplicity/commandline.py'
193--- duplicity/commandline.py 2015-01-10 19:38:59 +0000
194+++ duplicity/commandline.py 2015-01-14 11:12:41 +0000
195@@ -856,6 +856,7 @@
196 webdav://%(user)s[:%(password)s]@%(other_host)s/%(some_dir)s
197 webdavs://%(user)s[:%(password)s]@%(other_host)s/%(some_dir)s
198 gdocs://%(user)s[:%(password)s]@%(other_host)s/%(some_dir)s
199+ pydrive://%(user)s@%(other_host)s/%(some_dir)s
200 mega://%(user)s[:%(password)s]@%(other_host)s/%(some_dir)s
201 copy://%(user)s[:%(password)s]@%(other_host)s/%(some_dir)s
202 dpbx:///%(some_dir)s

Subscribers

People subscribed via source and target branches