-
Notifications
You must be signed in to change notification settings - Fork 0
Fix:S3->Oracle #134
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix:S3->Oracle #134
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,7 +28,9 @@ db.sqlite3 | |
|
|
||
| # Environments | ||
| .env | ||
| .env.local | ||
| .venv | ||
| .codex | ||
|
|
||
| # VScode | ||
| .vscode | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| from __future__ import annotations | ||
|
|
||
| import base64 | ||
| import json | ||
| import os | ||
| import sys | ||
| from typing import cast | ||
|
|
||
| from dotenv import load_dotenv | ||
| import oci | ||
| from oci.secrets.models import SecretBundle | ||
|
|
||
| from wacruit.src.settings import settings | ||
|
|
||
|
|
||
| def _required_env(name: str) -> str: | ||
| value = os.getenv(name, "").strip() | ||
| if not value: | ||
| raise ValueError(f"Missing required env: {name}") | ||
| return value | ||
|
|
||
|
|
||
| def _make_secrets_client() -> oci.secrets.SecretsClient: | ||
| try: | ||
| signer = oci.auth.signers.InstancePrincipalsSecurityTokenSigner() | ||
| return oci.secrets.SecretsClient({}, signer=signer) | ||
| except Exception: # pylint: disable=broad-exception-caught | ||
| profile = os.getenv("OCI_CLI_PROFILE", "DEFAULT") | ||
| config = oci.config.from_file(profile_name=profile) | ||
| return oci.secrets.SecretsClient(config) | ||
|
|
||
|
|
||
| def main() -> int: | ||
| try: | ||
| load_dotenv(".env.test") | ||
| secret_id = os.getenv("OCI_SECRET_OCID") or settings.oci_secret_ocid | ||
| if not secret_id: | ||
| raise ValueError( | ||
| "Missing OCI secret id. Set OCI_SECRET_OCID in .env.test " | ||
| "or run with ENV=dev/prod so settings.oci_secret_ocid is available." | ||
| ) | ||
| key_name = os.getenv("OCI_BUCKET_KEY_NAME", "portfolio_bucket_name") | ||
|
|
||
| client = _make_secrets_client() | ||
| bundle_response = client.get_secret_bundle(secret_id=secret_id) | ||
| if bundle_response is None or bundle_response.data is None: | ||
| raise ValueError(f"Secret bundle is empty for {secret_id}") | ||
| bundle = cast(SecretBundle, bundle_response.data) | ||
| if bundle.secret_bundle_content is None: | ||
| raise ValueError(f"Secret bundle content is empty for {secret_id}") | ||
| base64_secret_content = bundle.secret_bundle_content.content | ||
| decoded = base64.b64decode(base64_secret_content).decode("utf-8") | ||
| secret_data = json.loads(decoded) | ||
|
|
||
| if key_name not in secret_data: | ||
| print(f"[ERROR] Key not found in vault secret: {key_name}") | ||
| print(f"Available keys: {sorted(secret_data.keys())}") | ||
| return 1 | ||
|
|
||
| print(f"Vault key found: {key_name}") | ||
| print(f"Bucket name: {secret_data[key_name]}") | ||
| return 0 | ||
| except Exception as exc: # pylint: disable=broad-exception-caught | ||
| print(f"[ERROR] {exc}") | ||
| return 1 | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| sys.exit(main()) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| from __future__ import annotations | ||
|
|
||
| import os | ||
| import sys | ||
|
|
||
| import boto3 | ||
| from botocore.client import BaseClient | ||
| from botocore.config import Config | ||
| from botocore.exceptions import ClientError | ||
| from dotenv import load_dotenv | ||
|
|
||
|
|
||
| def _required_env(name: str) -> str: | ||
| value = os.getenv(name, "").strip() | ||
| if not value: | ||
| raise ValueError(f"Missing required env: {name}") | ||
| return value | ||
|
|
||
|
|
||
| def _make_s3_client() -> BaseClient: | ||
| load_dotenv(".env.test") | ||
| access_key = _required_env("OCI_ACCESS_KEY_ID") | ||
| secret_key = _required_env("OCI_SECRET_ACCESS_KEY") | ||
| endpoint_url = _required_env("OCI_S3_COMPAT_ENDPOINT") | ||
| region = os.getenv("OCI_REGION", "ap-chuncheon-1") | ||
|
|
||
| # Force path-style for maximum compatibility with OCI S3 endpoint. | ||
| config = Config(s3={"addressing_style": "path"}) | ||
| return boto3.client( | ||
| "s3", | ||
| region_name=region, | ||
| endpoint_url=endpoint_url, | ||
| aws_access_key_id=access_key, | ||
| aws_secret_access_key=secret_key, | ||
| config=config, | ||
| ) | ||
|
|
||
|
|
||
| def main() -> int: | ||
| try: | ||
| s3 = _make_s3_client() | ||
| target_bucket = _required_env("S3_PORTFOLIO_BUCKET_NAME") | ||
|
|
||
| buckets = s3.list_buckets().get("Buckets", []) | ||
| bucket_names = [bucket["Name"] for bucket in buckets] | ||
| print("Connected to OCI S3-compatible endpoint") | ||
| print(f"Visible buckets: {bucket_names}") | ||
|
|
||
| s3.head_bucket(Bucket=target_bucket) | ||
| print(f"Target bucket is reachable: {target_bucket}") | ||
| return 0 | ||
| except ValueError as exc: | ||
| print(f"[CONFIG ERROR] {exc}") | ||
| except ClientError as exc: | ||
| print(f"[OCI ERROR] {exc}") | ||
| except Exception as exc: # pylint: disable=broad-exception-caught | ||
| print(f"[UNEXPECTED ERROR] {exc}") | ||
| return 1 | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| sys.exit(main()) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,28 +1,56 @@ | ||
| from pydantic import BaseSettings | ||
| from pydantic import Field | ||
|
|
||
| from wacruit.src.secrets import OCISecretManager | ||
| from wacruit.src.settings import settings | ||
|
|
||
| _REQUIRED_OBJECT_STORAGE_SECRET_KEYS = { | ||
| "object_storage_bucket_name": "bucket_name", | ||
| "object_storage_region": "region", | ||
| "object_storage_endpoint_url": "endpoint_url", | ||
| "object_storage_access_key_id": "access_key_id", | ||
| "object_storage_secret_access_key": "secret_access_key", | ||
| } | ||
|
|
||
| class S3PortfolioConfig(BaseSettings): | ||
|
|
||
| class StorageConfig(BaseSettings): | ||
| bucket_name: str = "" | ||
| region: str = "ap-northeast-2" | ||
| endpoint_url: str | None = None | ||
| access_key_id: str | None = None | ||
| secret_access_key: str | None = None | ||
| addressing_style: str = "path" | ||
|
|
||
| class Config: | ||
| class Config(BaseSettings.Config): | ||
| case_sensitive = False | ||
| env_prefix = "S3_PORTFOLIO_" | ||
|
|
||
| env_prefix = "OBJECT_STORAGE_" | ||
| env_file = settings.env_files | ||
|
|
||
| def __init__(self): | ||
| super().__init__() | ||
| aws_secrets = OCISecretManager() | ||
| if aws_secrets.is_available(): | ||
| self.bucket_name = aws_secrets.get_secret("portfolio_bucket_name") | ||
| secret_manager = OCISecretManager() | ||
| if secret_manager.is_available(): | ||
| self._load_from_vault(secret_manager) | ||
|
|
||
| def _load_from_vault(self, secret_manager: OCISecretManager) -> None: | ||
| missing_keys: list[str] = [] | ||
| for secret_key, attr_name in _REQUIRED_OBJECT_STORAGE_SECRET_KEYS.items(): | ||
| try: | ||
| value = secret_manager.get_secret(secret_key) | ||
| except KeyError: | ||
| missing_keys.append(secret_key) | ||
| continue | ||
|
|
||
| if not value: | ||
| missing_keys.append(secret_key) | ||
| continue | ||
|
|
||
| setattr(self, attr_name, value) | ||
|
|
||
| @property | ||
| def bucket_region(self) -> str: | ||
| return "ap-northeast-2" | ||
| if missing_keys: | ||
| raise ValueError( | ||
| "Missing required object storage secret keys in OCI Vault: " | ||
| + ", ".join(missing_keys) | ||
| ) | ||
|
Comment on lines
28
to
+53
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Description: OCISecretManager.get_secret 메서드의 에러 처리 확인
ast-grep --pattern $'def get_secret(self, $key) -> str:
$$$
}'Repository: wafflestudio/wacruit-server Length of output: 53 🏁 Script executed: find . -name "secrets.py" -type f | head -20Repository: wafflestudio/wacruit-server Length of output: 95 🏁 Script executed: rg "class OCISecretManager" -A 20Repository: wafflestudio/wacruit-server Length of output: 1436 🏁 Script executed: cat wacruit/src/apps/portfolio/file/aws/config.py | head -40Repository: wafflestudio/wacruit-server Length of output: 1246 🏁 Script executed: rg "def get_secret" wacruit/src/secrets.py -A 15Repository: wafflestudio/wacruit-server Length of output: 602 🏁 Script executed: cat wacruit/src/secrets.py | tail -50Repository: wafflestudio/wacruit-server Length of output: 1742 Vault의 키 누락 시
각 🤖 Prompt for AI Agents |
||
|
|
||
|
|
||
| s3_config = S3PortfolioConfig() | ||
| storage_config = StorageConfig() | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
기본 키 이름이
StorageConfig와 불일치할 수 있음기본값
portfolio_bucket_name이StorageConfig에서 사용하는object_storage_bucket_name과 다릅니다. 이 스크립트가 새로운 스토리지 설정을 검증하기 위한 것이라면 기본값을object_storage_bucket_name으로 변경하는 것이 일관성 있습니다.🔧 수정 제안
📝 Committable suggestion
🤖 Prompt for AI Agents