← Back to team overview

duplicity-team team mailing list archive

[Merge] lp:~mterry/duplicity/drop-u1 into lp:duplicity

 

Michael Terry has proposed merging lp:~mterry/duplicity/drop-u1 into lp:duplicity.

Requested reviews:
  duplicity-team (duplicity-team)

For more details, see:
https://code.launchpad.net/~mterry/duplicity/drop-u1/+merge/216371

Ubuntu One is closing shop.  So no need to support a u1 backend anymore.
-- 
https://code.launchpad.net/~mterry/duplicity/drop-u1/+merge/216371
Your team duplicity-team is requested to review the proposed merge of lp:~mterry/duplicity/drop-u1 into lp:duplicity.
=== modified file 'bin/duplicity.1'
--- bin/duplicity.1	2014-03-09 20:37:24 +0000
+++ bin/duplicity.1	2014-04-17 16:50:40 +0000
@@ -128,15 +128,6 @@
 .B Python keystoneclient module
 - https://github.com/openstack/python-keystoneclient/
 .TP
-.B "Ubuntu One"
-.B httplib2
-(python  HTTP client library)
-- http://code.google.com/p/httplib2/
-.br
-.B oauthlib
-(python OAuth request-signing logic)
-- http://pypi.python.org/pypi/oauthlib
-.TP
 .B "webdav backend"
 .B certificate authority database file
 for ssl certificate verification of HTTPS connections
@@ -1131,15 +1122,6 @@
 .PP
 tahoe://alias/directory
 .PP
-.BI "Ubuntu One"
-.br
-u1://host_is_ignored/volume_path
-.br
-u1+http://volume_path
-.br
-See also
-.BI "A NOTE ON UBUNTU ONE"
-.PP
 webdav[s]://user[:password]@other.host[:port]/some_dir
 .RE
 
@@ -1706,21 +1688,6 @@
 .BI PASSPHRASE
 for symmetric encryption and the passphrase of the signing key are identical.
 
-.SH A NOTE ON UBUNTU ONE
-
-To use Ubuntu One you must have an Ubuntu One OAuth access token. Such
-OAuth tokens have a practically unlimited lifetime; you can have multiple
-active tokens and you can revoke tokens using the Ubuntu One web interface.
-.PP
-Duplicity expects the token in the environment variable
-.B FTP_PASSWORD
-(in the format "consumer_key:consumer_secret:token:token_secret"). If no
-token is present, duplicity asks for your Ubuntu One email address and password
-and requests an access token from the Ubuntu SSO service. The newly
-acquired token is then printed to the console.
-.PP
-See https://one.ubuntu.com/ for more information about Ubuntu One.
-
 .SH A NOTE ON FILENAME PREFIXES
 
 Filename prefixes can be used in conjunction with S3 lifecycle rules to transition 

=== modified file 'duplicity/backend.py'
--- duplicity/backend.py	2014-02-07 19:04:51 +0000
+++ duplicity/backend.py	2014-04-17 16:50:40 +0000
@@ -190,7 +190,6 @@
                                      'hsi',
                                      'rsync',
                                      's3',
-                                     'u1',
                                      'scp', 'ssh', 'sftp',
                                      'webdav', 'webdavs',
                                      'gdocs',

