Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / pithos.py @ cec2dfcd

History | View | Annotate | Download (9.5 kB)

1
# Copyright 2011-2013 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.command
33

    
34
from io import StringIO
35
from pydoc import pager
36

    
37
from kamaki.cli import command
38
from kamaki.cli.command_tree import CommandTree
39
from kamaki.cli.commands import (
40
    _command_init, errors, addLogSettings, DontRaiseKeyError, _optional_json,
41
    _name_filter, _optional_output_cmd)
42
from kamaki.clients.pithos import PithosClient
43
from kamaki.cli.errors import (
44
    CLIBaseUrlError)
45
from kamaki.cli.argument import (
46
    FlagArgument, IntArgument, ValueArgument, DateArgument)
47
from kamaki.cli.utils import (format_size, bold)
48

    
49
file_cmds = CommandTree('file', 'Pithos+/Storage object level API commands')
50
container_cmds = CommandTree(
51
    'container', 'Pithos+/Storage container level API commands')
52
sharers_commands = CommandTree('sharers', 'Pithos+/Storage sharers')
53
_commands = [file_cmds, container_cmds, sharers_commands]
54

    
55

    
56
class _pithos_init(_command_init):
57
    """Initilize a pithos+ client
58
    There is always a default account (current user uuid)
59
    There is always a default container (pithos)
60
    """
61

    
62
    @DontRaiseKeyError
63
    def _custom_container(self):
64
        return self.config.get_cloud(self.cloud, 'pithos_container')
65

    
66
    @DontRaiseKeyError
67
    def _custom_uuid(self):
68
        return self.config.get_cloud(self.cloud, 'pithos_uuid')
69

    
70
    def _set_account(self):
71
        self.account = self._custom_uuid()
72
        if self.account:
73
            return
74
        astakos = getattr(self, 'auth_base', None)
75
        if astakos:
76
            self.account = astakos.user_term('id', self.token)
77
        else:
78
            raise CLIBaseUrlError(service='astakos')
79

    
80
    @errors.generic.all
81
    @addLogSettings
82
    def _run(self):
83
        cloud = getattr(self, 'cloud', None)
84
        if cloud:
85
            self.base_url = self._custom_url('pithos')
86
        else:
87
            self.cloud = 'default'
88
        self.token = self._custom_token('pithos')
89
        self.container = self._custom_container() or 'pithos'
90

    
91
        astakos = getattr(self, 'auth_base', None)
92
        if astakos:
93
            self.token = self.token or astakos.token
94
            if not self.base_url:
95
                pithos_endpoints = astakos.get_service_endpoints(
96
                    self._custom_type('pithos') or 'object-store',
97
                    self._custom_version('pithos') or '')
98
                self.base_url = pithos_endpoints['publicURL']
99
        else:
100
            raise CLIBaseUrlError(service='astakos')
101

    
102
        self._set_account()
103
        self.client = PithosClient(
104
            self.base_url, self.token, self.account, self.container)
105

    
106
    def main(self):
107
        self._run()
108

    
109

    
110
class _pithos_account(_pithos_init):
111
    """Setup account"""
112

    
113
    def __init__(self, *args, **kwargs):
114
        super(_pithos_account, self).__init__(*args, **kwargs)
115
        self['account'] = ValueArgument(
116
            'Use (a different) user uuid', ('-A', '--account'))
117

    
118
    def _run(self):
119
        super(_pithos_account, self)._run()
120
        self.client.account = self['account'] or getattr(
121
            self, 'account', getattr(self.client, 'account', None))
122

    
123

    
124
class _pithos_container(_pithos_account):
125
    """Setup container"""
126

    
127
    def __init__(self, *args, **kwargs):
128
        super(_pithos_container, self).__init__(*args, **kwargs)
129
        self['container'] = ValueArgument(
130
            'Use this container (default: pithos)', ('-C', '--container'))
131

    
132
    def _resolve_pithos_url(self, url):
133
        """Match urls of one of the following formats:
134
        pithos://ACCOUNT/CONTAINER/OBJECT_PATH
135
        /CONTAINER/OBJECT_PATH
136
        Anything resolved, is set as self.<account|container|path>
137
        """
138
        account, container, path, prefix = '', '', url, 'pithos://'
139
        if url.startswith(prefix):
140
            self.account, sep, url = url[len(prefix):].partition('/')
141
            url = '/%s' % url
142
        if url.startswith('/'):
143
            self.container, sep, path = url[1:].partition('/')
