We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Use DependencyInstaller to sync maven and build dependencies in Integtests


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `bundle-workflow/src/manifests/build_manifest.py`
Content:
```
1 # SPDX-License-Identifier: Apache-2.0
2 #
3 # The OpenSearch Contributors require contributions made to
4 # this file be licensed under the Apache-2.0 license or a
5 # compatible open source license.
6 
7 import os
8 
9 from aws.s3_bucket import S3Bucket
10 from manifests.manifest import Manifest
11 
12 """
13 A BuildManifest is an immutable view of the outputs from a build step
14 The manifest contains information about the product that was built (in the `build` section),
15 and the components that made up the build in the `components` section.
16 
17 The format for schema version 1.0 is:
18 schema-version: "1.0"
19 build:
20   name: string
21   version: string
22   architecture: x64 or arm64
23 components:
24   - name: string
25     repository: URL of git repository
26     ref: git ref that was built (sha, branch, or tag)
27     commit_id: The actual git commit ID that was built (i.e. the resolved "ref")
28     artifacts:
29       maven:
30         - maven/relative/path/to/artifact
31         - ...
32       plugins:
33         - plugins/relative/path/to/artifact
34         - ...
35       libs:
36         - libs/relative/path/to/artifact
37         - ...
38   - ...
39 """
40 
41 
42 class BuildManifest(Manifest):
43     components: list
44 
45     SCHEMA = {
46         "build": {
47             "required": True,
48             "type": "dict",
49             "schema": {
50                 "architecture": {"required": True, "type": "string"},
51                 "id": {"required": True, "type": "string"},
52                 "name": {"required": True, "type": "string"},
53                 "version": {"required": True, "type": "string"},
54             },
55         },
56         "schema-version": {"required": True, "type": "string", "allowed": ["1.0"]},
57         "components": {
58             "type": "list",
59             "schema": {
60                 "type": "dict",
61                 "schema": {
62                     "artifacts": {
63                         "type": "dict",
64                         "schema": {
65                             "maven": {"type": "list"},
66                             "plugins": {"type": "list"},
67                             "bundle": {"type": "list"},
68                             "core-plugins": {"type": "list"},
69                             "libs": {"type": "list"},
70                         },
71                     },
72                     "commit_id": {"required": True, "type": "string"},
73                     "name": {"required": True, "type": "string"},
74                     "ref": {"required": True, "type": "string"},
75                     "repository": {"required": True, "type": "string"},
76                     "version": {"required": True, "type": "string"},
77                 },
78             },
79         },
80     }
81 
82     def __init__(self, data):
83         super().__init__(data)
84 
85         self.build = self.Build(data["build"])
86         self.components = list(
87             map(lambda entry: self.Component(entry), data.get("components", []))
88         )
89 
90     def __to_dict__(self):
91         return {
92             "schema-version": "1.0",
93             "build": self.build.__to_dict__(),
94             "components": list(
95                 map(lambda component: component.__to_dict__(), self.components)
96             ),
97         }
98 
99     @staticmethod
100     def get_build_manifest_relative_location(
101         build_id, opensearch_version, architecture
102     ):
103         return f"builds/{opensearch_version}/{build_id}/{architecture}/manifest.yml"
104 
105     @staticmethod
106     def from_s3(bucket_name, build_id, opensearch_version, architecture, work_dir=None):
107         work_dir = work_dir if not None else str(os.getcwd())
108         manifest_s3_path = BuildManifest.get_build_manifest_relative_location(
109             build_id, opensearch_version, architecture
110         )
111         S3Bucket(bucket_name).download_file(manifest_s3_path, work_dir)
112         with open("manifest.yml", "r") as file:
113             build_manifest = BuildManifest.from_file(file)
114         os.remove(os.path.realpath(os.path.join(work_dir, "manifest.yml")))
115         return build_manifest
116 
117     class Build:
118         def __init__(self, data):
119             self.name = data["name"]
120             self.version = data["version"]
121             self.architecture = data["architecture"]
122             self.id = data["id"]
123 
124         def __to_dict__(self):
125             return {
126                 "name": self.name,
127                 "version": self.version,
128                 "architecture": self.architecture,
129                 "id": self.id,
130             }
131 
132     class Component:
133         def __init__(self, data):
134             self.name = data["name"]
135             self.repository = data["repository"]
136             self.ref = data["ref"]
137             self.commit_id = data["commit_id"]
138             self.artifacts = data.get("artifacts", [])
139             self.version = data["version"]
140 
141         def __to_dict__(self):
142             return {
143                 "name": self.name,
144                 "repository": self.repository,
145                 "ref": self.ref,
146                 "commit_id": self.commit_id,
147                 "artifacts": self.artifacts,
148                 "version": self.version,
149             }
150
```

