1"""
2Swift utility class
3===================
4Author: Anthony Stanton <anthony.stanton@gmail.com>
5"""
6
7import logging
8import sys
9from errno import EEXIST
10from os import makedirs
11from os.path import dirname, isdir
12
13import salt.utils.files
14
15# Get logging started
16log = logging.getLogger(__name__)
17
18HAS_SWIFT = False
19try:
20    from swiftclient import client
21
22    HAS_SWIFT = True
23except ImportError:
24    pass
25
26
27def check_swift():
28    return HAS_SWIFT
29
30
31def mkdirs(path):
32    try:
33        makedirs(path)
34    except OSError as err:
35        if err.errno != EEXIST:
36            raise
37
38
39# we've been playing fast and loose with kwargs, but the swiftclient isn't
40# going to accept any old thing
41def _sanitize(kwargs):
42    variables = (
43        "user",
44        "key",
45        "authurl",
46        "retries",
47        "preauthurl",
48        "preauthtoken",
49        "snet",
50        "starting_backoff",
51        "max_backoff",
52        "tenant_name",
53        "os_options",
54        "auth_version",
55        "cacert",
56        "insecure",
57        "ssl_compression",
58    )
59    ret = {}
60    for var in kwargs:
61        if var in variables:
62            ret[var] = kwargs[var]
63
64    return ret
65
66
67class SaltSwift:
68    """
69    Class for all swiftclient functions
70    """
71
72    def __init__(
73        self, user, tenant_name, auth_url, password=None, auth_version=2, **kwargs
74    ):
75        """
76        Set up openstack credentials
77        """
78        if not HAS_SWIFT:
79            log.error(
80                "Error:: unable to find swiftclient. Try installing it from the"
81                " appropriate repository."
82            )
83            return None
84
85        self.kwargs = kwargs.copy()
86        self.kwargs["user"] = user
87        self.kwargs["password"] = password
88        self.kwargs["tenant_name"] = tenant_name
89        self.kwargs["authurl"] = auth_url
90        self.kwargs["auth_version"] = auth_version
91        if "key" not in self.kwargs:
92            self.kwargs["key"] = password
93
94        self.kwargs = _sanitize(self.kwargs)
95
96        self.conn = client.Connection(**self.kwargs)
97
98    def get_account(self):
99        """
100        List Swift containers
101        """
102        try:
103            listing = self.conn.get_account()
104            return listing
105        except Exception as exc:  # pylint: disable=broad-except
106            log.error("There was an error::")
107            if hasattr(exc, "code") and hasattr(exc, "msg"):
108                log.error("    Code: %s: %s", exc.code, exc.msg)
109            log.error("    Content: \n%s", getattr(exc, "read", lambda: str(exc))())
110            return False
111
112    def get_container(self, cont):
113        """
114        List files in a Swift container
115        """
116        try:
117            listing = self.conn.get_container(cont)
118            return listing
119        except Exception as exc:  # pylint: disable=broad-except
120            log.error("There was an error::")
121            if hasattr(exc, "code") and hasattr(exc, "msg"):
122                log.error("    Code: %s: %s", exc.code, exc.msg)
123            log.error("    Content: \n%s", getattr(exc, "read", lambda: str(exc))())
124            return False
125
126    def put_container(self, cont):
127        """
128        Create a new Swift container
129        """
130        try:
131            self.conn.put_container(cont)
132            return True
133        except Exception as exc:  # pylint: disable=broad-except
134            log.error("There was an error::")
135            if hasattr(exc, "code") and hasattr(exc, "msg"):
136                log.error("    Code: %s: %s", exc.code, exc.msg)
137            log.error("    Content: \n%s", getattr(exc, "read", lambda: str(exc))())
138            return False
139
140    def delete_container(self, cont):
141        """
142        Delete a Swift container
143        """
144        try:
145            self.conn.delete_container(cont)
146            return True
147        except Exception as exc:  # pylint: disable=broad-except
148            log.error("There was an error::")
149            if hasattr(exc, "code") and hasattr(exc, "msg"):
150                log.error("    Code: %s: %s", exc.code, exc.msg)
151            log.error("    Content: \n%s", getattr(exc, "read", lambda: str(exc))())
152            return False
153
154    def post_container(self, cont, metadata=None):
155        """
156        Update container metadata
157        """
158
159    def head_container(self, cont):
160        """
161        Get container metadata
162        """
163
164    def get_object(self, cont, obj, local_file=None, return_bin=False):
165        """
166        Retrieve a file from Swift
167        """
168        try:
169            if local_file is None and return_bin is False:
170                return False
171
172            headers, body = self.conn.get_object(cont, obj, resp_chunk_size=65536)
173
174            if return_bin is True:
175                fp = sys.stdout
176            else:
177                dirpath = dirname(local_file)
178                if dirpath and not isdir(dirpath):
179                    mkdirs(dirpath)
180                # pylint: disable=resource-leakage
181                fp = salt.utils.files.fopen(local_file, "wb")
182                # pylint: enable=resource-leakage
183
184            read_length = 0
185            for chunk in body:
186                read_length += len(chunk)
187                fp.write(chunk)
188            fp.close()
189            return True
190
191        # ClientException
192        # file/dir exceptions
193        except Exception as exc:  # pylint: disable=broad-except
194            log.error("There was an error::")
195            if hasattr(exc, "code") and hasattr(exc, "msg"):
196                log.error("    Code: %s: %s", exc.code, exc.msg)
197            log.error("    Content: \n%s", getattr(exc, "read", lambda: str(exc))())
198            return False
199
200    def put_object(self, cont, obj, local_file):
201        """
202        Upload a file to Swift
203        """
204        try:
205            with salt.utils.files.fopen(local_file, "rb") as fp_:
206                self.conn.put_object(cont, obj, fp_)
207            return True
208        except Exception as exc:  # pylint: disable=broad-except
209            log.error("There was an error::")
210            if hasattr(exc, "code") and hasattr(exc, "msg"):
211                log.error("    Code: %s: %s", exc.code, exc.msg)
212            log.error("    Content: \n%s", getattr(exc, "read", lambda: str(exc))())
213            return False
214
215    def delete_object(self, cont, obj):
216        """
217        Delete a file from Swift
218        """
219        try:
220            self.conn.delete_object(cont, obj)
221            return True
222        except Exception as exc:  # pylint: disable=broad-except
223            log.error("There was an error::")
224            if hasattr(exc, "code") and hasattr(exc, "msg"):
225                log.error("    Code: %s: %s", exc.code, exc.msg)
226            log.error("    Content: \n%s", getattr(exc, "read", lambda: str(exc))())
227            return False
228
229    def head_object(self, cont, obj):
230        """
231        Get object metadata
232        """
233
234    def post_object(self, cont, obj, metadata):
235        """
236        Update object metadata
237        """
238