144
        self.path = path
145

    
146
    def _run(self, url=None):
147
        super(_pithos_container, self)._run()
148
        self._resolve_pithos_url(url or '')
149
        self.client.container = self['container'] or getattr(
150
            self, 'container', None) or getattr(self.client, 'container', '')
151

    
152

    
153
@command(file_cmds)
154
class file_list(_pithos_container, _optional_json, _name_filter):
155
    """List all objects in a container or a directory object"""
156

    
157
    arguments = dict(
158
        detail=FlagArgument('detailed output', ('-l', '--list')),
159
        limit=IntArgument('limit number of listed items', ('-n', '--number')),
160
        marker=ValueArgument('output greater that marker', '--marker'),
161
        delimiter=ValueArgument('show output up to delimiter', '--delimiter'),
162
        meta=ValueArgument(
163
            'show output with specified meta keys', '--meta',
164
            default=[]),
165
        if_modified_since=ValueArgument(
166
            'show output modified since then', '--if-modified-since'),
167
        if_unmodified_since=ValueArgument(
168
            'show output not modified since then', '--if-unmodified-since'),
169
        until=DateArgument('show metadata until then', '--until'),
170
        format=ValueArgument(
171
            'format to parse until data (default: d/m/Y H:M:S )', '--format'),
172
        shared=FlagArgument('show only shared', '--shared'),
173
        more=FlagArgument('read long results', '--more'),
174
        enum=FlagArgument('Enumerate results', '--enumerate'),
175
        recursive=FlagArgument(
176
            'Recursively list containers and their contents',
177
            ('-R', '--recursive'))
178
    )
179

    
180
    def print_objects(self, object_list):
181
        for index, obj in enumerate(object_list):
182
            pretty_obj = obj.copy()
183
            index += 1
184
            empty_space = ' ' * (len(str(len(object_list))) - len(str(index)))
185
            if 'subdir' in obj:
186
                continue
187
            if obj['content_type'] == 'application/directory':
188
                isDir, size = True, 'D'
189
            else:
190
                isDir, size = False, format_size(obj['bytes'])
191
                pretty_obj['bytes'] = '%s (%s)' % (obj['bytes'], size)
192
            oname = obj['name'] if self['more'] else bold(obj['name'])
193
            prfx = ('%s%s. ' % (empty_space, index)) if self['enum'] else ''
194
            if self['detail']:
195
                self.writeln('%s%s' % (prfx, oname))
196
                self.print_dict(pretty_obj, exclude=('name'))
197
                self.writeln()
198
            else:
199
                oname = '%s%9s %s' % (prfx, size, oname)
200
                oname += '/' if isDir else u''
201
                self.writeln(oname)
202

    
203
    @errors.generic.all
204
    @errors.pithos.connection
205
    @errors.pithos.container
206
    @errors.pithos.object_path
207
    def _run(self):
208
        r = self.client.container_get(
209
            limit=False if self['more'] else self['limit'],
210
            marker=self['marker'],
211
            prefix=self['name_pref'] or '/',
212
            delimiter=self['delimiter'],
213
            path=self.path or '',
214
            if_modified_since=self['if_modified_since'],
215
            if_unmodified_since=self['if_unmodified_since'],
216
            until=self['until'],
217
            meta=self['meta'],
218
            show_only_shared=self['shared'])
219
        files = self._filter_by_name(r.json)
220
        if self['more']:
221
            outbu, self._out = self._out, StringIO()
222
        try:
223
            if self['json_output'] or self['output_format']:
224
                self._print(files)
225
            else:
226
                self.print_objects(files)
227
        finally:
228
            if self['more']:
229
                pager(self._out.getvalue())
230
                self._out = outbu
231

    
232
    def main(self, path_or_url='/'):
233
        super(self.__class__, self)._run(path_or_url)
234
        self._run()
235

    
236

    
237
@command(file_cmds)
238
class file_create(_pithos_container, _optional_output_cmd):
239
    """Create an empty remove file"""
240

    
241
    arguments = dict(
242
        content_type=ValueArgument(
243
            'Set content type (default: application/octet-stream)',
244
            '--content-type',
245
            default='application/octet-stream')
246
    )
247

    
248
    def _run(self):
249
        self._optional_output(
250
            self.client.create_object(self.path, self['content_type']))
251

    
252
    def main(self, path_or_url):
253
        super(self.__class__, self)._run(path_or_url)
254
        self._run()