Path: `bundle-workflow/src/aws/s3_bucket.py`
Content:
```
1 # SPDX-License-Identifier: Apache-2.0
2 #
3 # The OpenSearch Contributors require contributions made to
4 # this file be licensed under the Apache-2.0 license or a
5 # compatible open source license.
6 
7 import os
8 from pathlib import Path
9 from urllib.parse import urlparse
10 
11 import boto3
12 from botocore.exceptions import ClientError
13 
14 
15 class S3Bucket:
16     AWS_ROLE_ARN = "AWS_ROLE_ARN"
17     AWS_ROLE_SESSION_NAME = "AWS_ROLE_SESSION_NAME"
18 
19     def __init__(self, bucket_name, role_arn=None, role_session_name=None):
20         """
21         Provides methods to download/upload files and folders to S3 bucket
22 
23         :param bucket_name: The s3 bucket name
24         :param role_arn: the arn of the role that has permissions to access S3
25         :param role_session_name: the aws role session name
26         """
27         self.bucket_name = bucket_name
28         self.role_arn = (
29             role_arn if role_arn is not None else os.environ.get(S3Bucket.AWS_ROLE_ARN)
30         )
31         self.role_session_name = (
32             role_session_name
33             if role_session_name is not None
34             else os.environ.get(S3Bucket.AWS_ROLE_SESSION_NAME)
35         )
36         # TODO: later use for credential refereshing
37         assumed_role_creds = self.__sts_assume_role()
38         self.__s3_client, self.__s3_resource = self.__create_s3_clients(
39             assumed_role_creds
40         )
41 
42     def __sts_assume_role(self):
43         try:
44             sts_connection = boto3.client("sts")
45             response = sts_connection.assume_role(
46                 RoleArn=self.role_arn,
47                 RoleSessionName=self.role_session_name,
48                 DurationSeconds=3600,
49             )
50             return response["Credentials"]
51         except Exception as e:
52             raise STSError(e)
53 
54     def __create_s3_clients(self, assumed_role_cred):
55         s3_client = boto3.client(
56             "s3",
57             aws_access_key_id=assumed_role_cred["AccessKeyId"],
58             aws_secret_access_key=assumed_role_cred["SecretAccessKey"],
59             aws_session_token=assumed_role_cred["SessionToken"],
60         )
61         s3_resource = boto3.resource(
62             "s3",
63             aws_access_key_id=assumed_role_cred["AccessKeyId"],
64             aws_secret_access_key=assumed_role_cred["SecretAccessKey"],
65             aws_session_token=assumed_role_cred["SessionToken"],
66         )
67         return s3_client, s3_resource
68 
69     def download_folder(self, prefix, dest):
70         """
71         Download the contents of a folder directory
72 
73         :param prefix: The folder path inside the bucket
74         :param dest: local destination to download the folder at
75         """
76         bucket = self.__s3_resource.Bucket(self.bucket_name)
77         s3_path = urlparse(prefix).path.lstrip("/")
78         local_dir = Path(dest)
79         s3_response = bucket.objects.filter(Prefix=s3_path)
80         for obj in s3_response:
81             target = (
82                 obj.key
83                 if local_dir is None
84                 else local_dir / Path(obj.key).relative_to(s3_path)
85             )
86             target.parent.mkdir(parents=True, exist_ok=True)
87             if obj.key[-1] == "/":
88                 continue
89             self.__download(bucket, obj.key, str(target))
90 
91     def download_file(self, key, dest):
92         """
93         Download a single object from s3.
94 
95         :param key: The s3 key for the object to download
96         :param dest: local destination
97         """
98         bucket = self.__s3_resource.Bucket(self.bucket_name)
99         local_dir = Path(dest)
100         file_name = key.split("/")[-1]
101         target = Path(local_dir) / Path(file_name)
102         return self.__download(bucket, key, str(target))
103 
104     @staticmethod
105     def __download(bucket, key, path):
106         try:
107             bucket.download_file(key, path)
108         except ClientError as e:
109             raise S3DownloadError(e)
110 
111     def upload_file(self, key, source):
112         """
113         Upload a file to s3.
114 
115         :param key: The s3 key for the uploaded object
116         :param source: local path of the file
117         """
118         try:
119             self.__s3_client.upload_file(source, self.bucket_name, key)
120         except ClientError as e:
121             raise S3UploadError(e)
122 
123 
124 class S3Error(Exception):
125     """Base class for S3 Errors"""
126 
127     pass
128 
129 
130 class STSError(Exception):
131     """Base class for STS Error"""
132 
133     pass
134 
135 
136 class S3DownloadError(S3Error):
137     """Raised when there is a download object failure"""
138 
139     pass
140 
141 
142 class S3UploadError(S3Error):
143     """Raised when there is an upload object failure"""
144 
145     pass
146
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/bundle-workflow/src/aws/s3_bucket.py b/bundle-workflow/src/aws/s3_bucket.py
--- a/bundle-workflow/src/aws/s3_bucket.py
+++ b/bundle-workflow/src/aws/s3_bucket.py
@@ -4,6 +4,7 @@
 # this file be licensed under the Apache-2.0 license or a
 # compatible open source license.
 
+import logging
 import os
 from pathlib import Path
 from urllib.parse import urlparse
@@ -106,6 +107,7 @@
         try:
             bucket.download_file(key, path)
         except ClientError as e:
+            logging.error(f"Failed to download s3 key: {key} from path: {path}")
             raise S3DownloadError(e)
 
     def upload_file(self, key, source):
@@ -118,6 +120,7 @@
         try:
             self.__s3_client.upload_file(source, self.bucket_name, key)
         except ClientError as e:
+            logging.error(f"Failed to upload s3 key: {key} from local source: {source}")
             raise S3UploadError(e)
 
 
diff --git a/bundle-workflow/src/manifests/build_manifest.py b/bundle-workflow/src/manifests/build_manifest.py
--- a/bundle-workflow/src/manifests/build_manifest.py
+++ b/bundle-workflow/src/manifests/build_manifest.py
@@ -96,6 +96,17 @@
             ),
         }
 
+    def get_component(self, component_name):
+        component = next(
+            iter(filter(lambda comp: comp.name == component_name, self.components)),
+            None,
+        )
+        if component is None:
+            raise BuildManifest.ComponentNotFoundError(
+                f"{component_name} not found in build manifest.yml"
+            )
+        return component
+
     @staticmethod
     def get_build_manifest_relative_location(
         build_id, opensearch_version, architecture
@@ -114,6 +125,9 @@
         os.remove(os.path.realpath(os.path.join(work_dir, "manifest.yml")))
         return build_manifest
 
+    class ComponentNotFoundError(Exception):
+        pass
+
     class Build:
         def __init__(self, data):
             self.name = data["name"]

{"golden_diff": "diff --git a/bundle-workflow/src/aws/s3_bucket.py b/bundle-workflow/src/aws/s3_bucket.py\n--- a/bundle-workflow/src/aws/s3_bucket.py\n+++ b/bundle-workflow/src/aws/s3_bucket.py\n@@ -4,6 +4,7 @@\n # this file be licensed under the Apache-2.0 license or a\n # compatible open source license.\n \n+import logging\n import os\n from pathlib import Path\n from urllib.parse import urlparse\n@@ -106,6 +107,7 @@\n         try:\n             bucket.download_file(key, path)\n         except ClientError as e:\n+            logging.error(f\"Failed to download s3 key: {key} from path: {path}\")\n             raise S3DownloadError(e)\n \n     def upload_file(self, key, source):\n@@ -118,6 +120,7 @@\n         try:\n             self.__s3_client.upload_file(source, self.bucket_name, key)\n         except ClientError as e:\n+            logging.error(f\"Failed to upload s3 key: {key} from local source: {source}\")\n             raise S3UploadError(e)\n \n \ndiff --git a/bundle-workflow/src/manifests/build_manifest.py b/bundle-workflow/src/manifests/build_manifest.py\n--- a/bundle-workflow/src/manifests/build_manifest.py\n+++ b/bundle-workflow/src/manifests/build_manifest.py\n@@ -96,6 +96,17 @@\n             ),\n         }\n \n+    def get_component(self, component_name):\n+        component = next(\n+            iter(filter(lambda comp: comp.name == component_name, self.components)),\n+            None,\n+        )\n+        if component is None:\n+            raise BuildManifest.ComponentNotFoundError(\n+                f\"{component_name} not found in build manifest.yml\"\n+            )\n+        return component\n+\n     @staticmethod\n     def get_build_manifest_relative_location(\n         build_id, opensearch_version, architecture\n@@ -114,6 +125,9 @@\n         os.remove(os.path.realpath(os.path.join(work_dir, \"manifest.yml\")))\n         return build_manifest\n \n+    class ComponentNotFoundError(Exception):\n+        pass\n+\n     class Build:\n         def __init__(self, data):\n             self.name = data[\"name\"]\n", "issue": "Use DependencyInstaller to sync maven and build dependencies in Integtests\n\n", "before_files": [{"content": "# SPDX-License-Identifier: Apache-2.0\n#\n# The OpenSearch Contributors require contributions made to\n# this file be licensed under the Apache-2.0 license or a\n# compatible open source license.\n\nimport os\n\nfrom aws.s3_bucket import S3Bucket\nfrom manifests.manifest import Manifest\n\n\"\"\"\nA BuildManifest is an immutable view of the outputs from a build step\nThe manifest contains information about the product that was built (in the `build` section),\nand the components that made up the build in the `components` section.\n\nThe format for schema version 1.0 is:\nschema-version: \"1.0\"\nbuild:\n  name: string\n  version: string\n  architecture: x64 or arm64\ncomponents:\n  - name: string\n    repository: URL of git repository\n    ref: git ref that was built (sha, branch, or tag)\n    commit_id: The actual git commit ID that was built (i.e. the resolved \"ref\")\n    artifacts:\n      maven:\n        - maven/relative/path/to/artifact\n        - ...\n      plugins:\n        - plugins/relative/path/to/artifact\n        - ...\n      libs:\n        - libs/relative/path/to/artifact\n        - ...\n  - ...\n\"\"\"\n\n\nclass BuildManifest(Manifest):\n    components: list\n\n    SCHEMA = {\n        \"build\": {\n            \"required\": True,\n            \"type\": \"dict\",\n            \"schema\": {\n                \"architecture\": {\"required\": True, \"type\": \"string\"},\n                \"id\": {\"required\": True, \"type\": \"string\"},\n                \"name\": {\"required\": True, \"type\": \"string\"},\n                \"version\": {\"required\": True, \"type\": \"string\"},\n            },\n        },\n        \"schema-version\": {\"required\": True, \"type\": \"string\", \"allowed\": [\"1.0\"]},\n        \"components\": {\n            \"type\": \"list\",\n            \"schema\": {\n                \"type\": \"dict\",\n                \"schema\": {\n                    \"artifacts\": {\n                        \"type\": \"dict\",\n                        \"schema\": {\n                            \"maven\": {\"type\": \"list\"},\n                            \"plugins\": {\"type\": \"list\"},\n                            \"bundle\": {\"type\": \"list\"},\n                            \"core-plugins\": {\"type\": \"list\"},\n                            \"libs\": {\"type\": \"list\"},\n                        },\n                    },\n                    \"commit_id\": {\"required\": True, \"type\": \"string\"},\n                    \"name\": {\"required\": True, \"type\": \"string\"},\n                    \"ref\": {\"required\": True, \"type\": \"string\"},\n                    \"repository\": {\"required\": True, \"type\": \"string\"},\n                    \"version\": {\"required\": True, \"type\": \"string\"},\n                },\n            },\n        },\n    }\n\n    def __init__(self, data):\n        super().__init__(data)\n\n        self.build = self.Build(data[\"build\"])\n        self.components = list(\n            map(lambda entry: self.Component(entry), data.get(\"components\", []))\n        )\n\n    def __to_dict__(self):\n        return {\n            \"schema-version\": \"1.0\",\n            \"build\": self.build.__to_dict__(),\n            \"components\": list(\n                map(lambda component: component.__to_dict__(), self.components)\n            ),\n        }\n\n    @staticmethod\n    def get_build_manifest_relative_location(\n        build_id, opensearch_version, architecture\n    ):\n        return f\"builds/{opensearch_version}/{build_id}/{architecture}/manifest.yml\"\n\n    @staticmethod\n    def from_s3(bucket_name, build_id, opensearch_version, architecture, work_dir=None):\n        work_dir = work_dir if not None else str(os.getcwd())\n        manifest_s3_path = BuildManifest.get_build_manifest_relative_location(\n            build_id, opensearch_version, architecture\n        )\n        S3Bucket(bucket_name).download_file(manifest_s3_path, work_dir)\n        with open(\"manifest.yml\", \"r\") as file:\n            build_manifest = BuildManifest.from_file(file)\n        os.remove(os.path.realpath(os.path.join(work_dir, \"manifest.yml\")))\n        return build_manifest\n\n    class Build:\n        def __init__(self, data):\n            self.name = data[\"name\"]\n            self.version = data[\"version\"]\n            self.architecture = data[\"architecture\"]\n            self.id = data[\"id\"]\n\n        def __to_dict__(self):\n            return {\n                \"name\": self.name,\n                \"version\": self.version,\n                \"architecture\": self.architecture,\n                \"id\": self.id,\n            }\n\n    class Component:\n        def __init__(self, data):\n            self.name = data[\"name\"]\n            self.repository = data[\"repository\"]\n            self.ref = data[\"ref\"]\n            self.commit_id = data[\"commit_id\"]\n            self.artifacts = data.get(\"artifacts\", [])\n            self.version = data[\"version\"]\n\n        def __to_dict__(self):\n            return {\n                \"name\": self.name,\n                \"repository\": self.repository,\n                \"ref\": self.ref,\n                \"commit_id\": self.commit_id,\n                \"artifacts\": self.artifacts,\n                \"version\": self.version,\n            }\n", "path": "bundle-workflow/src/manifests/build_manifest.py"}, {"content": "# SPDX-License-Identifier: Apache-2.0\n#\n# The OpenSearch Contributors require contributions made to\n# this file be licensed under the Apache-2.0 license or a\n# compatible open source license.\n\nimport os\nfrom pathlib import Path\nfrom urllib.parse import urlparse\n\nimport boto3\nfrom botocore.exceptions import ClientError\n\n\nclass S3Bucket:\n    AWS_ROLE_ARN = \"AWS_ROLE_ARN\"\n    AWS_ROLE_SESSION_NAME = \"AWS_ROLE_SESSION_NAME\"\n\n    def __init__(self, bucket_name, role_arn=None, role_session_name=None):\n        \"\"\"\n        Provides methods to download/upload files and folders to S3 bucket\n\n        :param bucket_name: The s3 bucket name\n        :param role_arn: the arn of the role that has permissions to access S3\n        :param role_session_name: the aws role session name\n        \"\"\"\n        self.bucket_name = bucket_name\n        self.role_arn = (\n            role_arn if role_arn is not None else os.environ.get(S3Bucket.AWS_ROLE_ARN)\n        )\n        self.role_session_name = (\n            role_session_name\n            if role_session_name is not None\n            else os.environ.get(S3Bucket.AWS_ROLE_SESSION_NAME)\n        )\n        # TODO: later use for credential refereshing\n        assumed_role_creds = self.__sts_assume_role()\n        self.__s3_client, self.__s3_resource = self.__create_s3_clients(\n            assumed_role_creds\n        )\n\n    def __sts_assume_role(self):\n        try:\n            sts_connection = boto3.client(\"sts\")\n            response = sts_connection.assume_role(\n                RoleArn=self.role_arn,\n                RoleSessionName=self.role_session_name,\n                DurationSeconds=3600,\n            )\n            return response[\"Credentials\"]\n        except Exception as e:\n            raise STSError(e)\n\n    def __create_s3_clients(self, assumed_role_cred):\n        s3_client = boto3.client(\n            \"s3\",\n            aws_access_key_id=assumed_role_cred[\"AccessKeyId\"],\n            aws_secret_access_key=assumed_role_cred[\"SecretAccessKey\"],\n            aws_session_token=assumed_role_cred[\"SessionToken\"],\n        )\n        s3_resource = boto3.resource(\n            \"s3\",\n            aws_access_key_id=assumed_role_cred[\"AccessKeyId\"],\n            aws_secret_access_key=assumed_role_cred[\"SecretAccessKey\"],\n            aws_session_token=assumed_role_cred[\"SessionToken\"],\n        )\n        return s3_client, s3_resource\n\n    def download_folder(self, prefix, dest):\n        \"\"\"\n        Download the contents of a folder directory\n\n        :param prefix: The folder path inside the bucket\n        :param dest: local destination to download the folder at\n        \"\"\"\n        bucket = self.__s3_resource.Bucket(self.bucket_name)\n        s3_path = urlparse(prefix).path.lstrip(\"/\")\n        local_dir = Path(dest)\n        s3_response = bucket.objects.filter(Prefix=s3_path)\n        for obj in s3_response:\n            target = (\n                obj.key\n                if local_dir is None\n                else local_dir / Path(obj.key).relative_to(s3_path)\n            )\n            target.parent.mkdir(parents=True, exist_ok=True)\n            if obj.key[-1] == \"/\":\n                continue\n            self.__download(bucket, obj.key, str(target))\n\n    def download_file(self, key, dest):\n        \"\"\"\n        Download a single object from s3.\n\n        :param key: The s3 key for the object to download\n        :param dest: local destination\n        \"\"\"\n        bucket = self.__s3_resource.Bucket(self.bucket_name)\n        local_dir = Path(dest)\n        file_name = key.split(\"/\")[-1]\n        target = Path(local_dir) / Path(file_name)\n        return self.__download(bucket, key, str(target))\n\n    @staticmethod\n    def __download(bucket, key, path):\n        try:\n            bucket.download_file(key, path)\n        except ClientError as e:\n            raise S3DownloadError(e)\n\n    def upload_file(self, key, source):\n        \"\"\"\n        Upload a file to s3.\n\n        :param key: The s3 key for the uploaded object\n        :param source: local path of the file\n        \"\"\"\n        try:\n            self.__s3_client.upload_file(source, self.bucket_name, key)\n        except ClientError as e:\n            raise S3UploadError(e)\n\n\nclass S3Error(Exception):\n    \"\"\"Base class for S3 Errors\"\"\"\n\n    pass\n\n\nclass STSError(Exception):\n    \"\"\"Base class for STS Error\"\"\"\n\n    pass\n\n\nclass S3DownloadError(S3Error):\n    \"\"\"Raised when there is a download object failure\"\"\"\n\n    pass\n\n\nclass S3UploadError(S3Error):\n    \"\"\"Raised when there is an upload object failure\"\"\"\n\n    pass\n", "path": "bundle-workflow/src/aws/s3_bucket.py"}], "after_files": [{"content": "# SPDX-License-Identifier: Apache-2.0\n#\n# The OpenSearch Contributors require contributions made to\n# this file be licensed under the Apache-2.0 license or a\n# compatible open source license.\n\nimport os\n\nfrom aws.s3_bucket import S3Bucket\nfrom manifests.manifest import Manifest\n\n\"\"\"\nA BuildManifest is an immutable view of the outputs from a build step\nThe manifest contains information about the product that was built (in the `build` section),\nand the components that made up the build in the `components` section.\n\nThe format for schema version 1.0 is:\nschema-version: \"1.0\"\nbuild:\n  name: string\n  version: string\n  architecture: x64 or arm64\ncomponents:\n  - name: string\n    repository: URL of git repository\n    ref: git ref that was built (sha, branch, or tag)\n    commit_id: The actual git commit ID that was built (i.e. the resolved \"ref\")\n    artifacts:\n      maven:\n        - maven/relative/path/to/artifact\n        - ...\n      plugins:\n        - plugins/relative/path/to/artifact\n        - ...\n      libs:\n        - libs/relative/path/to/artifact\n        - ...\n  - ...\n\"\"\"\n\n\nclass BuildManifest(Manifest):\n    components: list\n\n    SCHEMA = {\n        \"build\": {\n            \"required\": True,\n            \"type\": \"dict\",\n            \"schema\": {\n                \"architecture\": {\"required\": True, \"type\": \"string\"},\n                \"id\": {\"required\": True, \"type\": \"string\"},\n                \"name\": {\"required\": True, \"type\": \"string\"},\n                \"version\": {\"required\": True, \"type\": \"string\"},\n            },\n        },\n        \"schema-version\": {\"required\": True, \"type\": \"string\", \"allowed\": [\"1.0\"]},\n        \"components\": {\n            \"type\": \"list\",\n            \"schema\": {\n                \"type\": \"dict\",\n                \"schema\": {\n                    \"artifacts\": {\n                        \"type\": \"dict\",\n                        \"schema\": {\n                            \"maven\": {\"type\": \"list\"},\n                            \"plugins\": {\"type\": \"list\"},\n                            \"bundle\": {\"type\": \"list\"},\n                            \"core-plugins\": {\"type\": \"list\"},\n                            \"libs\": {\"type\": \"list\"},\n                        },\n                    },\n                    \"commit_id\": {\"required\": True, \"type\": \"string\"},\n                    \"name\": {\"required\": True, \"type\": \"string\"},\n                    \"ref\": {\"required\": True, \"type\": \"string\"},\n                    \"repository\": {\"required\": True, \"type\": \"string\"},\n                    \"version\": {\"required\": True, \"type\": \"string\"},\n                },\n            },\n        },\n    }\n\n    def __init__(self, data):\n        super().__init__(data)\n\n        self.build = self.Build(data[\"build\"])\n        self.components = list(\n            map(lambda entry: self.Component(entry), data.get(\"components\", []))\n        )\n\n    def __to_dict__(self):\n        return {\n            \"schema-version\": \"1.0\",\n            \"build\": self.build.__to_dict__(),\n            \"components\": list(\n                map(lambda component: component.__to_dict__(), self.components)\n            ),\n        }\n\n    def get_component(self, component_name):\n        component = next(\n            iter(filter(lambda comp: comp.name == component_name, self.components)),\n            None,\n        )\n        if component is None:\n            raise BuildManifest.ComponentNotFoundError(\n                f\"{component_name} not found in build manifest.yml\"\n            )\n        return component\n\n    @staticmethod\n    def get_build_manifest_relative_location(\n        build_id, opensearch_version, architecture\n    ):\n        return f\"builds/{opensearch_version}/{build_id}/{architecture}/manifest.yml\"\n\n    @staticmethod\n    def from_s3(bucket_name, build_id, opensearch_version, architecture, work_dir=None):\n        work_dir = work_dir if not None else str(os.getcwd())\n        manifest_s3_path = BuildManifest.get_build_manifest_relative_location(\n            build_id, opensearch_version, architecture\n        )\n        S3Bucket(bucket_name).download_file(manifest_s3_path, work_dir)\n        with open(\"manifest.yml\", \"r\") as file:\n            build_manifest = BuildManifest.from_file(file)\n        os.remove(os.path.realpath(os.path.join(work_dir, \"manifest.yml\")))\n        return build_manifest\n\n    class ComponentNotFoundError(Exception):\n        pass\n\n    class Build:\n        def __init__(self, data):\n            self.name = data[\"name\"]\n            self.version = data[\"version\"]\n            self.architecture = data[\"architecture\"]\n            self.id = data[\"id\"]\n\n        def __to_dict__(self):\n            return {\n                \"name\": self.name,\n                \"version\": self.version,\n                \"architecture\": self.architecture,\n                \"id\": self.id,\n            }\n\n    class Component:\n        def __init__(self, data):\n            self.name = data[\"name\"]\n            self.repository = data[\"repository\"]\n            self.ref = data[\"ref\"]\n            self.commit_id = data[\"commit_id\"]\n            self.artifacts = data.get(\"artifacts\", [])\n            self.version = data[\"version\"]\n\n        def __to_dict__(self):\n            return {\n                \"name\": self.name,\n                \"repository\": self.repository,\n                \"ref\": self.ref,\n                \"commit_id\": self.commit_id,\n                \"artifacts\": self.artifacts,\n                \"version\": self.version,\n            }\n", "path": "bundle-workflow/src/manifests/build_manifest.py"}, {"content": "# SPDX-License-Identifier: Apache-2.0\n#\n# The OpenSearch Contributors require contributions made to\n# this file be licensed under the Apache-2.0 license or a\n# compatible open source license.\n\nimport logging\nimport os\nfrom pathlib import Path\nfrom urllib.parse import urlparse\n\nimport boto3\nfrom botocore.exceptions import ClientError\n\n\nclass S3Bucket:\n    AWS_ROLE_ARN = \"AWS_ROLE_ARN\"\n    AWS_ROLE_SESSION_NAME = \"AWS_ROLE_SESSION_NAME\"\n\n    def __init__(self, bucket_name, role_arn=None, role_session_name=None):\n        \"\"\"\n        Provides methods to download/upload files and folders to S3 bucket\n\n        :param bucket_name: The s3 bucket name\n        :param role_arn: the arn of the role that has permissions to access S3\n        :param role_session_name: the aws role session name\n        \"\"\"\n        self.bucket_name = bucket_name\n        self.role_arn = (\n            role_arn if role_arn is not None else os.environ.get(S3Bucket.AWS_ROLE_ARN)\n        )\n        self.role_session_name = (\n            role_session_name\n            if role_session_name is not None\n            else os.environ.get(S3Bucket.AWS_ROLE_SESSION_NAME)\n        )\n        # TODO: later use for credential refereshing\n        assumed_role_creds = self.__sts_assume_role()\n        self.__s3_client, self.__s3_resource = self.__create_s3_clients(\n            assumed_role_creds\n        )\n\n    def __sts_assume_role(self):\n        try:\n            sts_connection = boto3.client(\"sts\")\n            response = sts_connection.assume_role(\n                RoleArn=self.role_arn,\n                RoleSessionName=self.role_session_name,\n                DurationSeconds=3600,\n            )\n            return response[\"Credentials\"]\n        except Exception as e:\n            raise STSError(e)\n\n    def __create_s3_clients(self, assumed_role_cred):\n        s3_client = boto3.client(\n            \"s3\",\n            aws_access_key_id=assumed_role_cred[\"AccessKeyId\"],\n            aws_secret_access_key=assumed_role_cred[\"SecretAccessKey\"],\n            aws_session_token=assumed_role_cred[\"SessionToken\"],\n        )\n        s3_resource = boto3.resource(\n            \"s3\",\n            aws_access_key_id=assumed_role_cred[\"AccessKeyId\"],\n            aws_secret_access_key=assumed_role_cred[\"SecretAccessKey\"],\n            aws_session_token=assumed_role_cred[\"SessionToken\"],\n        )\n        return s3_client, s3_resource\n\n    def download_folder(self, prefix, dest):\n        \"\"\"\n        Download the contents of a folder directory\n\n        :param prefix: The folder path inside the bucket\n        :param dest: local destination to download the folder at\n        \"\"\"\n        bucket = self.__s3_resource.Bucket(self.bucket_name)\n        s3_path = urlparse(prefix).path.lstrip(\"/\")\n        local_dir = Path(dest)\n        s3_response = bucket.objects.filter(Prefix=s3_path)\n        for obj in s3_response:\n            target = (\n                obj.key\n                if local_dir is None\n                else local_dir / Path(obj.key).relative_to(s3_path)\n            )\n            target.parent.mkdir(parents=True, exist_ok=True)\n            if obj.key[-1] == \"/\":\n                continue\n            self.__download(bucket, obj.key, str(target))\n\n    def download_file(self, key, dest):\n        \"\"\"\n        Download a single object from s3.\n\n        :param key: The s3 key for the object to download\n        :param dest: local destination\n        \"\"\"\n        bucket = self.__s3_resource.Bucket(self.bucket_name)\n        local_dir = Path(dest)\n        file_name = key.split(\"/\")[-1]\n        target = Path(local_dir) / Path(file_name)\n        return self.__download(bucket, key, str(target))\n\n    @staticmethod\n    def __download(bucket, key, path):\n        try:\n            bucket.download_file(key, path)\n        except ClientError as e:\n            logging.error(f\"Failed to download s3 key: {key} from path: {path}\")\n            raise S3DownloadError(e)\n\n    def upload_file(self, key, source):\n        \"\"\"\n        Upload a file to s3.\n\n        :param key: The s3 key for the uploaded object\n        :param source: local path of the file\n        \"\"\"\n        try:\n            self.__s3_client.upload_file(source, self.bucket_name, key)\n        except ClientError as e:\n            logging.error(f\"Failed to upload s3 key: {key} from local source: {source}\")\n            raise S3UploadError(e)\n\n\nclass S3Error(Exception):\n    \"\"\"Base class for S3 Errors\"\"\"\n\n    pass\n\n\nclass STSError(Exception):\n    \"\"\"Base class for STS Error\"\"\"\n\n    pass\n\n\nclass S3DownloadError(S3Error):\n    \"\"\"Raised when there is a download object failure\"\"\"\n\n    pass\n\n\nclass S3UploadError(S3Error):\n    \"\"\"Raised when there is an upload object failure\"\"\"\n\n    pass\n", "path": "bundle-workflow/src/aws/s3_bucket.py"}]}