File size: 7,933 Bytes
105b369
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
from typing import Optional, Any, Dict, List
from typing_extensions import Literal

from phi.aws.api_client import AwsApiClient
from phi.aws.resource.base import AwsResource
from phi.aws.resource.s3.object import S3Object
from phi.cli.console import print_info
from phi.utils.log import logger


class S3Bucket(AwsResource):
    """
    Reference:
    - https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html#service-resource
    """

    resource_type: str = "s3"
    service_name: str = "s3"

    # Name of the bucket
    name: str
    # The canned ACL to apply to the bucket.
    acl: Optional[Literal["private", "public-read", "public-read-write", "authenticated-read"]] = None
    grant_full_control: Optional[str] = None
    grant_read: Optional[str] = None
    grant_read_ACP: Optional[str] = None
    grant_write: Optional[str] = None
    grant_write_ACP: Optional[str] = None
    object_lock_enabled_for_bucket: Optional[bool] = None
    object_ownership: Optional[Literal["BucketOwnerPreferred", "ObjectWriter", "BucketOwnerEnforced"]] = None

    @property
    def uri(self) -> str:
        """Returns the URI of the s3.Bucket

        Returns:
            str: The URI of the s3.Bucket
        """
        return f"s3://{self.name}"

    def get_resource(self, aws_client: Optional[AwsApiClient] = None) -> Optional[Any]:
        """Returns the s3.Bucket

        Args:
            aws_client: The AwsApiClient for the current cluster
        """
        client: AwsApiClient = aws_client or self.get_aws_client()
        service_resource = self.get_service_resource(client)
        return service_resource.Bucket(name=self.name)

    def _create(self, aws_client: AwsApiClient) -> bool:
        """Creates the s3.Bucket

        Args:
            aws_client: The AwsApiClient for the current cluster
        """
        print_info(f"Creating {self.get_resource_type()}: {self.get_resource_name()}")

        # Step 1: Build bucket configuration
        # Bucket names are GLOBALLY unique!
        # AWS will give you the IllegalLocationConstraintException if you collide
        # with an already existing bucket if you've specified a region different than
        # the region of the already existing bucket. If you happen to guess the correct region of the
        # existing bucket it will give you the BucketAlreadyExists exception.
        bucket_configuration = None
        if aws_client.aws_region is not None and aws_client.aws_region != "us-east-1":
            bucket_configuration = {"LocationConstraint": aws_client.aws_region}

        # create a dict of args which are not null, otherwise aws type validation fails
        not_null_args: Dict[str, Any] = {}
        if bucket_configuration:
            not_null_args["CreateBucketConfiguration"] = bucket_configuration
        if self.acl:
            not_null_args["ACL"] = self.acl
        if self.grant_full_control:
            not_null_args["GrantFullControl"] = self.grant_full_control
        if self.grant_read:
            not_null_args["GrantRead"] = self.grant_read
        if self.grant_read_ACP:
            not_null_args["GrantReadACP"] = self.grant_read_ACP
        if self.grant_write:
            not_null_args["GrantWrite"] = self.grant_write
        if self.grant_write_ACP:
            not_null_args["GrantWriteACP"] = self.grant_write_ACP
        if self.object_lock_enabled_for_bucket:
            not_null_args["ObjectLockEnabledForBucket"] = self.object_lock_enabled_for_bucket
        if self.object_ownership:
            not_null_args["ObjectOwnership"] = self.object_ownership

        # Step 2: Create Bucket
        service_client = self.get_service_client(aws_client)
        try:
            response = service_client.create_bucket(
                Bucket=self.name,
                **not_null_args,
            )
            logger.debug(f"Response: {response}")
            bucket_location = response.get("Location")
            if bucket_location is not None:
                logger.debug(f"Bucket created: {bucket_location}")
                self.active_resource = response
                return True
        except Exception as e:
            logger.error(f"{self.get_resource_type()} could not be created.")
            logger.error(e)
        return False

    def post_create(self, aws_client: AwsApiClient) -> bool:
        # Wait for Bucket to be created
        if self.wait_for_create:
            try:
                print_info(f"Waiting for {self.get_resource_type()} to be created.")
                waiter = self.get_service_client(aws_client).get_waiter("bucket_exists")
                waiter.wait(
                    Bucket=self.name,
                    WaiterConfig={
                        "Delay": self.waiter_delay,
                        "MaxAttempts": self.waiter_max_attempts,
                    },
                )
            except Exception as e:
                logger.error("Waiter failed.")
                logger.error(e)
        return True

    def _read(self, aws_client: AwsApiClient) -> Optional[Any]:
        """Returns the s3.Bucket

        Args:
            aws_client: The AwsApiClient for the current cluster
        """
        logger.debug(f"Reading {self.get_resource_type()}: {self.get_resource_name()}")

        from botocore.exceptions import ClientError

        try:
            service_resource = self.get_service_resource(aws_client)
            bucket = service_resource.Bucket(name=self.name)
            bucket.load()
            creation_date = bucket.creation_date
            logger.debug(f"Bucket creation_date: {creation_date}")
            if creation_date is not None:
                logger.debug(f"Bucket found: {bucket.name}")
                self.active_resource = {
                    "name": bucket.name,
                    "creation_date": creation_date,
                }
        except ClientError as ce:
            logger.debug(f"ClientError: {ce}")
        except Exception as e:
            logger.error(f"Error reading {self.get_resource_type()}.")
            logger.error(e)
        return self.active_resource

    def _delete(self, aws_client: AwsApiClient) -> bool:
        """Deletes the s3.Bucket

        Args:
            aws_client: The AwsApiClient for the current cluster
        """
        print_info(f"Deleting {self.get_resource_type()}: {self.get_resource_name()}")

        service_client = self.get_service_client(aws_client)
        self.active_resource = None
        try:
            response = service_client.delete_bucket(Bucket=self.name)
            logger.debug(f"Response: {response}")
            return True
        except Exception as e:
            logger.error(f"{self.get_resource_type()} could not be deleted.")
            logger.error("Please try again or delete resources manually.")
            logger.error(e)
        return False

    def get_objects(self, aws_client: Optional[AwsApiClient] = None, prefix: Optional[str] = None) -> List[Any]:
        """Returns a list of s3.Object objects for the s3.Bucket

        Args:
            aws_client: The AwsApiClient for the current cluster
            prefix: Prefix to filter objects by
        """
        bucket = self.get_resource(aws_client)
        if bucket is None:
            logger.warning(f"Could not get bucket: {self.name}")
            return []

        logger.debug(f"Getting objects for bucket: {bucket.name}")
        # Get all objects in bucket
        object_summaries = bucket.objects.all()
        all_objects: List[S3Object] = []
        for object_summary in object_summaries:
            if prefix is not None and not object_summary.key.startswith(prefix):
                continue
            all_objects.append(
                S3Object(
                    bucket_name=bucket.name,
                    name=object_summary.key,
                )
            )
        return all_objects