Some checks failed
Build and Push Docker Image / build (push) Has been cancelled
- NHN Cloud API packages: token, vpc, compute, nks, storage - REST API endpoints with Swagger documentation - Async task processing for long-running operations - CORS configuration for frontend integration - Enhanced logging for debugging API calls Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
280 lines
9.8 KiB
Python
280 lines
9.8 KiB
Python
"""
|
|
NHN Cloud Object Storage API Module
|
|
|
|
Object Storage 컨테이너 및 오브젝트 관리
|
|
"""
|
|
|
|
import logging
|
|
from typing import Optional, List
|
|
|
|
import requests
|
|
|
|
from .base import BaseAPI, NHNCloudEndpoints, Region, NHNCloudAPIError
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ApiStorageObject(BaseAPI):
|
|
"""NHN Cloud Object Storage API 클래스"""
|
|
|
|
def __init__(self, region: str, token: str, storage_account: str):
|
|
"""
|
|
Args:
|
|
region: 리전 (kr1: 판교, kr2: 평촌)
|
|
token: API 인증 토큰
|
|
storage_account: 스토리지 계정 (AUTH_...)
|
|
"""
|
|
super().__init__(region, token)
|
|
self.storage_account = storage_account
|
|
|
|
if self.region == Region.KR1:
|
|
self.storage_url = NHNCloudEndpoints.STORAGE_KR1
|
|
else:
|
|
self.storage_url = NHNCloudEndpoints.STORAGE_KR2
|
|
|
|
def _get_url(self, container: Optional[str] = None, obj: Optional[str] = None) -> str:
|
|
"""URL 생성"""
|
|
parts = [self.storage_url, self.storage_account]
|
|
if container:
|
|
parts.append(container)
|
|
if obj:
|
|
parts.append(obj)
|
|
return "/".join(parts)
|
|
|
|
def _get_headers(self, extra_headers: Optional[dict] = None) -> dict:
|
|
"""Object Storage 전용 헤더"""
|
|
headers = {"X-Auth-Token": self.token}
|
|
if extra_headers:
|
|
headers.update(extra_headers)
|
|
return headers
|
|
|
|
# ==================== Container ====================
|
|
|
|
def get_container_list(self) -> List[str]:
|
|
"""컨테이너 목록 조회"""
|
|
url = self._get_url()
|
|
try:
|
|
response = self._session.get(
|
|
url,
|
|
headers=self._get_headers(),
|
|
timeout=self.DEFAULT_TIMEOUT,
|
|
)
|
|
if response.status_code >= 400:
|
|
self._handle_error(response)
|
|
return [c for c in response.text.split("\n") if c]
|
|
except requests.exceptions.RequestException as e:
|
|
logger.error(f"컨테이너 목록 조회 실패: {e}")
|
|
raise NHNCloudAPIError(f"컨테이너 목록 조회 실패: {e}")
|
|
|
|
def create_container(self, container_name: str) -> dict:
|
|
"""
|
|
컨테이너 생성
|
|
|
|
Args:
|
|
container_name: 컨테이너 이름
|
|
|
|
Returns:
|
|
dict: 생성 결과
|
|
"""
|
|
url = self._get_url(container_name)
|
|
try:
|
|
response = self._session.put(
|
|
url,
|
|
headers=self._get_headers(),
|
|
timeout=self.DEFAULT_TIMEOUT,
|
|
)
|
|
logger.info(f"컨테이너 생성: {container_name}")
|
|
return {"status": response.status_code, "container": container_name}
|
|
except requests.exceptions.RequestException as e:
|
|
logger.error(f"컨테이너 생성 실패: {e}")
|
|
raise NHNCloudAPIError(f"컨테이너 생성 실패: {e}")
|
|
|
|
def delete_container(self, container_name: str) -> dict:
|
|
"""컨테이너 삭제"""
|
|
url = self._get_url(container_name)
|
|
try:
|
|
response = self._session.delete(
|
|
url,
|
|
headers=self._get_headers(),
|
|
timeout=self.DEFAULT_TIMEOUT,
|
|
)
|
|
logger.info(f"컨테이너 삭제: {container_name}")
|
|
return {"status": response.status_code, "container": container_name}
|
|
except requests.exceptions.RequestException as e:
|
|
logger.error(f"컨테이너 삭제 실패: {e}")
|
|
raise NHNCloudAPIError(f"컨테이너 삭제 실패: {e}")
|
|
|
|
def set_container_public(self, container_name: str, is_public: bool = True) -> dict:
|
|
"""
|
|
컨테이너 공개/비공개 설정
|
|
|
|
Args:
|
|
container_name: 컨테이너 이름
|
|
is_public: 공개 여부 (True: 공개, False: 비공개)
|
|
|
|
Returns:
|
|
dict: 설정 결과
|
|
"""
|
|
url = self._get_url(container_name)
|
|
headers = self._get_headers()
|
|
headers["X-Container-Read"] = ".r:*" if is_public else ""
|
|
|
|
try:
|
|
response = self._session.post(
|
|
url,
|
|
headers=headers,
|
|
timeout=self.DEFAULT_TIMEOUT,
|
|
)
|
|
logger.info(f"컨테이너 공개 설정: {container_name} -> {is_public}")
|
|
return {"status": response.status_code, "public": is_public}
|
|
except requests.exceptions.RequestException as e:
|
|
logger.error(f"컨테이너 공개 설정 실패: {e}")
|
|
raise NHNCloudAPIError(f"컨테이너 공개 설정 실패: {e}")
|
|
|
|
# ==================== Object ====================
|
|
|
|
def get_object_list(self, container_name: str) -> List[str]:
|
|
"""오브젝트 목록 조회"""
|
|
url = self._get_url(container_name)
|
|
try:
|
|
response = self._session.get(
|
|
url,
|
|
headers=self._get_headers(),
|
|
timeout=self.DEFAULT_TIMEOUT,
|
|
)
|
|
if response.status_code >= 400:
|
|
self._handle_error(response)
|
|
return [o for o in response.text.split("\n") if o]
|
|
except requests.exceptions.RequestException as e:
|
|
logger.error(f"오브젝트 목록 조회 실패: {e}")
|
|
raise NHNCloudAPIError(f"오브젝트 목록 조회 실패: {e}")
|
|
|
|
def upload_object(
|
|
self,
|
|
container_name: str,
|
|
object_name: str,
|
|
file_path: str,
|
|
) -> dict:
|
|
"""
|
|
오브젝트 업로드
|
|
|
|
Args:
|
|
container_name: 컨테이너 이름
|
|
object_name: 오브젝트 이름
|
|
file_path: 업로드할 파일 경로
|
|
|
|
Returns:
|
|
dict: 업로드 결과
|
|
"""
|
|
url = self._get_url(container_name, object_name)
|
|
|
|
try:
|
|
with open(file_path, "rb") as f:
|
|
response = self._session.put(
|
|
url,
|
|
headers=self._get_headers(),
|
|
data=f.read(),
|
|
timeout=self.DEFAULT_TIMEOUT,
|
|
)
|
|
logger.info(f"오브젝트 업로드: {container_name}/{object_name}")
|
|
return {"status": response.status_code, "object": object_name}
|
|
except FileNotFoundError:
|
|
raise NHNCloudAPIError(f"파일을 찾을 수 없습니다: {file_path}")
|
|
except requests.exceptions.RequestException as e:
|
|
logger.error(f"오브젝트 업로드 실패: {e}")
|
|
raise NHNCloudAPIError(f"오브젝트 업로드 실패: {e}")
|
|
|
|
def upload_object_data(
|
|
self,
|
|
container_name: str,
|
|
object_name: str,
|
|
data: bytes,
|
|
content_type: Optional[str] = None,
|
|
) -> dict:
|
|
"""
|
|
데이터를 오브젝트로 업로드
|
|
|
|
Args:
|
|
container_name: 컨테이너 이름
|
|
object_name: 오브젝트 이름
|
|
data: 업로드할 데이터
|
|
content_type: Content-Type 헤더
|
|
|
|
Returns:
|
|
dict: 업로드 결과
|
|
"""
|
|
url = self._get_url(container_name, object_name)
|
|
headers = self._get_headers()
|
|
if content_type:
|
|
headers["Content-Type"] = content_type
|
|
|
|
try:
|
|
response = self._session.put(
|
|
url,
|
|
headers=headers,
|
|
data=data,
|
|
timeout=self.DEFAULT_TIMEOUT,
|
|
)
|
|
logger.info(f"오브젝트 업로드: {container_name}/{object_name}")
|
|
return {"status": response.status_code, "object": object_name}
|
|
except requests.exceptions.RequestException as e:
|
|
logger.error(f"오브젝트 업로드 실패: {e}")
|
|
raise NHNCloudAPIError(f"오브젝트 업로드 실패: {e}")
|
|
|
|
def download_object(self, container_name: str, object_name: str) -> bytes:
|
|
"""
|
|
오브젝트 다운로드
|
|
|
|
Args:
|
|
container_name: 컨테이너 이름
|
|
object_name: 오브젝트 이름
|
|
|
|
Returns:
|
|
bytes: 오브젝트 데이터
|
|
"""
|
|
url = self._get_url(container_name, object_name)
|
|
|
|
try:
|
|
response = self._session.get(
|
|
url,
|
|
headers=self._get_headers(),
|
|
timeout=self.DEFAULT_TIMEOUT,
|
|
)
|
|
if response.status_code >= 400:
|
|
self._handle_error(response)
|
|
return response.content
|
|
except requests.exceptions.RequestException as e:
|
|
logger.error(f"오브젝트 다운로드 실패: {e}")
|
|
raise NHNCloudAPIError(f"오브젝트 다운로드 실패: {e}")
|
|
|
|
def delete_object(self, container_name: str, object_name: str) -> dict:
|
|
"""오브젝트 삭제"""
|
|
url = self._get_url(container_name, object_name)
|
|
|
|
try:
|
|
response = self._session.delete(
|
|
url,
|
|
headers=self._get_headers(),
|
|
timeout=self.DEFAULT_TIMEOUT,
|
|
)
|
|
logger.info(f"오브젝트 삭제: {container_name}/{object_name}")
|
|
return {"status": response.status_code, "object": object_name}
|
|
except requests.exceptions.RequestException as e:
|
|
logger.error(f"오브젝트 삭제 실패: {e}")
|
|
raise NHNCloudAPIError(f"오브젝트 삭제 실패: {e}")
|
|
|
|
def get_object_info(self, container_name: str, object_name: str) -> dict:
|
|
"""오브젝트 메타데이터 조회"""
|
|
url = self._get_url(container_name, object_name)
|
|
|
|
try:
|
|
response = self._session.head(
|
|
url,
|
|
headers=self._get_headers(),
|
|
timeout=self.DEFAULT_TIMEOUT,
|
|
)
|
|
return dict(response.headers)
|
|
except requests.exceptions.RequestException as e:
|
|
logger.error(f"오브젝트 정보 조회 실패: {e}")
|
|
raise NHNCloudAPIError(f"오브젝트 정보 조회 실패: {e}")
|