=== removed file 'duplicity/backends/u1backend.py'
--- duplicity/backends/u1backend.py	2013-12-27 06:39:00 +0000
+++ duplicity/backends/u1backend.py	1970-01-01 00:00:00 +0000
@@ -1,277 +0,0 @@
-# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
-#
-# Copyright 2011 Canonical Ltd
-# Authors: Michael Terry <michael.terry@xxxxxxxxxxxxx>
-#          Alexander Zangerl <az@xxxxxxxxxx>
-#
-# This file is part of duplicity.
-#
-# Duplicity is free software; you can redistribute it and/or modify it
-# under the terms of the GNU General Public License as published by the
-# Free Software Foundation; either version 2 of the License, or (at your
-# option) any later version.
-#
-# Duplicity is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with duplicity.  If not, see <http://www.gnu.org/licenses/>.
-
-import duplicity.backend
-from duplicity.errors import BackendException
-from duplicity import log
-from duplicity import globals
-
-from urlparse import urlparse, parse_qsl
-from json import loads, dumps
-# python3 splitted urllib
-try:
-    import urllib
-except ImportError:
-    import urllib.request as urllib
-import getpass
-import os
-import sys
-import time
-
-class OAuthHttpClient(object):
-    """a simple HTTP client with OAuth added on"""
-    def __init__(self):
-        # lazily import non standard python libs
-        global oauth1, Http
-        from oauthlib import oauth1
-        from httplib2 import Http
-
-        self.consumer_key = None
-        self.consumer_secret = None
-        self.token = None
-        self.token_secret = None
-        self.client = Http()
-
-    def set_consumer(self, consumer_key, consumer_secret):
-        self.consumer_key = consumer_key
-        self.consumer_secret = consumer_secret
-
-    def set_token(self, token, token_secret):
-        self.token = token
-        self.token_secret = token_secret
-
-    def _get_oauth_request_header(self, url, method):
-        """Get an oauth request header given the token and the url"""
-        client = oauth1.Client(
-            unicode(self.consumer_key),
-            client_secret=unicode(self.consumer_secret),
-            resource_owner_key=unicode(self.token),
-            resource_owner_secret=unicode(self.token_secret))
-        url, headers, body = client.sign(
-            unicode(url),
-            http_method=unicode(method))
-        return [url, headers]
-
-    def request(self, url, method="GET", body=None, headers={}, ignore=None):
-        url, oauth_header = self._get_oauth_request_header(url, method)
-        headers.update(oauth_header)
-
-        for n in range(1, globals.num_retries+1):
-            log.Info("making %s request to %s (attempt %d)" % (method,url,n))
-            try:
-                resp, content = self.client.request(url, method, headers=headers, body=body)
-            except Exception, e:
-                log.Info("request failed, exception %s" % e);
-                log.Debug("Backtrace of previous error: %s"
-                          % duplicity.util.exception_traceback())
-                if n == globals.num_retries:
-                    log.FatalError("Giving up on request after %d attempts, last exception %s" % (n,e))
-
-                if isinstance(body, file):
-                    body.seek(0) # Go to the beginning of the file for the retry
-
-                time.sleep(30)
-                continue
-
-            log.Info("completed request with status %s %s" % (resp.status,resp.reason))
-            oops_id = resp.get('x-oops-id', None)
-            if oops_id:
-                log.Debug("Server Error: method %s url %s Oops-ID %s" % (method, url, oops_id))
-
-            if resp['content-type'] == 'application/json':
-                content = loads(content)
-
-            # were we successful? status either 2xx or code we're told to ignore
-            numcode=int(resp.status)
-            if (numcode>=200 and numcode<300) or (ignore and numcode in ignore):
-                return resp, content
-
-            ecode = log.ErrorCode.backend_error
-            if numcode == 402:  # Payment Required
-                ecode = log.ErrorCode.backend_no_space
-            elif numcode == 404:
-                ecode = log.ErrorCode.backend_not_found
-
-            if isinstance(body, file):
-                body.seek(0) # Go to the beginning of the file for the retry
-
-            if n < globals.num_retries:
-                time.sleep(30)
-
-        log.FatalError("Giving up on request after %d attempts, last status %d %s" % (n,numcode,resp.reason),
-                       ecode)
-
-
-    def get_and_set_token(self,email, password, hostname):
-        """Acquire an Ubuntu One access token via OAuth with the Ubuntu SSO service.
-        See https://one.ubuntu.com/developer/account_admin/auth/otherplatforms for details.
-        """
-
-        # Request new access token from the Ubuntu SSO service
-        self.client.add_credentials(email,password)
-        resp, content = self.client.request('https://login.ubuntu.com/api/1.0/authentications?'
-                                            +'ws.op=authenticate&token_name=Ubuntu%%20One%%20@%%20%s' % hostname)
-        if resp.status!=200:
-            log.FatalError("Token request failed: Incorrect Ubuntu One credentials",log.ErrorCode.backend_permission_denied)
-            self.client.clear_credentials()
-
-        tokendata=loads(content)
-        self.set_consumer(tokendata['consumer_key'],tokendata['consumer_secret'])
-        self.set_token(tokendata['token'],tokendata['token_secret'])
-
-        # and finally tell Ubuntu One about the token
-        resp, content = self.request('https://one.ubuntu.com/oauth/sso-finished-so-get-tokens/')
-        if resp.status!=200:
-            log.FatalError("Ubuntu One token was not accepted: %s %s" % (resp.status,resp.reason))
-
-        return tokendata
-
-class U1Backend(duplicity.backend.Backend):
-    """
-    Backend for Ubuntu One, through the use of the REST API.
-    See https://one.ubuntu.com/developer/ for REST documentation.
-    """
-    def __init__(self, url):
-        duplicity.backend.Backend.__init__(self, url)
-
-        # u1://dontcare/volname or u1+http:///volname
-        path = self.parsed_url.path.lstrip('/')
-
-        self.api_base = "https://one.ubuntu.com/api/file_storage/v1";
-        self.content_base = "https://files.one.ubuntu.com";
-
-        self.volume_uri = "%s/volumes/~/%s" % (self.api_base, path)
-        self.meta_base = "%s/~/%s/" % (self.api_base, path)
-
-        self.client=OAuthHttpClient();
-
-        if 'FTP_PASSWORD' not in os.environ:
-            sys.stderr.write("No Ubuntu One token found in $FTP_PASSWORD, requesting a new one\n")
-            email=raw_input('Enter Ubuntu One account email: ')
-            password=getpass.getpass("Enter Ubuntu One password: ")
-            hostname=os.uname()[1]
-
-            tokendata = self.client.get_and_set_token(email, password, hostname)
-            tokenstring = "%s:%s:%s:%s" % (tokendata['consumer_key'], tokendata['consumer_secret'],
-                                tokendata['token'], tokendata['token_secret'])
-            sys.stderr.write("\nPlease record your new Ubuntu One access token for future use with duplicity:\n"
-                             + "FTP_PASSWORD=%s\n\n" % tokenstring)
-            os.environ['FTP_PASSWORD'] = tokenstring
-
-        (consumer,consumer_secret,token,token_secret) = os.environ['FTP_PASSWORD'].split(':')
-        self.client.set_consumer(consumer, consumer_secret)
-        self.client.set_token(token, token_secret)
-
-        resp, content = self.client.request(self.api_base,ignore=[400,401,403])
-        if resp['status']!='200':
-           log.FatalError("Access failed: Ubuntu One credentials incorrect",
-                           log.ErrorCode.user_error)
-
-        # Create volume, but check existence first
-        resp, content = self.client.request(self.volume_uri,ignore=[404])
-        if resp['status']=='404':
-            resp, content = self.client.request(self.volume_uri,"PUT")
-
-    def quote(self, url):
-        return urllib.quote(url, safe="/~").replace(" ","%20")
-
-    def put(self, source_path, remote_filename = None):
-        """Copy file to remote"""
-        if not remote_filename:
-            remote_filename = source_path.get_filename()
-        remote_full = self.meta_base + self.quote(remote_filename)
-        # check if it exists already, returns existing content_path
-        resp, content = self.client.request(remote_full,ignore=[404])
-        if resp['status']=='404':
-            # put with path returns new content_path
-            resp, content = self.client.request(remote_full,
-                                                method="PUT",
-                                                headers = { 'content-type': 'application/json' },
-                                                body=dumps({"kind":"file"}))
-        elif resp['status']!='200':
-            raise BackendException("access to %s failed, code %s" % (remote_filename, resp['status']))
-
-        assert(content['content_path'] is not None)
-        # content_path allows put of the actual material
-        remote_full = self.content_base + self.quote(content['content_path'])
-        log.Info("uploading file %s to location %s" % (remote_filename, remote_full))
-
-        size = os.path.getsize(source_path.name)
-        fh=open(source_path.name,'rb')
-
-        content_type = 'application/octet-stream'
-        headers = {"Content-Length": str(size),
-                   "Content-Type": content_type}
-        resp, content = self.client.request(remote_full,
-                                            method="PUT",
-                                            body=fh,
-                                            headers=headers)
-        fh.close()
-
-    def get(self, filename, local_path):
-        """Get file and put in local_path (Path object)"""
-
-        # get with path returns content_path
-        remote_full = self.meta_base + self.quote(filename)
-        resp, content = self.client.request(remote_full)
-
-        assert(content['content_path'] is not None)
-        # now we have content_path to access the actual material
-        remote_full = self.content_base + self.quote(content['content_path'])
-        log.Info("retrieving file %s from location %s" % (filename, remote_full))
-        resp, content = self.client.request(remote_full)
-
-        f = open(local_path.name, 'wb')
-        f.write(content)
-        f.close()
-        local_path.setdata()
-
-    def _list(self):
-        """List files in that directory"""
-        remote_full = self.meta_base + "?include_children=true"
-        resp, content = self.client.request(remote_full)
-
-        filelist = []
-        if 'children' in content:
-            for child in content['children']:
-                path = urllib.unquote(child['path'].lstrip('/'))
-                filelist += [path.encode('utf-8')]
-        return filelist
-
-    def delete(self, filename_list):
-        """Delete all files in filename list"""
-        import types
-        assert type(filename_list) is not types.StringType
-
-        for filename in filename_list:
-            remote_full = self.meta_base + self.quote(filename)
-            resp, content = self.client.request(remote_full,method="DELETE")
-
-    def _query_file_info(self, filename):
-        """Query attributes on filename"""
-        remote_full = self.meta_base + self.quote(filename)
-        resp, content = self.client.request(remote_full)
-
-        size = content['size']
-        return {'size': size}
-
-duplicity.backend.register_backend("u1", U1Backend)
-duplicity.backend.register_backend("u1+http", U1Backend)


Follow ups