syndicate/core/resources/swagger_ui_resource.py (133 lines of code) (raw):

""" Copyright 2024 EPAM Systems, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import io import os import posixpath from pathlib import PurePath from shutil import rmtree from zipfile import ZipFile from syndicate.commons import deep_get from syndicate.commons.log_helper import get_logger, get_user_logger from syndicate.core.constants import ARTIFACTS_FOLDER, S3_PATH_NAME, \ SWAGGER_UI_SPEC_NAME_TEMPLATE from syndicate.core.helper import build_path, unpack_kwargs from syndicate.core.resources.base_resource import BaseResource from syndicate.core.resources.helper import build_description_obj INDEX_FILE_NAME = 'index.html' _LOG = get_logger(__name__) USER_LOG = get_user_logger() class SwaggerUIResource(BaseResource): def __init__(self, s3_conn, deploy_target_bucket, deploy_target_bucket_key_compound, region, account_id, extended_prefix_mode, prefix, suffix) -> None: from syndicate.core import CONF_PATH self.s3_conn = s3_conn self.deploy_target_bucket = deploy_target_bucket self.deploy_target_bucket_key_compound = \ deploy_target_bucket_key_compound self.region = region self.account_id = account_id self.extended_prefix_mode = extended_prefix_mode self.prefix = prefix self.suffix = suffix self.conf_path = CONF_PATH def create_update_swagger_ui(self, args): return self.create_pool(self._create_update_swagger_ui_from_meta, args) def remove_swagger_ui(self, args): return self.create_pool(self._remove_swagger_ui, args) @unpack_kwargs def _create_update_swagger_ui_from_meta(self, name, meta, context=None): artifact_dir = PurePath(self.conf_path, ARTIFACTS_FOLDER, name).as_posix() target_bucket = meta.get('target_bucket') if not target_bucket: raise AssertionError(f'Target bucket for Swagger UI \'{name}\' is ' f'absent in resource meta') if not self.s3_conn.is_bucket_exists(target_bucket): raise AssertionError(f'Target bucket \'{target_bucket}\' for ' f'Swagger UI \'{name}\' doesn\'t exists') artifact_path = meta.get(S3_PATH_NAME) if not artifact_path: raise AssertionError(f'Can\'t resolve Swagger UI artifact path') artifact_src_path = posixpath.join( self.deploy_target_bucket_key_compound, artifact_path) _LOG.info(f'Downloading an artifact for Swagger UI \'{name}\'') with io.BytesIO() as artifact: self.s3_conn.download_to_file( bucket_name=self.deploy_target_bucket, key=artifact_src_path, file=artifact) extract_to = build_path(artifact_dir, name) with ZipFile(artifact, 'r') as zf: zf.extractall(extract_to) _LOG.info(f'Uploading files for Swagger UI \'{name}\' to target ' f'bucket \'{target_bucket}\'') for file in os.listdir(extract_to): extra_args = None if file == INDEX_FILE_NAME: extra_args = { 'ContentType': 'text/html', 'ContentDisposition': f'inline;filename={INDEX_FILE_NAME}' } self.s3_conn.upload_single_file(path=PurePath(extract_to, file).as_posix(), key=file, bucket=target_bucket, extra_args=extra_args) _LOG.info(f'Removing temporary directory \'{artifact_dir}\'') rmtree(artifact_dir) return self.describe_swagger_ui(name, meta) def describe_swagger_ui(self, name, meta): target_bucket = meta.get('target_bucket') arn = (f'arn:aws-syndicate:{self.region}:{self.account_id}:' f'{name}') spec_file_name = SWAGGER_UI_SPEC_NAME_TEMPLATE.format( name=name) website_hosting = self.s3_conn.get_bucket_website(target_bucket) bucket_description = { 'arn': f'arn:aws:s3:::{target_bucket}', 'bucket_acl': self.s3_conn.get_bucket_acl(target_bucket), 'location': self.s3_conn.get_bucket_location(target_bucket), 'policy': self.s3_conn.get_bucket_policy(target_bucket) } if not bucket_description['location']: return {} response = { 'host_description': bucket_description } if website_hosting: hosting_config = { "enabled": True, "index_document": deep_get(website_hosting, ['IndexDocument', 'Suffix']) } if self.s3_conn.is_file_exists(target_bucket, spec_file_name): hosting_config['api_spec_document'] = spec_file_name hosting_config['endpoint'] = ( f'http://{target_bucket}.s3-website.{self.region}.' f'amazonaws.com') response['website_hosting'] = hosting_config return { arn: build_description_obj(response, name, meta) } @unpack_kwargs def _remove_swagger_ui(self, arn, config): resource_name = arn.split(':')[-1] target_bucket = deep_get(config, ['resource_meta', 'target_bucket']) pure_name = resource_name if self.extended_prefix_mode: if self.prefix: pure_name = pure_name[len(self.prefix):] if self.suffix: pure_name = pure_name[:-len(self.suffix)] if not self.s3_conn.is_bucket_exists(target_bucket): USER_LOG.info(f'Target bucket with name \'{target_bucket}\' not ' f'found') return {arn: config} else: self.s3_conn.remove_object(bucket_name=target_bucket, file_name=INDEX_FILE_NAME) self.s3_conn.remove_object( bucket_name=target_bucket, file_name=SWAGGER_UI_SPEC_NAME_TEMPLATE.format( name=pure_name)) USER_LOG.info(f'Swagger UI \'{resource_name}\' removed') return {arn: config}