1"""
2Connection module for Amazon Elasticsearch Service
3
4.. versionadded:: 3001
5
6:configuration: This module accepts explicit IAM credentials but can also
7    utilize IAM roles assigned to the instance trough Instance Profiles.
8    Dynamic credentials are then automatically obtained from AWS API and no
9    further 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        es.keyid: GKTADJGHEIQSXMKKRBJ08H
21        es.key: askdjghsdfjkghWupUjasdflkdfklgjsdfjajkghs
22
23    A region may also be specified in the configuration:
24
25    .. code-block:: yaml
26
27        es.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    All methods return a dict with:
42        'result' key containing a boolean indicating success or failure,
43        'error' key containing the errormessage returned by boto on error,
44        'response' key containing the data of the response returned by boto on success.
45
46:codeauthor: Herbert Buurman <herbert.buurman@ogd.nl>
47:depends: boto3
48"""
49# keep lint from choking on _get_conn and _cache_id
50# pylint: disable=E0602
51
52
53import logging
54
55import salt.utils.compat
56import salt.utils.json
57import salt.utils.versions
58from salt.exceptions import SaltInvocationError
59from salt.utils.decorators import depends
60
61try:
62    # Disable unused import-errors as these are only used for dependency checking
63    # pylint: disable=unused-import
64    import boto3
65    import botocore
66
67    # pylint: enable=unused-import
68    from botocore.exceptions import ClientError, ParamValidationError, WaiterError
69
70    logging.getLogger("boto3").setLevel(logging.INFO)
71    HAS_BOTO = True
72except ImportError:
73    HAS_BOTO = False
74
75log = logging.getLogger(__name__)
76
77
78def __virtual__():
79    """
80    Only load if boto libraries exist and if boto libraries are greater than
81    a given version.
82    """
83    return HAS_BOTO and salt.utils.versions.check_boto_reqs(
84        boto3_ver="1.2.7", check_boto=False
85    )
86
87
88def __init__(opts):
89    _ = opts
90    if HAS_BOTO:
91        __utils__["boto3.assign_funcs"](__name__, "es")
92
93
94def add_tags(
95    domain_name=None,
96    arn=None,
97    tags=None,
98    region=None,
99    key=None,
100    keyid=None,
101    profile=None,
102):
103    """
104    Attaches tags to an existing Elasticsearch domain.
105    Tags are a set of case-sensitive key value pairs.
106    An Elasticsearch domain may have up to 10 tags.
107
108    :param str domain_name: The name of the Elasticsearch domain you want to add tags to.
109    :param str arn: The ARN of the Elasticsearch domain you want to add tags to.
110        Specifying this overrides ``domain_name``.
111    :param dict tags: The dict of tags to add to the Elasticsearch domain.
112
113    :rtype: dict
114    :return: Dictionary with key 'result' and as value a boolean denoting success or failure.
115        Upon failure, also contains a key 'error' with the error message as value.
116
117    .. versionadded:: 3001
118
119    CLI Example:
120
121    .. code-block:: bash
122
123        salt myminion boto3_elasticsearch.add_tags domain_name=mydomain tags='{"foo": "bar", "baz": "qux"}'
124    """
125    if not any((arn, domain_name)):
126        raise SaltInvocationError(
127            "At least one of domain_name or arn must be specified."
128        )
129    ret = {"result": False}
130    if arn is None:
131        res = describe_elasticsearch_domain(
132            domain_name=domain_name,
133            region=region,
134            key=key,
135            keyid=keyid,
136            profile=profile,
137        )
138        if "error" in res:
139            ret.update(res)
140        elif not res["result"]:
141            ret.update(
142                {
143                    "error": 'The domain with name "{}" does not exist.'.format(
144                        domain_name
145                    )
146                }
147            )
148        else:
149            arn = res["response"].get("ARN")
150    if arn:
151        boto_params = {
152            "ARN": arn,
153            "TagList": [
154                {"Key": k, "Value": value} for k, value in (tags or {}).items()
155            ],
156        }
157        try:
158            conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
159            conn.add_tags(**boto_params)
160            ret["result"] = True
161        except (ParamValidationError, ClientError) as exp:
162            ret.update({"error": __utils__["boto3.get_error"](exp)["message"]})
163    return ret
164
165
166@depends("botocore", version="1.12.21")
167def cancel_elasticsearch_service_software_update(
168    domain_name, region=None, keyid=None, key=None, profile=None
169):
170    """
171    Cancels a scheduled service software update for an Amazon ES domain. You can
172    only perform this operation before the AutomatedUpdateDate and when the UpdateStatus
173    is in the PENDING_UPDATE state.
174
175    :param str domain_name: The name of the domain that you want to stop the latest
176        service software update on.
177
178    :rtype: dict
179    :return: Dictionary with key 'result' and as value a boolean denoting success or failure.
180        Upon success, also contains a key 'reponse' with the current service software options.
181        Upon failure, also contains a key 'error' with the error message as value.
182
183    .. versionadded:: 3001
184
185    """
186    ret = {"result": False}
187    try:
188        conn = _get_conn(region=region, keyid=keyid, key=key, profile=profile)
189        res = conn.cancel_elasticsearch_service_software_update(DomainName=domain_name)
190        ret["result"] = True
191        res["response"] = res["ServiceSoftwareOptions"]
192    except (ParamValidationError, ClientError) as exp:
193        ret.update({"error": __utils__["boto3.get_error"](exp)["message"]})
194    return ret
195
196
197def create_elasticsearch_domain(
198    domain_name,
199    elasticsearch_version=None,
200    elasticsearch_cluster_config=None,
201    ebs_options=None,
202    access_policies=None,
203    snapshot_options=None,
204    vpc_options=None,
205    cognito_options=None,
206    encryption_at_rest_options=None,
207    node_to_node_encryption_options=None,
208    advanced_options=None,
209    log_publishing_options=None,
210    blocking=False,
211    region=None,
212    key=None,
213    keyid=None,
214    profile=None,
215):
216    """
217    Given a valid config, create a domain.
218
219    :param str domain_name: The name of the Elasticsearch domain that you are creating.
220        Domain names are unique across the domains owned by an account within an
221        AWS region. Domain names must start with a letter or number and can contain
222        the following characters: a-z (lowercase), 0-9, and - (hyphen).
223    :param str elasticsearch_version: String of format X.Y to specify version for
224        the Elasticsearch domain eg. "1.5" or "2.3".
225    :param dict elasticsearch_cluster_config: Dictionary specifying the configuration
226        options for an Elasticsearch domain. Keys (case sensitive) in here are:
227
228        - InstanceType (str): The instance type for an Elasticsearch cluster.
229        - InstanceCount (int): The instance type for an Elasticsearch cluster.
230        - DedicatedMasterEnabled (bool): Indicate whether a dedicated master
231          node is enabled.
232        - ZoneAwarenessEnabled (bool): Indicate whether zone awareness is enabled.
233          If this is not enabled, the Elasticsearch domain will only be in one
234          availability zone.
235        - ZoneAwarenessConfig (dict): Specifies the zone awareness configuration
236          for a domain when zone awareness is enabled.
237          Keys (case sensitive) in here are:
238
239          - AvailabilityZoneCount (int): An integer value to indicate the
240            number of availability zones for a domain when zone awareness is
241            enabled. This should be equal to number of subnets if VPC endpoints
242            is enabled. Allowed values: 2, 3
243
244        - DedicatedMasterType (str): The instance type for a dedicated master node.
245        - DedicatedMasterCount (int): Total number of dedicated master nodes,
246          active and on standby, for the cluster.
247    :param dict ebs_options: Dict specifying the options to enable or disable and
248        specifying the type and size of EBS storage volumes.
249        Keys (case sensitive) in here are:
250
251        - EBSEnabled (bool): Specifies whether EBS-based storage is enabled.
252        - VolumeType (str): Specifies the volume type for EBS-based storage.
253        - VolumeSize (int): Integer to specify the size of an EBS volume.
254        - Iops (int): Specifies the IOPD for a Provisioned IOPS EBS volume (SSD).
255    :type access_policies: str or dict
256    :param access_policies: Dict or JSON string with the IAM access policy.
257    :param dict snapshot_options: Dict specifying the snapshot options.
258        Keys (case sensitive) in here are:
259
260        - AutomatedSnapshotStartHour (int): Specifies the time, in UTC format,
261          when the service takes a daily automated snapshot of the specified
262          Elasticsearch domain. Default value is 0 hours.
263    :param dict vpc_options: Dict with the options to specify the subnets and security
264        groups for the VPC endpoint.
265        Keys (case sensitive) in here are:
266
267        - SubnetIds (list): The list of subnets for the VPC endpoint.
268        - SecurityGroupIds (list): The list of security groups for the VPC endpoint.
269    :param dict cognito_options: Dict with options to specify the cognito user and
270        identity pools for Kibana authentication.
271        Keys (case sensitive) in here are:
272
273        - Enabled (bool): Specifies the option to enable Cognito for Kibana authentication.
274        - UserPoolId (str): Specifies the Cognito user pool ID for Kibana authentication.
275        - IdentityPoolId (str): Specifies the Cognito identity pool ID for Kibana authentication.
276        - RoleArn (str): Specifies the role ARN that provides Elasticsearch permissions
277          for accessing Cognito resources.
278    :param dict encryption_at_rest_options: Dict specifying the encryption at rest
279        options. Keys (case sensitive) in here are:
280
281        - Enabled (bool): Specifies the option to enable Encryption At Rest.
282        - KmsKeyId (str): Specifies the KMS Key ID for Encryption At Rest options.
283    :param dict node_to_node_encryption_options: Dict specifying the node to node
284        encryption options. Keys (case sensitive) in here are:
285
286        - Enabled (bool): Specify True to enable node-to-node encryption.
287    :param dict advanced_options: Dict with option to allow references to indices
288        in an HTTP request body. Must be False when configuring access to individual
289        sub-resources. By default, the value is True.
290        See http://docs.aws.amazon.com/elasticsearch-service/latest/developerguide\
291        /es-createupdatedomains.html#es-createdomain-configure-advanced-options
292        for more information.
293    :param dict log_publishing_options: Dict with options for various type of logs.
294        The keys denote the type of log file and can be one of the following:
295
296        - INDEX_SLOW_LOGS
297        - SEARCH_SLOW_LOGS
298        - ES_APPLICATION_LOGS
299
300        The value assigned to each key is a dict with the following case sensitive keys:
301
302        - CloudWatchLogsLogGroupArn (str): The ARN of the Cloudwatch log
303          group to which the log needs to be published.
304        - Enabled (bool): Specifies whether given log publishing option is enabled or not.
305    :param bool blocking: Whether or not to wait (block) until the Elasticsearch
306        domain has been created.
307
308    Note: Not all instance types allow enabling encryption at rest. See https://docs.aws.amazon.com\
309        /elasticsearch-service/latest/developerguide/aes-supported-instance-types.html
310
311    :rtype: dict
312    :return: Dictionary with key 'result' and as value a boolean denoting success or failure.
313        Upon success, also contains a key 'reponse' with the domain status configuration.
314        Upon failure, also contains a key 'error' with the error message as value.
315
316    .. versionadded:: 3001
317
318    CLI Example:
319
320    .. code-block:: bash
321
322        salt myminion boto3_elasticsearch.create_elasticsearch_domain mydomain \\
323        elasticsearch_cluster_config='{ \\
324          "InstanceType": "t2.micro.elasticsearch", \\
325          "InstanceCount": 1, \\
326          "DedicatedMasterEnabled": False, \\
327          "ZoneAwarenessEnabled": False}' \\
328        ebs_options='{ \\
329          "EBSEnabled": True, \\
330          "VolumeType": "gp2", \\
331          "VolumeSize": 10, \\
332          "Iops": 0}' \\
333        access_policies='{ \\
334          "Version": "2012-10-17", \\
335          "Statement": [ \\
336            {"Effect": "Allow", \\
337             "Principal": {"AWS": "*"}, \\
338             "Action": "es:*", \\
339             "Resource": "arn:aws:es:us-east-1:111111111111:domain/mydomain/*", \\
340             "Condition": {"IpAddress": {"aws:SourceIp": ["127.0.0.1"]}}}]}' \\
341        snapshot_options='{"AutomatedSnapshotStartHour": 0}' \\
342        advanced_options='{"rest.action.multi.allow_explicit_index": "true"}'
343    """
344    boto_kwargs = salt.utils.data.filter_falsey(
345        {
346            "DomainName": domain_name,
347            "ElasticsearchVersion": str(elasticsearch_version or ""),
348            "ElasticsearchClusterConfig": elasticsearch_cluster_config,
349            "EBSOptions": ebs_options,
350            "AccessPolicies": (
351                salt.utils.json.dumps(access_policies)
352                if isinstance(access_policies, dict)
353                else access_policies
354            ),
355            "SnapshotOptions": snapshot_options,
356            "VPCOptions": vpc_options,
357            "CognitoOptions": cognito_options,
358            "EncryptionAtRestOptions": encryption_at_rest_options,
359            "NodeToNodeEncryptionOptions": node_to_node_encryption_options,
360            "AdvancedOptions": advanced_options,
361            "LogPublishingOptions": log_publishing_options,
362        }
363    )
364    ret = {"result": False}
365    try:
366        conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
367        res = conn.create_elasticsearch_domain(**boto_kwargs)
368        if res and "DomainStatus" in res:
369            ret["result"] = True
370            ret["response"] = res["DomainStatus"]
371        if blocking:
372            conn.get_waiter("ESDomainAvailable").wait(DomainName=domain_name)
373    except (ParamValidationError, ClientError, WaiterError) as exp:
374        ret.update({"error": __utils__["boto3.get_error"](exp)["message"]})
375    return ret
376
377
378def delete_elasticsearch_domain(
379    domain_name, blocking=False, region=None, key=None, keyid=None, profile=None
380):
381    """
382    Permanently deletes the specified Elasticsearch domain and all of its data.
383    Once a domain is deleted, it cannot be recovered.
384
385    :param str domain_name: The name of the domain to delete.
386    :param bool blocking: Whether or not to wait (block) until the Elasticsearch
387        domain has been deleted.
388
389    :rtype: dict
390    :return: Dictionary with key 'result' and as value a boolean denoting success or failure.
391        Upon failure, also contains a key 'error' with the error message as value.
392
393    .. versionadded:: 3001
394
395    """
396    ret = {"result": False}
397    try:
398        conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
399        conn.delete_elasticsearch_domain(DomainName=domain_name)
400        ret["result"] = True
401        if blocking:
402            conn.get_waiter("ESDomainDeleted").wait(DomainName=domain_name)
403    except (ParamValidationError, ClientError, WaiterError) as exp:
404        ret.update({"error": __utils__["boto3.get_error"](exp)["message"]})
405    return ret
406
407
408@depends("botocore", version="1.7.30")
409def delete_elasticsearch_service_role(region=None, keyid=None, key=None, profile=None):
410    """
411    Deletes the service-linked role that Elasticsearch Service uses to manage and
412    maintain VPC domains. Role deletion will fail if any existing VPC domains use
413    the role. You must delete any such Elasticsearch domains before deleting the role.
414
415    :rtype: dict
416    :return: Dictionary with key 'result' and as value a boolean denoting success or failure.
417        Upon failure, also contains a key 'error' with the error message as value.
418
419    .. versionadded:: 3001
420
421    """
422    ret = {"result": False}
423    try:
424        conn = _get_conn(region=region, keyid=keyid, key=key, profile=profile)
425        conn.delete_elasticsearch_service_role()
426        ret["result"] = True
427    except (ParamValidationError, ClientError) as exp:
428        ret.update({"error": __utils__["boto3.get_error"](exp)["message"]})
429    return ret
430
431
432def describe_elasticsearch_domain(
433    domain_name, region=None, keyid=None, key=None, profile=None
434):
435    """
436    Given a domain name gets its status description.
437
438    :param str domain_name: The name of the domain to get the status of.
439
440    :rtype: dict
441    :return: Dictionary ith key 'result' and as value a boolean denoting success or failure.
442        Upon success, also contains a key 'reponse' with the domain status information.
443        Upon failure, also contains a key 'error' with the error message as value.
444
445    .. versionadded:: 3001
446
447    """
448    ret = {"result": False}
449    try:
450        conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
451        res = conn.describe_elasticsearch_domain(DomainName=domain_name)
452        if res and "DomainStatus" in res:
453            ret["result"] = True
454            ret["response"] = res["DomainStatus"]
455    except (ParamValidationError, ClientError) as exp:
456        ret.update({"error": __utils__["boto3.get_error"](exp)["message"]})
457    return ret
458
459
460def describe_elasticsearch_domain_config(
461    domain_name, region=None, keyid=None, key=None, profile=None
462):
463    """
464    Provides cluster configuration information about the specified Elasticsearch domain,
465    such as the state, creation date, update version, and update date for cluster options.
466
467    :param str domain_name: The name of the domain to describe.
468
469    :rtype: dict
470    :return: Dictionary with key 'result' and as value a boolean denoting success or failure.
471        Upon success, also contains a key 'reponse' with the current configuration information.
472        Upon failure, also contains a key 'error' with the error message as value.
473
474    .. versionadded:: 3001
475
476    """
477    ret = {"result": False}
478    try:
479        conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
480        res = conn.describe_elasticsearch_domain_config(DomainName=domain_name)
481        if res and "DomainConfig" in res:
482            ret["result"] = True
483            ret["response"] = res["DomainConfig"]
484    except (ParamValidationError, ClientError) as exp:
485        ret.update({"error": __utils__["boto3.get_error"](exp)["message"]})
486    return ret
487
488
489def describe_elasticsearch_domains(
490    domain_names, region=None, keyid=None, key=None, profile=None
491):
492    """
493    Returns domain configuration information about the specified Elasticsearch
494    domains, including the domain ID, domain endpoint, and domain ARN.
495
496    :param list domain_names: List of domain names to get information for.
497
498    :rtype: dict
499    :return: Dictionary with key 'result' and as value a boolean denoting success or failure.
500        Upon success, also contains a key 'reponse' with the list of domain status information.
501        Upon failure, also contains a key 'error' with the error message as value.
502
503    .. versionadded:: 3001
504
505    CLI Example:
506
507    .. code-block:: bash
508
509        salt myminion boto3_elasticsearch.describe_elasticsearch_domains '["domain_a", "domain_b"]'
510    """
511    ret = {"result": False}
512    try:
513        conn = _get_conn(region=region, keyid=keyid, key=key, profile=profile)
514        res = conn.describe_elasticsearch_domains(DomainNames=domain_names)
515        if res and "DomainStatusList" in res:
516            ret["result"] = True
517            ret["response"] = res["DomainStatusList"]
518    except (ParamValidationError, ClientError) as exp:
519        ret.update({"error": __utils__["boto3.get_error"](exp)["message"]})
520    return ret
521
522
523@depends("botocore", version="1.5.18")
524def describe_elasticsearch_instance_type_limits(
525    instance_type,
526    elasticsearch_version,
527    domain_name=None,
528    region=None,
529    keyid=None,
530    key=None,
531    profile=None,
532):
533    """
534    Describe Elasticsearch Limits for a given InstanceType and ElasticsearchVersion.
535    When modifying existing Domain, specify the `` DomainName `` to know what Limits
536    are supported for modifying.
537
538    :param str instance_type: The instance type for an Elasticsearch cluster for
539        which Elasticsearch ``Limits`` are needed.
540    :param str elasticsearch_version: Version of Elasticsearch for which ``Limits``
541        are needed.
542    :param str domain_name: Represents the name of the Domain that we are trying
543        to modify. This should be present only if we are querying for Elasticsearch
544        ``Limits`` for existing domain.
545
546    :rtype: dict
547    :return: Dictionary with key 'result' and as value a boolean denoting success or failure.
548        Upon success, also contains a key 'reponse' with the limits information.
549        Upon failure, also contains a key 'error' with the error message as value.
550
551    .. versionadded:: 3001
552
553    CLI Example:
554
555    .. code-block:: bash
556
557        salt myminion boto3_elasticsearch.describe_elasticsearch_instance_type_limits \\
558          instance_type=r3.8xlarge.elasticsearch \\
559          elasticsearch_version='6.2'
560    """
561    ret = {"result": False}
562    boto_params = salt.utils.data.filter_falsey(
563        {
564            "DomainName": domain_name,
565            "InstanceType": instance_type,
566            "ElasticsearchVersion": str(elasticsearch_version),
567        }
568    )
569    try:
570        conn = _get_conn(region=region, keyid=keyid, key=key, profile=profile)
571        res = conn.describe_elasticsearch_instance_type_limits(**boto_params)
572        if res and "LimitsByRole" in res:
573            ret["result"] = True
574            ret["response"] = res["LimitsByRole"]
575    except (ParamValidationError, ClientError) as exp:
576        ret.update({"error": __utils__["boto3.get_error"](exp)["message"]})
577    return ret
578
579
580@depends("botocore", version="1.10.15")
581def describe_reserved_elasticsearch_instance_offerings(
582    reserved_elasticsearch_instance_offering_id=None,
583    region=None,
584    keyid=None,
585    key=None,
586    profile=None,
587):
588    """
589    Lists available reserved Elasticsearch instance offerings.
590
591    :param str reserved_elasticsearch_instance_offering_id: The offering identifier
592        filter value. Use this parameter to show only the available offering that
593        matches the specified reservation identifier.
594
595    :rtype: dict
596    :return: Dictionary with key 'result' and as value a boolean denoting success or failure.
597        Upon success, also contains a key 'reponse' with the list of offerings information.
598        Upon failure, also contains a key 'error' with the error message as value.
599
600    .. versionadded:: 3001
601
602    """
603    ret = {"result": False}
604    try:
605        conn = _get_conn(region=region, keyid=keyid, key=key, profile=profile)
606        boto_params = {
607            "ReservedElasticsearchInstanceOfferingId": reserved_elasticsearch_instance_offering_id
608        }
609        res = []
610        for page in conn.get_paginator(
611            "describe_reserved_elasticsearch_instance_offerings"
612        ).paginate(**boto_params):
613            res.extend(page["ReservedElasticsearchInstanceOfferings"])
614        if res:
615            ret["result"] = True
616            ret["response"] = res
617    except (ParamValidationError, ClientError) as exp:
618        ret.update({"error": __utils__["boto3.get_error"](exp)["message"]})
619    return ret
620
621
622@depends("botocore", version="1.10.15")
623def describe_reserved_elasticsearch_instances(
624    reserved_elasticsearch_instance_id=None,
625    region=None,
626    keyid=None,
627    key=None,
628    profile=None,
629):
630    """
631    Returns information about reserved Elasticsearch instances for this account.
632
633    :param str reserved_elasticsearch_instance_id: The reserved instance identifier
634        filter value. Use this parameter to show only the reservation that matches
635        the specified reserved Elasticsearch instance ID.
636
637    :rtype: dict
638    :return: Dictionary with key 'result' and as value a boolean denoting success or failure.
639        Upon success, also contains a key 'reponse' with a list of information on
640        reserved instances.
641        Upon failure, also contains a key 'error' with the error message as value.
642
643    :note: Version 1.9.174 of boto3 has a bug in that reserved_elasticsearch_instance_id
644        is considered a required argument, even though the documentation says otherwise.
645
646    .. versionadded:: 3001
647
648    """
649    ret = {"result": False}
650    try:
651        conn = _get_conn(region=region, keyid=keyid, key=key, profile=profile)
652        boto_params = {
653            "ReservedElasticsearchInstanceId": reserved_elasticsearch_instance_id,
654        }
655        res = []
656        for page in conn.get_paginator(
657            "describe_reserved_elasticsearch_instances"
658        ).paginate(**boto_params):
659            res.extend(page["ReservedElasticsearchInstances"])
660        if res:
661            ret["result"] = True
662            ret["response"] = res
663    except (ParamValidationError, ClientError) as exp:
664        ret.update({"error": __utils__["boto3.get_error"](exp)["message"]})
665    return ret
666
667
668@depends("botocore", version="1.10.77")
669def get_compatible_elasticsearch_versions(
670    domain_name=None, region=None, keyid=None, key=None, profile=None
671):
672    """
673    Returns a list of upgrade compatible Elastisearch versions. You can optionally
674    pass a ``domain_name`` to get all upgrade compatible Elasticsearch versions
675    for that specific domain.
676
677    :param str domain_name: The name of an Elasticsearch domain.
678
679    :rtype: dict
680    :return: Dictionary with key 'result' and as value a boolean denoting success or failure.
681        Upon success, also contains a key 'reponse' with a list of compatible versions.
682        Upon failure, also contains a key 'error' with the error message as value.
683
684    .. versionadded:: 3001
685
686    """
687    ret = {"result": False}
688    boto_params = salt.utils.data.filter_falsey({"DomainName": domain_name})
689    try:
690        conn = _get_conn(region=region, keyid=keyid, key=key, profile=profile)
691        res = conn.get_compatible_elasticsearch_versions(**boto_params)
692        if res and "CompatibleElasticsearchVersions" in res:
693            ret["result"] = True
694            ret["response"] = res["CompatibleElasticsearchVersions"]
695    except (ParamValidationError, ClientError) as exp:
696        ret.update({"error": __utils__["boto3.get_error"](exp)["message"]})
697    return ret
698
699
700@depends("botocore", version="1.10.77")
701def get_upgrade_history(domain_name, region=None, keyid=None, key=None, profile=None):
702    """
703    Retrieves the complete history of the last 10 upgrades that were performed on the domain.
704
705    :param str domain_name: The name of an Elasticsearch domain. Domain names are
706        unique across the domains owned by an account within an AWS region. Domain
707        names start with a letter or number and can contain the following characters:
708        a-z (lowercase), 0-9, and - (hyphen).
709
710    :rtype: dict
711    :return: Dictionary with key 'result' and as value a boolean denoting success or failure.
712        Upon success, also contains a key 'reponse' with a list of upgrade histories.
713        Upon failure, also contains a key 'error' with the error message as value.
714
715    .. versionadded:: 3001
716
717    """
718    ret = {"result": False}
719    try:
720        conn = _get_conn(region=region, keyid=keyid, key=key, profile=profile)
721        boto_params = {"DomainName": domain_name}
722        res = []
723        for page in conn.get_paginator("get_upgrade_history").paginate(**boto_params):
724            res.extend(page["UpgradeHistories"])
725        if res:
726            ret["result"] = True
727            ret["response"] = res
728    except (ParamValidationError, ClientError) as exp:
729        ret.update({"error": __utils__["boto3.get_error"](exp)["message"]})
730    return ret
731
732
733@depends("botocore", version="1.10.77")
734def get_upgrade_status(domain_name, region=None, keyid=None, key=None, profile=None):
735    """
736    Retrieves the latest status of the last upgrade or upgrade eligibility check
737    that was performed on the domain.
738
739    :param str domain_name: The name of an Elasticsearch domain. Domain names are
740        unique across the domains owned by an account within an AWS region. Domain
741        names start with a letter or number and can contain the following characters:
742        a-z (lowercase), 0-9, and - (hyphen).
743
744    :rtype: dict
745    :return: Dictionary with key 'result' and as value a boolean denoting success or failure.
746        Upon success, also contains a key 'reponse' with upgrade status information.
747        Upon failure, also contains a key 'error' with the error message as value.
748
749    .. versionadded:: 3001
750
751    """
752    ret = {"result": False}
753    boto_params = {"DomainName": domain_name}
754    try:
755        conn = _get_conn(region=region, keyid=keyid, key=key, profile=profile)
756        res = conn.get_upgrade_status(**boto_params)
757        ret["result"] = True
758        ret["response"] = res
759        del res["ResponseMetadata"]
760    except (ParamValidationError, ClientError) as exp:
761        ret.update({"error": __utils__["boto3.get_error"](exp)["message"]})
762    return ret
763
764
765def list_domain_names(region=None, keyid=None, key=None, profile=None):
766    """
767    Returns the name of all Elasticsearch domains owned by the current user's account.
768
769    :rtype: dict
770    :return: Dictionary with key 'result' and as value a boolean denoting success or failure.
771        Upon success, also contains a key 'reponse' with a list of domain names.
772        Upon failure, also contains a key 'error' with the error message as value.
773
774    .. versionadded:: 3001
775
776    """
777    ret = {"result": False}
778    try:
779        conn = _get_conn(region=region, keyid=keyid, key=key, profile=profile)
780        res = conn.list_domain_names()
781        if res and "DomainNames" in res:
782            ret["result"] = True
783            ret["response"] = [item["DomainName"] for item in res["DomainNames"]]
784    except (ParamValidationError, ClientError) as exp:
785        ret.update({"error": __utils__["boto3.get_error"](exp)["message"]})
786    return ret
787
788
789@depends("botocore", version="1.5.18")
790def list_elasticsearch_instance_types(
791    elasticsearch_version,
792    domain_name=None,
793    region=None,
794    keyid=None,
795    key=None,
796    profile=None,
797):
798    """
799    List all Elasticsearch instance types that are supported for given ElasticsearchVersion.
800
801    :param str elasticsearch_version: Version of Elasticsearch for which list of
802        supported elasticsearch instance types are needed.
803    :param str domain_name: DomainName represents the name of the Domain that we
804        are trying to modify. This should be present only if we are querying for
805        list of available Elasticsearch instance types when modifying existing domain.
806
807    :rtype: dict
808    :return: Dictionary with key 'result' and as value a boolean denoting success or failure.
809        Upon success, also contains a key 'reponse' with a list of Elasticsearch instance types.
810        Upon failure, also contains a key 'error' with the error message as value.
811
812    .. versionadded:: 3001
813
814    """
815    ret = {"result": False}
816    try:
817        conn = _get_conn(region=region, keyid=keyid, key=key, profile=profile)
818        boto_params = salt.utils.data.filter_falsey(
819            {
820                "ElasticsearchVersion": str(elasticsearch_version),
821                "DomainName": domain_name,
822            }
823        )
824        res = []
825        for page in conn.get_paginator("list_elasticsearch_instance_types").paginate(
826            **boto_params
827        ):
828            res.extend(page["ElasticsearchInstanceTypes"])
829        if res:
830            ret["result"] = True
831            ret["response"] = res
832    except (ParamValidationError, ClientError) as exp:
833        ret.update({"error": __utils__["boto3.get_error"](exp)["message"]})
834    return ret
835
836
837@depends("botocore", version="1.5.18")
838def list_elasticsearch_versions(region=None, keyid=None, key=None, profile=None):
839    """
840    List all supported Elasticsearch versions.
841
842    :rtype: dict
843    :return: Dictionary with key 'result' and as value a boolean denoting success or failure.
844        Upon success, also contains a key 'reponse' with a list of Elasticsearch versions.
845        Upon failure, also contains a key 'error' with the error message as value.
846
847    .. versionadded:: 3001
848
849    """
850    ret = {"result": False}
851    try:
852        conn = _get_conn(region=region, keyid=keyid, key=key, profile=profile)
853        res = []
854        for page in conn.get_paginator("list_elasticsearch_versions").paginate():
855            res.extend(page["ElasticsearchVersions"])
856        if res:
857            ret["result"] = True
858            ret["response"] = res
859    except (ParamValidationError, ClientError) as exp:
860        ret.update({"error": __utils__["boto3.get_error"](exp)["message"]})
861    return ret
862
863
864def list_tags(
865    domain_name=None, arn=None, region=None, key=None, keyid=None, profile=None
866):
867    """
868    Returns all tags for the given Elasticsearch domain.
869
870    :rtype: dict
871    :return: Dictionary with key 'result' and as value a boolean denoting success or failure.
872        Upon success, also contains a key 'reponse' with a dict of tags.
873        Upon failure, also contains a key 'error' with the error message as value.
874
875    .. versionadded:: 3001
876
877    """
878    if not any((arn, domain_name)):
879        raise SaltInvocationError(
880            "At least one of domain_name or arn must be specified."
881        )
882    ret = {"result": False}
883    if arn is None:
884        res = describe_elasticsearch_domain(
885            domain_name=domain_name,
886            region=region,
887            key=key,
888            keyid=keyid,
889            profile=profile,
890        )
891        if "error" in res:
892            ret.update(res)
893        elif not res["result"]:
894            ret.update(
895                {
896                    "error": 'The domain with name "{}" does not exist.'.format(
897                        domain_name
898                    )
899                }
900            )
901        else:
902            arn = res["response"].get("ARN")
903    if arn:
904        try:
905            conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
906            res = conn.list_tags(ARN=arn)
907            ret["result"] = True
908            ret["response"] = {
909                item["Key"]: item["Value"] for item in res.get("TagList", [])
910            }
911        except (ParamValidationError, ClientError) as exp:
912            ret.update({"error": __utils__["boto3.get_error"](exp)["message"]})
913    return ret
914
915
916@depends("botocore", version="1.10.15")
917def purchase_reserved_elasticsearch_instance_offering(
918    reserved_elasticsearch_instance_offering_id,
919    reservation_name,
920    instance_count=None,
921    region=None,
922    keyid=None,
923    key=None,
924    profile=None,
925):
926    """
927    Allows you to purchase reserved Elasticsearch instances.
928
929    :param str reserved_elasticsearch_instance_offering_id: The ID of the reserved
930        Elasticsearch instance offering to purchase.
931    :param str reservation_name: A customer-specified identifier to track this reservation.
932    :param int instance_count: The number of Elasticsearch instances to reserve.
933
934    :rtype: dict
935    :return: Dictionary with key 'result' and as value a boolean denoting success or failure.
936        Upon success, also contains a key 'reponse' with purchase information.
937        Upon failure, also contains a key 'error' with the error message as value.
938
939    .. versionadded:: 3001
940
941    """
942    ret = {"result": False}
943    boto_params = salt.utils.data.filter_falsey(
944        {
945            "ReservedElasticsearchInstanceOfferingId": reserved_elasticsearch_instance_offering_id,
946            "ReservationName": reservation_name,
947            "InstanceCount": instance_count,
948        }
949    )
950    try:
951        conn = _get_conn(region=region, keyid=keyid, key=key, profile=profile)
952        res = conn.purchase_reserved_elasticsearch_instance_offering(**boto_params)
953        if res:
954            ret["result"] = True
955            ret["response"] = res
956    except (ParamValidationError, ClientError) as exp:
957        ret.update({"error": __utils__["boto3.get_error"](exp)["message"]})
958    return ret
959
960
961def remove_tags(
962    tag_keys,
963    domain_name=None,
964    arn=None,
965    region=None,
966    key=None,
967    keyid=None,
968    profile=None,
969):
970    """
971    Removes the specified set of tags from the specified Elasticsearch domain.
972
973    :param list tag_keys: List with tag keys you want to remove from the Elasticsearch domain.
974    :param str domain_name: The name of the Elasticsearch domain you want to remove tags from.
975    :param str arn: The ARN of the Elasticsearch domain you want to remove tags from.
976        Specifying this overrides ``domain_name``.
977
978    :rtype: dict
979    :return: Dictionary with key 'result' and as value a boolean denoting success or failure.
980        Upon failure, also contains a key 'error' with the error message as value.
981
982    .. versionadded:: 3001
983
984    CLI Example:
985
986    .. code-block:: bash
987
988        salt myminion boto3_elasticsearch.remove_tags '["foo", "bar"]' domain_name=my_domain
989    """
990    if not any((arn, domain_name)):
991        raise SaltInvocationError(
992            "At least one of domain_name or arn must be specified."
993        )
994    ret = {"result": False}
995    if arn is None:
996        res = describe_elasticsearch_domain(
997            domain_name=domain_name,
998            region=region,
999            key=key,
1000            keyid=keyid,
1001            profile=profile,
1002        )
1003        if "error" in res:
1004            ret.update(res)
1005        elif not res["result"]:
1006            ret.update(
1007                {
1008                    "error": 'The domain with name "{}" does not exist.'.format(
1009                        domain_name
1010                    )
1011                }
1012            )
1013        else:
1014            arn = res["response"].get("ARN")
1015    if arn:
1016        try:
1017            conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
1018            conn.remove_tags(ARN=arn, TagKeys=tag_keys)
1019            ret["result"] = True
1020        except (ParamValidationError, ClientError) as exp:
1021            ret.update({"error": __utils__["boto3.get_error"](exp)["message"]})
1022    return ret
1023
1024
1025@depends("botocore", version="1.12.21")
1026def start_elasticsearch_service_software_update(
1027    domain_name, region=None, keyid=None, key=None, profile=None
1028):
1029    """
1030    Schedules a service software update for an Amazon ES domain.
1031
1032    :param str domain_name: The name of the domain that you want to update to the
1033        latest service software.
1034
1035    :rtype: dict
1036    :return: Dictionary with key 'result' and as value a boolean denoting success or failure.
1037        Upon success, also contains a key 'reponse' with service software information.
1038        Upon failure, also contains a key 'error' with the error message as value.
1039
1040    .. versionadded:: 3001
1041
1042    """
1043    ret = {"result": False}
1044    boto_params = {"DomainName": domain_name}
1045    try:
1046        conn = _get_conn(region=region, keyid=keyid, key=key, profile=profile)
1047        res = conn.start_elasticsearch_service_software_update(**boto_params)
1048        if res and "ServiceSoftwareOptions" in res:
1049            ret["result"] = True
1050            ret["response"] = res["ServiceSoftwareOptions"]
1051    except (ParamValidationError, ClientError) as exp:
1052        ret.update({"error": __utils__["boto3.get_error"](exp)["message"]})
1053    return ret
1054
1055
1056def update_elasticsearch_domain_config(
1057    domain_name,
1058    elasticsearch_cluster_config=None,
1059    ebs_options=None,
1060    vpc_options=None,
1061    access_policies=None,
1062    snapshot_options=None,
1063    cognito_options=None,
1064    advanced_options=None,
1065    log_publishing_options=None,
1066    blocking=False,
1067    region=None,
1068    key=None,
1069    keyid=None,
1070    profile=None,
1071):
1072    """
1073    Modifies the cluster configuration of the specified Elasticsearch domain,
1074    for example setting the instance type and the number of instances.
1075
1076    :param str domain_name: The name of the Elasticsearch domain that you are creating.
1077        Domain names are unique across the domains owned by an account within an
1078        AWS region. Domain names must start with a letter or number and can contain
1079        the following characters: a-z (lowercase), 0-9, and - (hyphen).
1080    :param dict elasticsearch_cluster_config: Dictionary specifying the configuration
1081        options for an Elasticsearch domain. Keys (case sensitive) in here are:
1082
1083        - InstanceType (str): The instance type for an Elasticsearch cluster.
1084        - InstanceCount (int): The instance type for an Elasticsearch cluster.
1085        - DedicatedMasterEnabled (bool): Indicate whether a dedicated master
1086          node is enabled.
1087        - ZoneAwarenessEnabled (bool): Indicate whether zone awareness is enabled.
1088        - ZoneAwarenessConfig (dict): Specifies the zone awareness configuration
1089          for a domain when zone awareness is enabled.
1090          Keys (case sensitive) in here are:
1091
1092          - AvailabilityZoneCount (int): An integer value to indicate the
1093            number of availability zones for a domain when zone awareness is
1094            enabled. This should be equal to number of subnets if VPC endpoints
1095            is enabled.
1096
1097        - DedicatedMasterType (str): The instance type for a dedicated master node.
1098        - DedicatedMasterCount (int): Total number of dedicated master nodes,
1099          active and on standby, for the cluster.
1100    :param dict ebs_options: Dict specifying the options to enable or disable and
1101        specifying the type and size of EBS storage volumes.
1102        Keys (case sensitive) in here are:
1103
1104        - EBSEnabled (bool): Specifies whether EBS-based storage is enabled.
1105        - VolumeType (str): Specifies the volume type for EBS-based storage.
1106        - VolumeSize (int): Integer to specify the size of an EBS volume.
1107        - Iops (int): Specifies the IOPD for a Provisioned IOPS EBS volume (SSD).
1108    :param dict snapshot_options: Dict specifying the snapshot options.
1109        Keys (case sensitive) in here are:
1110
1111        - AutomatedSnapshotStartHour (int): Specifies the time, in UTC format,
1112          when the service takes a daily automated snapshot of the specified
1113          Elasticsearch domain. Default value is 0 hours.
1114    :param dict vpc_options: Dict with the options to specify the subnets and security
1115        groups for the VPC endpoint.
1116        Keys (case sensitive) in here are:
1117
1118        - SubnetIds (list): The list of subnets for the VPC endpoint.
1119        - SecurityGroupIds (list): The list of security groups for the VPC endpoint.
1120    :param dict cognito_options: Dict with options to specify the cognito user and
1121        identity pools for Kibana authentication.
1122        Keys (case sensitive) in here are:
1123
1124        - Enabled (bool): Specifies the option to enable Cognito for Kibana authentication.
1125        - UserPoolId (str): Specifies the Cognito user pool ID for Kibana authentication.
1126        - IdentityPoolId (str): Specifies the Cognito identity pool ID for Kibana authentication.
1127        - RoleArn (str): Specifies the role ARN that provides Elasticsearch permissions
1128          for accessing Cognito resources.
1129    :param dict advanced_options: Dict with option to allow references to indices
1130        in an HTTP request body. Must be False when configuring access to individual
1131        sub-resources. By default, the value is True.
1132        See http://docs.aws.amazon.com/elasticsearch-service/latest/developerguide\
1133        /es-createupdatedomains.html#es-createdomain-configure-advanced-options
1134        for more information.
1135    :param str/dict access_policies: Dict or JSON string with the IAM access policy.
1136    :param dict log_publishing_options: Dict with options for various type of logs.
1137        The keys denote the type of log file and can be one of the following:
1138
1139            INDEX_SLOW_LOGS, SEARCH_SLOW_LOGS, ES_APPLICATION_LOGS.
1140
1141        The value assigned to each key is a dict with the following case sensitive keys:
1142
1143        - CloudWatchLogsLogGroupArn (str): The ARN of the Cloudwatch log
1144          group to which the log needs to be published.
1145        - Enabled (bool): Specifies whether given log publishing option
1146          is enabled or not.
1147    :param bool blocking: Whether or not to wait (block) until the Elasticsearch
1148        domain has been updated.
1149
1150    :rtype: dict
1151    :return: Dictionary with key 'result' and as value a boolean denoting success or failure.
1152        Upon success, also contains a key 'reponse' with the domain configuration.
1153        Upon failure, also contains a key 'error' with the error message as value.
1154
1155    .. versionadded:: 3001
1156
1157    CLI Example:
1158
1159    .. code-block:: bash
1160
1161        salt myminion boto3_elasticsearch.update_elasticsearch_domain_config mydomain \\
1162          elasticsearch_cluster_config='{\\
1163            "InstanceType": "t2.micro.elasticsearch", \\
1164            "InstanceCount": 1, \\
1165            "DedicatedMasterEnabled": false,
1166            "ZoneAwarenessEnabled": false}' \\
1167          ebs_options='{\\
1168            "EBSEnabled": true, \\
1169            "VolumeType": "gp2", \\
1170            "VolumeSize": 10, \\
1171            "Iops": 0}' \\
1172          access_policies='{"Version": "2012-10-17", "Statement": [{\\
1173            "Effect": "Allow", "Principal": {"AWS": "*"}, "Action": "es:*", \\
1174            "Resource": "arn:aws:es:us-east-1:111111111111:domain/mydomain/*", \\
1175            "Condition": {"IpAddress": {"aws:SourceIp": ["127.0.0.1"]}}}]}' \\
1176          snapshot_options='{"AutomatedSnapshotStartHour": 0}' \\
1177          advanced_options='{"rest.action.multi.allow_explicit_index": "true"}'
1178    """
1179    ret = {"result": False}
1180    boto_kwargs = salt.utils.data.filter_falsey(
1181        {
1182            "DomainName": domain_name,
1183            "ElasticsearchClusterConfig": elasticsearch_cluster_config,
1184            "EBSOptions": ebs_options,
1185            "SnapshotOptions": snapshot_options,
1186            "VPCOptions": vpc_options,
1187            "CognitoOptions": cognito_options,
1188            "AdvancedOptions": advanced_options,
1189            "AccessPolicies": (
1190                salt.utils.json.dumps(access_policies)
1191                if isinstance(access_policies, dict)
1192                else access_policies
1193            ),
1194            "LogPublishingOptions": log_publishing_options,
1195        }
1196    )
1197    try:
1198        conn = _get_conn(region=region, keyid=keyid, key=key, profile=profile)
1199        res = conn.update_elasticsearch_domain_config(**boto_kwargs)
1200        if not res or "DomainConfig" not in res:
1201            log.warning("Domain was not updated")
1202        else:
1203            ret["result"] = True
1204            ret["response"] = res["DomainConfig"]
1205        if blocking:
1206            conn.get_waiter("ESDomainAvailable").wait(DomainName=domain_name)
1207    except (ParamValidationError, ClientError, WaiterError) as exp:
1208        ret.update({"error": __utils__["boto3.get_error"](exp)["message"]})
1209    return ret
1210
1211
1212@depends("botocore", version="1.10.77")
1213def upgrade_elasticsearch_domain(
1214    domain_name,
1215    target_version,
1216    perform_check_only=None,
1217    blocking=False,
1218    region=None,
1219    keyid=None,
1220    key=None,
1221    profile=None,
1222):
1223    """
1224    Allows you to either upgrade your domain or perform an Upgrade eligibility
1225    check to a compatible Elasticsearch version.
1226
1227    :param str domain_name: The name of an Elasticsearch domain. Domain names are
1228        unique across the domains owned by an account within an AWS region. Domain
1229        names start with a letter or number and can contain the following characters:
1230        a-z (lowercase), 0-9, and - (hyphen).
1231    :param str target_version: The version of Elasticsearch that you intend to
1232        upgrade the domain to.
1233    :param bool perform_check_only: This flag, when set to True, indicates that
1234        an Upgrade Eligibility Check needs to be performed. This will not actually
1235        perform the Upgrade.
1236    :param bool blocking: Whether or not to wait (block) until the Elasticsearch
1237        domain has been upgraded.
1238
1239    :rtype: dict
1240    :return: Dictionary with key 'result' and as value a boolean denoting success or failure.
1241        Upon success, also contains a key 'reponse' with the domain configuration.
1242        Upon failure, also contains a key 'error' with the error message as value.
1243
1244    .. versionadded:: 3001
1245
1246    CLI Example:
1247
1248    .. code-block:: bash
1249
1250        salt myminion boto3_elasticsearch.upgrade_elasticsearch_domain mydomain \\
1251        target_version='6.7' \\
1252        perform_check_only=True
1253    """
1254    ret = {"result": False}
1255    boto_params = salt.utils.data.filter_falsey(
1256        {
1257            "DomainName": domain_name,
1258            "TargetVersion": str(target_version),
1259            "PerformCheckOnly": perform_check_only,
1260        }
1261    )
1262    try:
1263        conn = _get_conn(region=region, keyid=keyid, key=key, profile=profile)
1264        res = conn.upgrade_elasticsearch_domain(**boto_params)
1265        if res:
1266            ret["result"] = True
1267            ret["response"] = res
1268        if blocking:
1269            conn.get_waiter("ESUpgradeFinished").wait(DomainName=domain_name)
1270    except (ParamValidationError, ClientError, WaiterError) as exp:
1271        ret.update({"error": __utils__["boto3.get_error"](exp)["message"]})
1272    return ret
1273
1274
1275def exists(domain_name, region=None, key=None, keyid=None, profile=None):
1276    """
1277    Given a domain name, check to see if the given domain exists.
1278
1279    :param str domain_name: The name of the domain to check.
1280
1281    :rtype: dict
1282    :return: Dictionary with key 'result' and as value a boolean denoting success or failure.
1283        Upon failure, also contains a key 'error' with the error message as value.
1284
1285    .. versionadded:: 3001
1286
1287    """
1288    ret = {"result": False}
1289    try:
1290        conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
1291        conn.describe_elasticsearch_domain(DomainName=domain_name)
1292        ret["result"] = True
1293    except (ParamValidationError, ClientError) as exp:
1294        if exp.response.get("Error", {}).get("Code") != "ResourceNotFoundException":
1295            ret.update({"error": __utils__["boto3.get_error"](exp)["message"]})
1296    return ret
1297
1298
1299def wait_for_upgrade(domain_name, region=None, keyid=None, key=None, profile=None):
1300    """
1301    Block until an upgrade-in-progress for domain ``name`` is finished.
1302
1303    :param str name: The name of the domain to wait for.
1304
1305    :rtype dict:
1306    :return: Dictionary with key 'result' and as value a boolean denoting success or failure.
1307        Upon failure, also contains a key 'error' with the error message as value.
1308
1309    .. versionadded:: 3001
1310
1311    """
1312    ret = {"result": False}
1313    try:
1314        conn = _get_conn(region=region, keyid=keyid, key=key, profile=profile)
1315        conn.get_waiter("ESUpgradeFinished").wait(DomainName=domain_name)
1316        ret["result"] = True
1317    except (ParamValidationError, ClientError, WaiterError) as exp:
1318        ret.update({"error": __utils__["boto3.get_error"](exp)["message"]})
1319    return ret
1320
1321
1322@depends("botocore", version="1.10.77")
1323def check_upgrade_eligibility(
1324    domain_name, elasticsearch_version, region=None, keyid=None, key=None, profile=None
1325):
1326    """
1327    Helper function to determine in one call if an Elasticsearch domain can be
1328    upgraded to the specified Elasticsearch version.
1329
1330    This assumes that the Elasticsearch domain is at rest at the moment this function
1331    is called. I.e. The domain is not in the process of :
1332
1333    - being created.
1334    - being updated.
1335    - another upgrade running, or a check thereof.
1336    - being deleted.
1337
1338    Behind the scenes, this does 3 things:
1339
1340    - Check if ``elasticsearch_version`` is among the compatible elasticsearch versions.
1341    - Perform a check if the Elasticsearch domain is eligible for the upgrade.
1342    - Check the result of the check and return the result as a boolean.
1343
1344    :param str name: The Elasticsearch domain name to check.
1345    :param str elasticsearch_version: The Elasticsearch version to upgrade to.
1346
1347    :rtype: dict
1348    :return: Dictionary with key 'result' and as value a boolean denoting success or failure.
1349        Upon success, also contains a key 'reponse' with boolean result of the check.
1350        Upon failure, also contains a key 'error' with the error message as value.
1351
1352    .. versionadded:: 3001
1353
1354    CLI Example:
1355
1356    .. code-block:: bash
1357
1358        salt myminion boto3_elasticsearch.check_upgrade_eligibility mydomain '6.7'
1359    """
1360    ret = {"result": False}
1361    # Check if the desired version is in the list of compatible versions
1362    res = get_compatible_elasticsearch_versions(
1363        domain_name, region=region, keyid=keyid, key=key, profile=profile
1364    )
1365    if "error" in res:
1366        return res
1367    compatible_versions = res["response"][0]["TargetVersions"]
1368    if str(elasticsearch_version) not in compatible_versions:
1369        ret["result"] = True
1370        ret["response"] = False
1371        ret["error"] = 'Desired version "{}" not in compatible versions: {}.'.format(
1372            elasticsearch_version, compatible_versions
1373        )
1374        return ret
1375    # Check if the domain is eligible to upgrade to the desired version
1376    res = upgrade_elasticsearch_domain(
1377        domain_name,
1378        elasticsearch_version,
1379        perform_check_only=True,
1380        blocking=True,
1381        region=region,
1382        keyid=keyid,
1383        key=key,
1384        profile=profile,
1385    )
1386    if "error" in res:
1387        return res
1388    res = wait_for_upgrade(
1389        domain_name, region=region, keyid=keyid, key=key, profile=profile
1390    )
1391    if "error" in res:
1392        return res
1393    res = get_upgrade_status(
1394        domain_name, region=region, keyid=keyid, key=key, profile=profile
1395    )
1396    ret["result"] = True
1397    ret["response"] = (
1398        res["response"]["UpgradeStep"] == "PRE_UPGRADE_CHECK"
1399        and res["response"]["StepStatus"] == "SUCCEEDED"
1400    )
1401    return ret
1402