1"""
2Connection module for Amazon SQS
3
4.. versionadded:: 2014.7.0
5
6:configuration: This module accepts explicit sqs credentials but can also utilize
7    IAM roles assigned to the instance through Instance Profiles. Dynamic
8    credentials are then automatically obtained from AWS API and no further
9    configuration is necessary. More information available at:
10
11    .. code-block:: text
12
13        http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html
14
15    If IAM roles are not used you need to specify them either in a pillar or
16    in the minion's config file:
17
18    .. code-block:: yaml
19
20        sqs.keyid: GKTADJGHEIQSXMKKRBJ08H
21        sqs.key: askdjghsdfjkghWupUjasdflkdfklgjsdfjajkghs
22
23    A region may also be specified in the configuration:
24
25    .. code-block:: yaml
26
27        sqs.region: us-east-1
28
29    If a region is not specified, the default is us-east-1.
30
31    It's also possible to specify key, keyid and region via a profile, either
32    as a passed in dict, or as a string to pull from pillars or minion config:
33
34    .. code-block:: yaml
35
36        myprofile:
37            keyid: GKTADJGHEIQSXMKKRBJ08H
38            key: askdjghsdfjkghWupUjasdflkdfklgjsdfjajkghs
39            region: us-east-1
40
41:depends: boto3
42"""
43# keep lint from choking on _get_conn and _cache_id
44# pylint: disable=E0602
45
46
47import logging
48import urllib.parse
49
50import salt.utils.json
51import salt.utils.versions
52
53log = logging.getLogger(__name__)
54
55__func_alias__ = {
56    "list_": "list",
57}
58
59try:
60    # pylint: disable=unused-import
61    import boto3
62    import botocore
63
64    # pylint: enable=unused-import
65    logging.getLogger("boto3").setLevel(logging.CRITICAL)
66    HAS_BOTO3 = True
67except ImportError:
68    HAS_BOTO3 = False
69
70
71def __virtual__():
72    """
73    Only load if boto3 libraries exist.
74    """
75    has_boto_reqs = salt.utils.versions.check_boto_reqs()
76    if has_boto_reqs is True:
77        __utils__["boto3.assign_funcs"](__name__, "sqs")
78    return has_boto_reqs
79
80
81def _preprocess_attributes(attributes):
82    """
83    Pre-process incoming queue attributes before setting them
84    """
85    if isinstance(attributes, str):
86        attributes = salt.utils.json.loads(attributes)
87
88    def stringified(val):
89        # Some attributes take full json policy documents, but they take them
90        # as json strings. Convert the value back into a json string.
91        if isinstance(val, dict):
92            return salt.utils.json.dumps(val)
93        return val
94
95    return {attr: stringified(val) for attr, val in attributes.items()}
96
97
98def exists(name, region=None, key=None, keyid=None, profile=None):
99    """
100    Check to see if a queue exists.
101
102    CLI Example:
103
104    .. code-block:: bash
105
106        salt myminion boto_sqs.exists myqueue region=us-east-1
107    """
108    conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
109
110    try:
111        conn.get_queue_url(QueueName=name)
112    except botocore.exceptions.ClientError as e:
113        missing_code = "AWS.SimpleQueueService.NonExistentQueue"
114        if e.response.get("Error", {}).get("Code") == missing_code:
115            return {"result": False}
116        return {"error": __utils__["boto3.get_error"](e)}
117    return {"result": True}
118
119
120def create(
121    name,
122    attributes=None,
123    region=None,
124    key=None,
125    keyid=None,
126    profile=None,
127):
128    """
129    Create an SQS queue.
130
131    CLI Example:
132
133    .. code-block:: bash
134
135        salt myminion boto_sqs.create myqueue region=us-east-1
136    """
137    conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
138
139    if attributes is None:
140        attributes = {}
141    attributes = _preprocess_attributes(attributes)
142
143    try:
144        conn.create_queue(QueueName=name, Attributes=attributes)
145    except botocore.exceptions.ClientError as e:
146        return {"error": __utils__["boto3.get_error"](e)}
147    return {"result": True}
148
149
150def delete(name, region=None, key=None, keyid=None, profile=None):
151    """
152    Delete an SQS queue.
153
154    CLI Example:
155
156    .. code-block:: bash
157
158        salt myminion boto_sqs.delete myqueue region=us-east-1
159    """
160    conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
161
162    try:
163        url = conn.get_queue_url(QueueName=name)["QueueUrl"]
164        conn.delete_queue(QueueUrl=url)
165    except botocore.exceptions.ClientError as e:
166        return {"error": __utils__["boto3.get_error"](e)}
167    return {"result": True}
168
169
170def list_(prefix="", region=None, key=None, keyid=None, profile=None):
171    """
172    Return a list of the names of all visible queues.
173
174    .. versionadded:: 2016.11.0
175
176    CLI Example:
177
178    .. code-block:: bash
179
180        salt myminion boto_sqs.list region=us-east-1
181    """
182    conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
183
184    def extract_name(queue_url):
185        # Note: this logic taken from boto, so should be safe
186        return urllib.parse.urlparse(queue_url).path.split("/")[2]
187
188    try:
189        r = conn.list_queues(QueueNamePrefix=prefix)
190        # The 'QueueUrls' attribute is missing if there are no queues
191        urls = r.get("QueueUrls", [])
192        return {"result": [extract_name(url) for url in urls]}
193    except botocore.exceptions.ClientError as e:
194        return {"error": __utils__["boto3.get_error"](e)}
195
196
197def get_attributes(name, region=None, key=None, keyid=None, profile=None):
198    """
199    Return attributes currently set on an SQS queue.
200
201    CLI Example:
202
203    .. code-block:: bash
204
205        salt myminion boto_sqs.get_attributes myqueue
206    """
207    conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
208
209    try:
210        url = conn.get_queue_url(QueueName=name)["QueueUrl"]
211        r = conn.get_queue_attributes(QueueUrl=url, AttributeNames=["All"])
212        return {"result": r["Attributes"]}
213    except botocore.exceptions.ClientError as e:
214        return {"error": __utils__["boto3.get_error"](e)}
215
216
217def set_attributes(
218    name,
219    attributes,
220    region=None,
221    key=None,
222    keyid=None,
223    profile=None,
224):
225    """
226    Set attributes on an SQS queue.
227
228    CLI Example:
229
230    .. code-block:: bash
231
232        salt myminion boto_sqs.set_attributes myqueue '{ReceiveMessageWaitTimeSeconds: 20}' region=us-east-1
233    """
234    conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
235
236    attributes = _preprocess_attributes(attributes)
237
238    try:
239        url = conn.get_queue_url(QueueName=name)["QueueUrl"]
240        conn.set_queue_attributes(QueueUrl=url, Attributes=attributes)
241    except botocore.exceptions.ClientError as e:
242        return {"error": __utils__["boto3.get_error"](e)}
243    return {"result": True}
244