Add NHN Cloud API integration with async task support
Some checks failed
Build and Push Docker Image / build (push) Has been cancelled
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>
This commit is contained in:
279
nhn/packages/storage.py
Normal file
279
nhn/packages/storage.py
Normal file
@ -0,0 +1,279 @@
|
||||
"""
|
||||
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}")
|
||||
Reference in New Issue
Block a user