1"""
2Connection module for Amazon S3 Buckets
3
4.. versionadded:: 2016.3.0
5
6:depends:
7    - boto
8    - boto3
9
10The dependencies listed above can be installed via package or pip.
11
12:configuration: This module accepts explicit Lambda credentials but can also
13    utilize IAM roles assigned to the instance through Instance Profiles.
14    Dynamic credentials are then automatically obtained from AWS API and no
15    further configuration is necessary. More Information available at:
16
17    .. code-block:: text
18
19        http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html
20
21    If IAM roles are not used you need to specify them either in a pillar or
22    in the minion's config file:
23
24    .. code-block:: yaml
25
26        s3.keyid: GKTADJGHEIQSXMKKRBJ08H
27        s3.key: askdjghsdfjkghWupUjasdflkdfklgjsdfjajkghs
28
29    A region may also be specified in the configuration:
30
31    .. code-block:: yaml
32
33        s3.region: us-east-1
34
35    If a region is not specified, the default is us-east-1.
36
37    It's also possible to specify key, keyid and region via a profile, either
38    as a passed in dict, or as a string to pull from pillars or minion config:
39
40    .. code-block:: yaml
41
42        myprofile:
43            keyid: GKTADJGHEIQSXMKKRBJ08H
44            key: askdjghsdfjkghWupUjasdflkdfklgjsdfjajkghs
45            region: us-east-1
46
47"""
48# keep lint from choking on _get_conn and _cache_id
49# pylint: disable=E0602
50#  disable complaints about perfectly valid non-assignment code
51# pylint: disable=W0106
52
53
54import logging
55
56import salt.utils.compat
57import salt.utils.json
58import salt.utils.versions
59from salt.exceptions import SaltInvocationError
60
61log = logging.getLogger(__name__)
62
63
64# pylint: disable=import-error
65try:
66    # pylint: disable=unused-import
67    import boto
68    import boto3
69
70    # pylint: enable=unused-import
71    from botocore.exceptions import ClientError
72
73    logging.getLogger("boto3").setLevel(logging.CRITICAL)
74    HAS_BOTO = True
75except ImportError:
76    HAS_BOTO = False
77# pylint: enable=import-error
78
79
80def __virtual__():
81    """
82    Only load if boto libraries exist and if boto libraries are greater than
83    a given version.
84    """
85    # the boto_lambda execution module relies on the connect_to_region() method
86    # which was added in boto 2.8.0
87    # https://github.com/boto/boto/commit/33ac26b416fbb48a60602542b4ce15dcc7029f12
88    return salt.utils.versions.check_boto_reqs(boto3_ver="1.2.1")
89
90
91def __init__(opts):
92    if HAS_BOTO:
93        __utils__["boto3.assign_funcs"](__name__, "s3")
94
95
96def exists(Bucket, region=None, key=None, keyid=None, profile=None):
97    """
98    Given a bucket name, check to see if the given bucket exists.
99
100    Returns True if the given bucket exists and returns False if the given
101    bucket does not exist.
102
103    CLI Example:
104
105    .. code-block:: bash
106
107        salt myminion boto_s3_bucket.exists mybucket
108
109    """
110
111    try:
112        conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
113        buckets = conn.head_bucket(Bucket=Bucket)
114        return {"exists": True}
115    except ClientError as e:
116        if e.response.get("Error", {}).get("Code") == "404":
117            return {"exists": False}
118        err = __utils__["boto3.get_error"](e)
119        return {"error": err}
120
121
122def create(
123    Bucket,
124    ACL=None,
125    LocationConstraint=None,
126    GrantFullControl=None,
127    GrantRead=None,
128    GrantReadACP=None,
129    GrantWrite=None,
130    GrantWriteACP=None,
131    region=None,
132    key=None,
133    keyid=None,
134    profile=None,
135):
136    """
137    Given a valid config, create an S3 Bucket.
138
139    Returns {created: true} if the bucket was created and returns
140    {created: False} if the bucket was not created.
141
142    CLI Example:
143
144    .. code-block:: bash
145
146        salt myminion boto_s3_bucket.create my_bucket \\
147                         GrantFullControl='emailaddress=example@example.com' \\
148                         GrantRead='uri="http://acs.amazonaws.com/groups/global/AllUsers"' \\
149                         GrantReadACP='emailaddress="exampl@example.com",id="2345678909876432"' \\
150                         LocationConstraint=us-west-1
151
152    """
153
154    try:
155        conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
156        kwargs = {}
157        for arg in (
158            "ACL",
159            "GrantFullControl",
160            "GrantRead",
161            "GrantReadACP",
162            "GrantWrite",
163            "GrantWriteACP",
164        ):
165            if locals()[arg] is not None:
166                kwargs[arg] = str(locals()[arg])
167        if LocationConstraint:
168            kwargs["CreateBucketConfiguration"] = {
169                "LocationConstraint": LocationConstraint
170            }
171        location = conn.create_bucket(Bucket=Bucket, **kwargs)
172        conn.get_waiter("bucket_exists").wait(Bucket=Bucket)
173        if location:
174            log.info(
175                "The newly created bucket name is located at %s", location["Location"]
176            )
177
178            return {"created": True, "name": Bucket, "Location": location["Location"]}
179        else:
180            log.warning("Bucket was not created")
181            return {"created": False}
182    except ClientError as e:
183        return {"created": False, "error": __utils__["boto3.get_error"](e)}
184
185
186def delete(
187    Bucket,
188    MFA=None,
189    RequestPayer=None,
190    Force=False,
191    region=None,
192    key=None,
193    keyid=None,
194    profile=None,
195):
196    """
197    Given a bucket name, delete it, optionally emptying it first.
198
199    Returns {deleted: true} if the bucket was deleted and returns
200    {deleted: false} if the bucket was not deleted.
201
202    CLI Example:
203
204    .. code-block:: bash
205
206        salt myminion boto_s3_bucket.delete mybucket
207
208    """
209
210    try:
211        conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
212        if Force:
213            empty(
214                Bucket,
215                MFA=MFA,
216                RequestPayer=RequestPayer,
217                region=region,
218                key=key,
219                keyid=keyid,
220                profile=profile,
221            )
222        conn.delete_bucket(Bucket=Bucket)
223        return {"deleted": True}
224    except ClientError as e:
225        return {"deleted": False, "error": __utils__["boto3.get_error"](e)}
226
227
228def delete_objects(
229    Bucket,
230    Delete,
231    MFA=None,
232    RequestPayer=None,
233    region=None,
234    key=None,
235    keyid=None,
236    profile=None,
237):
238    """
239    Delete objects in a given S3 bucket.
240
241    Returns {deleted: true} if all objects were deleted
242    and {deleted: false, failed: [key, ...]} otherwise
243
244    CLI Example:
245
246    .. code-block:: bash
247
248        salt myminion boto_s3_bucket.delete_objects mybucket '{Objects: [Key: myobject]}'
249
250    """
251
252    if isinstance(Delete, str):
253        Delete = salt.utils.json.loads(Delete)
254    if not isinstance(Delete, dict):
255        raise SaltInvocationError("Malformed Delete request.")
256    if "Objects" not in Delete:
257        raise SaltInvocationError("Malformed Delete request.")
258
259    failed = []
260    objs = Delete["Objects"]
261    for i in range(0, len(objs), 1000):
262        chunk = objs[i : i + 1000]
263        subset = {"Objects": chunk, "Quiet": True}
264        try:
265            args = {"Bucket": Bucket}
266            args.update({"MFA": MFA}) if MFA else None
267            args.update({"RequestPayer": RequestPayer}) if RequestPayer else None
268            args.update({"Delete": subset})
269            conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
270            ret = conn.delete_objects(**args)
271            failed += ret.get("Errors", [])
272        except ClientError as e:
273            return {"deleted": False, "error": __utils__["boto3.get_error"](e)}
274
275    if failed:
276        return {"deleted": False, "failed": failed}
277    else:
278        return {"deleted": True}
279
280
281def describe(Bucket, region=None, key=None, keyid=None, profile=None):
282    """
283    Given a bucket name describe its properties.
284
285    Returns a dictionary of interesting properties.
286
287    CLI Example:
288
289    .. code-block:: bash
290
291        salt myminion boto_s3_bucket.describe mybucket
292
293    """
294
295    try:
296        conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
297        result = {}
298        conn_dict = {
299            "ACL": conn.get_bucket_acl,
300            "CORS": conn.get_bucket_cors,
301            "LifecycleConfiguration": conn.get_bucket_lifecycle_configuration,
302            "Location": conn.get_bucket_location,
303            "Logging": conn.get_bucket_logging,
304            "NotificationConfiguration": conn.get_bucket_notification_configuration,
305            "Policy": conn.get_bucket_policy,
306            "Replication": conn.get_bucket_replication,
307            "RequestPayment": conn.get_bucket_request_payment,
308            "Versioning": conn.get_bucket_versioning,
309            "Website": conn.get_bucket_website,
310        }
311
312        for key, query in conn_dict.items():
313            try:
314                data = query(Bucket=Bucket)
315            except ClientError as e:
316                if e.response.get("Error", {}).get("Code") in (
317                    "NoSuchLifecycleConfiguration",
318                    "NoSuchCORSConfiguration",
319                    "NoSuchBucketPolicy",
320                    "NoSuchWebsiteConfiguration",
321                    "ReplicationConfigurationNotFoundError",
322                    "NoSuchTagSet",
323                ):
324                    continue
325                raise
326            if "ResponseMetadata" in data:
327                del data["ResponseMetadata"]
328            result[key] = data
329
330        tags = {}
331        try:
332            data = conn.get_bucket_tagging(Bucket=Bucket)
333            for tagdef in data.get("TagSet"):
334                tags[tagdef.get("Key")] = tagdef.get("Value")
335        except ClientError as e:
336            if not e.response.get("Error", {}).get("Code") == "NoSuchTagSet":
337                raise
338        if tags:
339            result["Tagging"] = tags
340        return {"bucket": result}
341    except ClientError as e:
342        err = __utils__["boto3.get_error"](e)
343        if e.response.get("Error", {}).get("Code") == "NoSuchBucket":
344            return {"bucket": None}
345        return {"error": __utils__["boto3.get_error"](e)}
346
347
348def empty(
349    Bucket, MFA=None, RequestPayer=None, region=None, key=None, keyid=None, profile=None
350):
351    """
352    Delete all objects in a given S3 bucket.
353
354    Returns {deleted: true} if all objects were deleted
355    and {deleted: false, failed: [key, ...]} otherwise
356
357    CLI Example:
358
359    .. code-block:: bash
360
361        salt myminion boto_s3_bucket.empty mybucket
362
363    """
364
365    stuff = list_object_versions(
366        Bucket, region=region, key=key, keyid=keyid, profile=profile
367    )
368    Delete = {}
369    Delete["Objects"] = [
370        {"Key": v["Key"], "VersionId": v["VersionId"]}
371        for v in stuff.get("Versions", [])
372    ]
373    Delete["Objects"] += [
374        {"Key": v["Key"], "VersionId": v["VersionId"]}
375        for v in stuff.get("DeleteMarkers", [])
376    ]
377    if Delete["Objects"]:
378        ret = delete_objects(
379            Bucket,
380            Delete,
381            MFA=MFA,
382            RequestPayer=RequestPayer,
383            region=region,
384            key=key,
385            keyid=keyid,
386            profile=profile,
387        )
388        failed = ret.get("failed", [])
389        if failed:
390            return {"deleted": False, "failed": ret[failed]}
391    return {"deleted": True}
392
393
394def list(region=None, key=None, keyid=None, profile=None):
395    """
396    List all buckets owned by the authenticated sender of the request.
397
398    Returns list of buckets
399
400    CLI Example:
401
402    .. code-block:: yaml
403
404        Owner: {...}
405        Buckets:
406          - {...}
407          - {...}
408
409    """
410    try:
411        conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
412        buckets = conn.list_buckets()
413        if not bool(buckets.get("Buckets")):
414            log.warning("No buckets found")
415        if "ResponseMetadata" in buckets:
416            del buckets["ResponseMetadata"]
417        return buckets
418    except ClientError as e:
419        return {"error": __utils__["boto3.get_error"](e)}
420
421
422def list_object_versions(
423    Bucket,
424    Delimiter=None,
425    EncodingType=None,
426    Prefix=None,
427    region=None,
428    key=None,
429    keyid=None,
430    profile=None,
431):
432    """
433    List objects in a given S3 bucket.
434
435    Returns a list of objects.
436
437    CLI Example:
438
439    .. code-block:: bash
440
441        salt myminion boto_s3_bucket.list_object_versions mybucket
442
443    """
444
445    try:
446        Versions = []
447        DeleteMarkers = []
448        args = {"Bucket": Bucket}
449        args.update({"Delimiter": Delimiter}) if Delimiter else None
450        args.update({"EncodingType": EncodingType}) if Delimiter else None
451        args.update({"Prefix": Prefix}) if Prefix else None
452        conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
453        IsTruncated = True
454        while IsTruncated:
455            ret = conn.list_object_versions(**args)
456            IsTruncated = ret.get("IsTruncated", False)
457            if IsTruncated in ("True", "true", True):
458                args["KeyMarker"] = ret["NextKeyMarker"]
459                args["VersionIdMarker"] = ret["NextVersionIdMarker"]
460            Versions += ret.get("Versions", [])
461            DeleteMarkers += ret.get("DeleteMarkers", [])
462        return {"Versions": Versions, "DeleteMarkers": DeleteMarkers}
463    except ClientError as e:
464        return {"error": __utils__["boto3.get_error"](e)}
465
466
467def list_objects(
468    Bucket,
469    Delimiter=None,
470    EncodingType=None,
471    Prefix=None,
472    FetchOwner=False,
473    StartAfter=None,
474    region=None,
475    key=None,
476    keyid=None,
477    profile=None,
478):
479    """
480    List objects in a given S3 bucket.
481
482    Returns a list of objects.
483
484    CLI Example:
485
486    .. code-block:: bash
487
488        salt myminion boto_s3_bucket.list_objects mybucket
489
490    """
491
492    try:
493        Contents = []
494        args = {"Bucket": Bucket, "FetchOwner": FetchOwner}
495        args.update({"Delimiter": Delimiter}) if Delimiter else None
496        args.update({"EncodingType": EncodingType}) if Delimiter else None
497        args.update({"Prefix": Prefix}) if Prefix else None
498        args.update({"StartAfter": StartAfter}) if StartAfter else None
499        conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
500        IsTruncated = True
501        while IsTruncated:
502            ret = conn.list_objects_v2(**args)
503            IsTruncated = ret.get("IsTruncated", False)
504            if IsTruncated in ("True", "true", True):
505                args["ContinuationToken"] = ret["NextContinuationToken"]
506            Contents += ret.get("Contents", [])
507        return {"Contents": Contents}
508    except ClientError as e:
509        return {"error": __utils__["boto3.get_error"](e)}
510
511
512def put_acl(
513    Bucket,
514    ACL=None,
515    AccessControlPolicy=None,
516    GrantFullControl=None,
517    GrantRead=None,
518    GrantReadACP=None,
519    GrantWrite=None,
520    GrantWriteACP=None,
521    region=None,
522    key=None,
523    keyid=None,
524    profile=None,
525):
526    """
527    Given a valid config, update the ACL for a bucket.
528
529    Returns {updated: true} if the ACL was updated and returns
530    {updated: False} if the ACL was not updated.
531
532    CLI Example:
533
534    .. code-block:: bash
535
536        salt myminion boto_s3_bucket.put_acl my_bucket 'public' \\
537                         GrantFullControl='emailaddress=example@example.com' \\
538                         GrantRead='uri="http://acs.amazonaws.com/groups/global/AllUsers"' \\
539                         GrantReadACP='emailaddress="exampl@example.com",id="2345678909876432"'
540
541    """
542
543    try:
544        conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
545        kwargs = {}
546        if AccessControlPolicy is not None:
547            if isinstance(AccessControlPolicy, str):
548                AccessControlPolicy = salt.utils.json.loads(AccessControlPolicy)
549            kwargs["AccessControlPolicy"] = AccessControlPolicy
550        for arg in (
551            "ACL",
552            "GrantFullControl",
553            "GrantRead",
554            "GrantReadACP",
555            "GrantWrite",
556            "GrantWriteACP",
557        ):
558            if locals()[arg] is not None:
559                kwargs[arg] = str(locals()[arg])
560        conn.put_bucket_acl(Bucket=Bucket, **kwargs)
561        return {"updated": True, "name": Bucket}
562    except ClientError as e:
563        return {"updated": False, "error": __utils__["boto3.get_error"](e)}
564
565
566def put_cors(Bucket, CORSRules, region=None, key=None, keyid=None, profile=None):
567    """
568    Given a valid config, update the CORS rules for a bucket.
569
570    Returns {updated: true} if CORS was updated and returns
571    {updated: False} if CORS was not updated.
572
573    CLI Example:
574
575    .. code-block:: bash
576
577        salt myminion boto_s3_bucket.put_cors my_bucket '[{\\
578              "AllowedHeaders":[],\\
579              "AllowedMethods":["GET"],\\
580              "AllowedOrigins":["*"],\\
581              "ExposeHeaders":[],\\
582              "MaxAgeSeconds":123,\\
583        }]'
584
585    """
586
587    try:
588        conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
589        if CORSRules is not None and isinstance(CORSRules, str):
590            CORSRules = salt.utils.json.loads(CORSRules)
591        conn.put_bucket_cors(Bucket=Bucket, CORSConfiguration={"CORSRules": CORSRules})
592        return {"updated": True, "name": Bucket}
593    except ClientError as e:
594        return {"updated": False, "error": __utils__["boto3.get_error"](e)}
595
596
597def put_lifecycle_configuration(
598    Bucket, Rules, region=None, key=None, keyid=None, profile=None
599):
600    """
601    Given a valid config, update the Lifecycle rules for a bucket.
602
603    Returns {updated: true} if Lifecycle was updated and returns
604    {updated: False} if Lifecycle was not updated.
605
606    CLI Example:
607
608    .. code-block:: bash
609
610        salt myminion boto_s3_bucket.put_lifecycle_configuration my_bucket '[{\\
611              "Expiration": {...},\\
612              "ID": "idstring",\\
613              "Prefix": "prefixstring",\\
614              "Status": "enabled",\\
615              "Transitions": [{...},],\\
616              "NoncurrentVersionTransitions": [{...},],\\
617              "NoncurrentVersionExpiration": {...},\\
618        }]'
619
620    """
621
622    try:
623        conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
624        if Rules is not None and isinstance(Rules, str):
625            Rules = salt.utils.json.loads(Rules)
626        conn.put_bucket_lifecycle_configuration(
627            Bucket=Bucket, LifecycleConfiguration={"Rules": Rules}
628        )
629        return {"updated": True, "name": Bucket}
630    except ClientError as e:
631        return {"updated": False, "error": __utils__["boto3.get_error"](e)}
632
633
634def put_logging(
635    Bucket,
636    TargetBucket=None,
637    TargetPrefix=None,
638    TargetGrants=None,
639    region=None,
640    key=None,
641    keyid=None,
642    profile=None,
643):
644    """
645    Given a valid config, update the logging parameters for a bucket.
646
647    Returns {updated: true} if parameters were updated and returns
648    {updated: False} if parameters were not updated.
649
650    CLI Example:
651
652    .. code-block:: bash
653
654        salt myminion boto_s3_bucket.put_logging my_bucket log_bucket '[{...}]' prefix
655
656    """
657
658    try:
659        conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
660        logstate = {}
661        targets = {
662            "TargetBucket": TargetBucket,
663            "TargetGrants": TargetGrants,
664            "TargetPrefix": TargetPrefix,
665        }
666        for key, val in targets.items():
667            if val is not None:
668                logstate[key] = val
669        if logstate:
670            logstatus = {"LoggingEnabled": logstate}
671        else:
672            logstatus = {}
673        if TargetGrants is not None and isinstance(TargetGrants, str):
674            TargetGrants = salt.utils.json.loads(TargetGrants)
675        conn.put_bucket_logging(Bucket=Bucket, BucketLoggingStatus=logstatus)
676        return {"updated": True, "name": Bucket}
677    except ClientError as e:
678        return {"updated": False, "error": __utils__["boto3.get_error"](e)}
679
680
681def put_notification_configuration(
682    Bucket,
683    TopicConfigurations=None,
684    QueueConfigurations=None,
685    LambdaFunctionConfigurations=None,
686    region=None,
687    key=None,
688    keyid=None,
689    profile=None,
690):
691    """
692    Given a valid config, update the notification parameters for a bucket.
693
694    Returns {updated: true} if parameters were updated and returns
695    {updated: False} if parameters were not updated.
696
697    CLI Example:
698
699    .. code-block:: bash
700
701        salt myminion boto_s3_bucket.put_notification_configuration my_bucket
702                [{...}] \\
703                [{...}] \\
704                [{...}]
705
706    """
707
708    try:
709        conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
710        if TopicConfigurations is None:
711            TopicConfigurations = []
712        elif isinstance(TopicConfigurations, str):
713            TopicConfigurations = salt.utils.json.loads(TopicConfigurations)
714        if QueueConfigurations is None:
715            QueueConfigurations = []
716        elif isinstance(QueueConfigurations, str):
717            QueueConfigurations = salt.utils.json.loads(QueueConfigurations)
718        if LambdaFunctionConfigurations is None:
719            LambdaFunctionConfigurations = []
720        elif isinstance(LambdaFunctionConfigurations, str):
721            LambdaFunctionConfigurations = salt.utils.json.loads(
722                LambdaFunctionConfigurations
723            )
724        # TODO allow the user to use simple names & substitute ARNs for those names
725        conn.put_bucket_notification_configuration(
726            Bucket=Bucket,
727            NotificationConfiguration={
728                "TopicConfigurations": TopicConfigurations,
729                "QueueConfigurations": QueueConfigurations,
730                "LambdaFunctionConfigurations": LambdaFunctionConfigurations,
731            },
732        )
733        return {"updated": True, "name": Bucket}
734    except ClientError as e:
735        return {"updated": False, "error": __utils__["boto3.get_error"](e)}
736
737
738def put_policy(Bucket, Policy, region=None, key=None, keyid=None, profile=None):
739    """
740    Given a valid config, update the policy for a bucket.
741
742    Returns {updated: true} if policy was updated and returns
743    {updated: False} if policy was not updated.
744
745    CLI Example:
746
747    .. code-block:: bash
748
749        salt myminion boto_s3_bucket.put_policy my_bucket {...}
750
751    """
752
753    try:
754        conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
755        if Policy is None:
756            Policy = "{}"
757        elif not isinstance(Policy, str):
758            Policy = salt.utils.json.dumps(Policy)
759        conn.put_bucket_policy(Bucket=Bucket, Policy=Policy)
760        return {"updated": True, "name": Bucket}
761    except ClientError as e:
762        return {"updated": False, "error": __utils__["boto3.get_error"](e)}
763
764
765def _get_role_arn(name, region=None, key=None, keyid=None, profile=None):
766    if name.startswith("arn:aws:iam:"):
767        return name
768
769    account_id = __salt__["boto_iam.get_account_id"](
770        region=region, key=key, keyid=keyid, profile=profile
771    )
772    if profile and "region" in profile:
773        region = profile["region"]
774    if region is None:
775        region = "us-east-1"
776    return "arn:aws:iam::{}:role/{}".format(account_id, name)
777
778
779def put_replication(
780    Bucket, Role, Rules, region=None, key=None, keyid=None, profile=None
781):
782    """
783    Given a valid config, update the replication configuration for a bucket.
784
785    Returns {updated: true} if replication configuration was updated and returns
786    {updated: False} if replication configuration was not updated.
787
788    CLI Example:
789
790    .. code-block:: bash
791
792        salt myminion boto_s3_bucket.put_replication my_bucket my_role [...]
793
794    """
795
796    try:
797        conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
798        Role = _get_role_arn(
799            name=Role, region=region, key=key, keyid=keyid, profile=profile
800        )
801        if Rules is None:
802            Rules = []
803        elif isinstance(Rules, str):
804            Rules = salt.utils.json.loads(Rules)
805        conn.put_bucket_replication(
806            Bucket=Bucket, ReplicationConfiguration={"Role": Role, "Rules": Rules}
807        )
808        return {"updated": True, "name": Bucket}
809    except ClientError as e:
810        return {"updated": False, "error": __utils__["boto3.get_error"](e)}
811
812
813def put_request_payment(Bucket, Payer, region=None, key=None, keyid=None, profile=None):
814    """
815    Given a valid config, update the request payment configuration for a bucket.
816
817    Returns {updated: true} if request payment configuration was updated and returns
818    {updated: False} if request payment configuration was not updated.
819
820    CLI Example:
821
822    .. code-block:: bash
823
824        salt myminion boto_s3_bucket.put_request_payment my_bucket Requester
825
826    """
827
828    try:
829        conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
830        conn.put_bucket_request_payment(
831            Bucket=Bucket, RequestPaymentConfiguration={"Payer": Payer}
832        )
833        return {"updated": True, "name": Bucket}
834    except ClientError as e:
835        return {"updated": False, "error": __utils__["boto3.get_error"](e)}
836
837
838def put_tagging(Bucket, region=None, key=None, keyid=None, profile=None, **kwargs):
839    """
840    Given a valid config, update the tags for a bucket.
841
842    Returns {updated: true} if tags were updated and returns
843    {updated: False} if tags were not updated.
844
845    CLI Example:
846
847    .. code-block:: bash
848
849        salt myminion boto_s3_bucket.put_tagging my_bucket my_role [...]
850
851    """
852
853    try:
854        conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
855        tagslist = []
856        for k, v in kwargs.items():
857            if str(k).startswith("__"):
858                continue
859            tagslist.append({"Key": str(k), "Value": str(v)})
860        conn.put_bucket_tagging(Bucket=Bucket, Tagging={"TagSet": tagslist})
861        return {"updated": True, "name": Bucket}
862    except ClientError as e:
863        return {"updated": False, "error": __utils__["boto3.get_error"](e)}
864
865
866def put_versioning(
867    Bucket,
868    Status,
869    MFADelete=None,
870    MFA=None,
871    region=None,
872    key=None,
873    keyid=None,
874    profile=None,
875):
876    """
877    Given a valid config, update the versioning configuration for a bucket.
878
879    Returns {updated: true} if versioning configuration was updated and returns
880    {updated: False} if versioning configuration was not updated.
881
882    CLI Example:
883
884    .. code-block:: bash
885
886        salt myminion boto_s3_bucket.put_versioning my_bucket Enabled
887
888    """
889
890    try:
891        conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
892        VersioningConfiguration = {"Status": Status}
893        if MFADelete is not None:
894            VersioningConfiguration["MFADelete"] = MFADelete
895        kwargs = {}
896        if MFA is not None:
897            kwargs["MFA"] = MFA
898        conn.put_bucket_versioning(
899            Bucket=Bucket, VersioningConfiguration=VersioningConfiguration, **kwargs
900        )
901        return {"updated": True, "name": Bucket}
902    except ClientError as e:
903        return {"updated": False, "error": __utils__["boto3.get_error"](e)}
904
905
906def put_website(
907    Bucket,
908    ErrorDocument=None,
909    IndexDocument=None,
910    RedirectAllRequestsTo=None,
911    RoutingRules=None,
912    region=None,
913    key=None,
914    keyid=None,
915    profile=None,
916):
917    """
918    Given a valid config, update the website configuration for a bucket.
919
920    Returns {updated: true} if website configuration was updated and returns
921    {updated: False} if website configuration was not updated.
922
923    CLI Example:
924
925    .. code-block:: bash
926
927        salt myminion boto_s3_bucket.put_website my_bucket IndexDocument='{"Suffix":"index.html"}'
928
929    """
930
931    try:
932        conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
933        WebsiteConfiguration = {}
934        for key in (
935            "ErrorDocument",
936            "IndexDocument",
937            "RedirectAllRequestsTo",
938            "RoutingRules",
939        ):
940            val = locals()[key]
941            if val is not None:
942                if isinstance(val, str):
943                    WebsiteConfiguration[key] = salt.utils.json.loads(val)
944                else:
945                    WebsiteConfiguration[key] = val
946        conn.put_bucket_website(
947            Bucket=Bucket, WebsiteConfiguration=WebsiteConfiguration
948        )
949        return {"updated": True, "name": Bucket}
950    except ClientError as e:
951        return {"updated": False, "error": __utils__["boto3.get_error"](e)}
952
953
954def delete_cors(Bucket, region=None, key=None, keyid=None, profile=None):
955    """
956    Delete the CORS configuration for the given bucket
957
958    Returns {deleted: true} if CORS was deleted and returns
959    {deleted: False} if CORS was not deleted.
960
961    CLI Example:
962
963    .. code-block:: bash
964
965        salt myminion boto_s3_bucket.delete_cors my_bucket
966
967    """
968
969    try:
970        conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
971        conn.delete_bucket_cors(Bucket=Bucket)
972        return {"deleted": True, "name": Bucket}
973    except ClientError as e:
974        return {"deleted": False, "error": __utils__["boto3.get_error"](e)}
975
976
977def delete_lifecycle_configuration(
978    Bucket, region=None, key=None, keyid=None, profile=None
979):
980    """
981    Delete the lifecycle configuration for the given bucket
982
983    Returns {deleted: true} if Lifecycle was deleted and returns
984    {deleted: False} if Lifecycle was not deleted.
985
986    CLI Example:
987
988    .. code-block:: bash
989
990        salt myminion boto_s3_bucket.delete_lifecycle_configuration my_bucket
991
992    """
993
994    try:
995        conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
996        conn.delete_bucket_lifecycle(Bucket=Bucket)
997        return {"deleted": True, "name": Bucket}
998    except ClientError as e:
999        return {"deleted": False, "error": __utils__["boto3.get_error"](e)}
1000
1001
1002def delete_policy(Bucket, region=None, key=None, keyid=None, profile=None):
1003    """
1004    Delete the policy from the given bucket
1005
1006    Returns {deleted: true} if policy was deleted and returns
1007    {deleted: False} if policy was not deleted.
1008
1009    CLI Example:
1010
1011    .. code-block:: bash
1012
1013        salt myminion boto_s3_bucket.delete_policy my_bucket
1014
1015    """
1016
1017    try:
1018        conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
1019        conn.delete_bucket_policy(Bucket=Bucket)
1020        return {"deleted": True, "name": Bucket}
1021    except ClientError as e:
1022        return {"deleted": False, "error": __utils__["boto3.get_error"](e)}
1023
1024
1025def delete_replication(Bucket, region=None, key=None, keyid=None, profile=None):
1026    """
1027    Delete the replication config from the given bucket
1028
1029    Returns {deleted: true} if replication configuration was deleted and returns
1030    {deleted: False} if replication configuration was not deleted.
1031
1032    CLI Example:
1033
1034    .. code-block:: bash
1035
1036        salt myminion boto_s3_bucket.delete_replication my_bucket
1037
1038    """
1039
1040    try:
1041        conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
1042        conn.delete_bucket_replication(Bucket=Bucket)
1043        return {"deleted": True, "name": Bucket}
1044    except ClientError as e:
1045        return {"deleted": False, "error": __utils__["boto3.get_error"](e)}
1046
1047
1048def delete_tagging(Bucket, region=None, key=None, keyid=None, profile=None):
1049    """
1050    Delete the tags from the given bucket
1051
1052    Returns {deleted: true} if tags were deleted and returns
1053    {deleted: False} if tags were not deleted.
1054
1055    CLI Example:
1056
1057    .. code-block:: bash
1058
1059        salt myminion boto_s3_bucket.delete_tagging my_bucket
1060
1061    """
1062
1063    try:
1064        conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
1065        conn.delete_bucket_tagging(Bucket=Bucket)
1066        return {"deleted": True, "name": Bucket}
1067    except ClientError as e:
1068        return {"deleted": False, "error": __utils__["boto3.get_error"](e)}
1069
1070
1071def delete_website(Bucket, region=None, key=None, keyid=None, profile=None):
1072    """
1073    Remove the website configuration from the given bucket
1074
1075    Returns {deleted: true} if website configuration was deleted and returns
1076    {deleted: False} if website configuration was not deleted.
1077
1078    CLI Example:
1079
1080    .. code-block:: bash
1081
1082        salt myminion boto_s3_bucket.delete_website my_bucket
1083
1084    """
1085
1086    try:
1087        conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
1088        conn.delete_bucket_website(Bucket=Bucket)
1089        return {"deleted": True, "name": Bucket}
1090    except ClientError as e:
1091        return {"deleted": False, "error": __utils__["boto3.get_error"](e)}
1092