1# -*- coding: utf-8 -*- #
2# Copyright 2020 Google LLC. All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#    http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15"""Classes for cloud/file references yielded by storage iterators."""
16
17from __future__ import absolute_import
18from __future__ import division
19from __future__ import unicode_literals
20
21import collections
22import json
23
24from googlecloudsdk.command_lib.storage import errors
25
26
27class Resource(object):
28  """Base class for a reference to one fully expanded iterator result.
29
30  This allows polymorphic iteration over wildcard-iterated URLs.  The
31  reference contains a fully expanded URL string containing no wildcards and
32  referring to exactly one entity (if a wildcard is contained, it is assumed
33  this is part of the raw string and should never be treated as a wildcard).
34
35  Each reference represents a Bucket, Object, or Prefix.  For filesystem URLs,
36  Objects represent files and Prefixes represent directories.
37
38  The metadata_object member contains the underlying object as it was retrieved.
39  It is populated by the calling iterator, which may only request certain
40  fields to reduce the number of server requests.
41
42  For filesystem and prefix URLs, metadata_object is not populated.
43
44  Attributes:
45    TYPE_STRING (str): String representing the resource's content type.
46    storage_url (StorageUrl): A StorageUrl object representing the resource.
47  """
48  TYPE_STRING = 'resource'
49
50  def __init__(self, storage_url_object):
51    """Initialize the Resource object.
52
53    Args:
54      storage_url_object (StorageUrl): A StorageUrl object representing the
55          resource.
56    """
57    self.storage_url = storage_url_object
58
59  def __str__(self):
60    return self.storage_url.url_string
61
62  def __eq__(self, other):
63    return (
64        isinstance(other, self.__class__) and
65        self.storage_url == other.storage_url
66    )
67
68  def is_container(self):
69    raise NotImplementedError('is_container must be overridden.')
70
71
72class CloudResource(Resource):
73  """For Resource classes with CloudUrl's.
74
75  Attributes:
76    TYPE_STRING (str): String representing the resource's content type.
77    scheme (storage_url.ProviderPrefix): Prefix indicating what cloud provider
78        hosts the bucket.
79    storage_url (StorageUrl): A StorageUrl object representing the resource.
80  """
81  TYPE_STRING = 'cloud_resource'
82
83  @property
84  def scheme(self):
85    # TODO(b/168690302): Stop using string scheme in storage_url.py.
86    return self.storage_url.scheme
87
88  def get_json_dump(self):
89    raise NotImplementedError('get_json_dump must be overridden.')
90
91
92class BucketResource(CloudResource):
93  """Class representing a bucket.
94
95  Attributes:
96    TYPE_STRING (str): String representing the resource's content type.
97    storage_url (StorageUrl): A StorageUrl object representing the bucket.
98    name (str): Name of bucket.
99    scheme (storage_url.ProviderPrefix): Prefix indicating what cloud provider
100        hosts the bucket.
101    etag (str): HTTP version identifier.
102    metadata (object | dict): Cloud-provider specific data type for holding
103        bucket metadata.
104  """
105  TYPE_STRING = 'cloud_bucket'
106
107  def __init__(self, storage_url_object, etag=None, metadata=None):
108    """Initializes resource. Args are a subset of attributes."""
109    super(BucketResource, self).__init__(storage_url_object)
110    self.etag = etag
111    self.metadata = metadata
112
113  @property
114  def name(self):
115    return self.storage_url.bucket_name
116
117  def __eq__(self, other):
118    return (
119        super(BucketResource, self).__eq__(other) and
120        self.etag == other.etag and
121        self.metadata == other.metadata
122    )
123
124  def is_container(self):
125    return True
126
127  def get_json_dump(self):
128    super(BucketResource).get_json_dump()
129
130
131class ObjectResource(CloudResource):
132  """Class representing a cloud object confirmed to exist.
133
134  Attributes:
135    TYPE_STRING (str): String representing the resource's content type.
136    storage_url (StorageUrl): A StorageUrl object representing the object.
137    creation_time (datetime|None): Time the object was created.
138    etag (str|None): HTTP version identifier.
139    md5_hash (bytes): Base64-encoded digest of md5 hash.
140    metageneration (int|None): Generation object's metadata.
141    metadata (object|dict|None): Cloud-specific metadata type.
142    size (int|None): Size of object in bytes.
143    scheme (storage_url.ProviderPrefix): Prefix indicating what cloud provider
144        hosts the object.
145    bucket (str): Bucket that contains the object.
146    name (str): Name of object.
147    generation (str|None): Generation (or "version") of the underlying object.
148  """
149  TYPE_STRING = 'cloud_object'
150
151  def __init__(self,
152               storage_url_object,
153               creation_time=None,
154               etag=None,
155               md5_hash=None,
156               metadata=None,
157               metageneration=None,
158               size=None):
159    """Initializes resource. Args are a subset of attributes."""
160    super(ObjectResource, self).__init__(storage_url_object)
161    self.creation_time = creation_time
162    self.etag = etag
163    self.md5_hash = md5_hash
164    self.metageneration = metageneration
165    self.metadata = metadata
166    self.size = size
167
168  @property
169  def bucket(self):
170    return self.storage_url.bucket_name
171
172  @property
173  def name(self):
174    return self.storage_url.object_name
175
176  @property
177  def generation(self):
178    return self.storage_url.generation
179
180  def __eq__(self, other):
181    return (super(ObjectResource, self).__eq__(other) and
182            self.etag == other.etag and self.generation == other.generation and
183            self.md5_hash == other.md5_hash and self.metadata == other.metadata)
184
185  def is_container(self):
186    return False
187
188  def get_json_dump(self):
189    super(ObjectResource).get_json_dump()
190
191
192class PrefixResource(Resource):
193  """Class representing a  cloud object.
194
195  Attributes:
196    TYPE_STRING (str): String representing the resource's content type.
197    storage_url (StorageUrl): A StorageUrl object representing the prefix.
198    prefix (str): A string representing the prefix.
199  """
200  TYPE_STRING = 'prefix'
201
202  def __init__(self, storage_url_object, prefix):
203    """Initialize the PrefixResource object.
204
205    Args:
206      storage_url_object (StorageUrl): A StorageUrl object representing the
207          prefix.
208      prefix (str): A string representing the prefix.
209    """
210    super(PrefixResource, self).__init__(storage_url_object)
211    self.prefix = prefix
212
213  def is_container(self):
214    return True
215
216  def get_json_dump(self):
217    return json.dumps(collections.OrderedDict([
218        ('url', self.storage_url.versionless_url_string),
219        ('type', self.TYPE_STRING),
220    ]), indent=2)
221
222
223class FileObjectResource(Resource):
224  """Wrapper for a filesystem file.
225
226  Attributes:
227    TYPE_STRING (str): String representing the resource's content type.
228    storage_url (StorageUrl): A StorageUrl object representing the resource.
229    md5_hash (bytes): Base64-encoded digest of md5 hash.
230  """
231  TYPE_STRING = 'file_object'
232
233  def __init__(self, storage_url_object, md5_hash=None):
234    """Initializes resource. Args are a subset of attributes."""
235    super(FileObjectResource, self).__init__(storage_url_object)
236    self.md5_hash = md5_hash
237
238  def is_container(self):
239    return False
240
241
242class FileDirectoryResource(Resource):
243  """Wrapper for a File system directory."""
244  TYPE_STRING = 'file_directory'
245
246  def is_container(self):
247    return True
248
249
250class UnknownResource(Resource):
251  """Represents a resource that may or may not exist."""
252  TYPE_STRING = 'unknown'
253
254  def is_container(self):
255    raise errors.ValueCannotBeDeterminedError(
256        'Unknown whether or not UnknownResource is a container.')
257