AmmarFahmy
adding all files
105b369
import json
from pathlib import Path
from typing import Optional, Any, Dict, List
from phi.aws.api_client import AwsApiClient
from phi.aws.resource.base import AwsResource
from phi.cli.console import print_info
from phi.utils.log import logger
class SecretsManager(AwsResource):
"""
Reference:
- https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/secretsmanager.html
"""
resource_type: Optional[str] = "Secret"
service_name: str = "secretsmanager"
# The name of the secret.
name: str
client_request_token: Optional[str] = None
# The description of the secret.
description: Optional[str] = None
kms_key_id: Optional[str] = None
# The binary data to encrypt and store in the new version of the secret.
# We recommend that you store your binary data in a file and then pass the contents of the file as a parameter.
secret_binary: Optional[bytes] = None
# The text data to encrypt and store in this new version of the secret.
# We recommend you use a JSON structure of key/value pairs for your secret value.
# Either SecretString or SecretBinary must have a value, but not both.
secret_string: Optional[str] = None
# A list of tags to attach to the secret.
tags: Optional[List[Dict[str, str]]] = None
# A list of Regions and KMS keys to replicate secrets.
add_replica_regions: Optional[List[Dict[str, str]]] = None
# Specifies whether to overwrite a secret with the same name in the destination Region.
force_overwrite_replica_secret: Optional[str] = None
# Read secret key/value pairs from yaml files
secret_files: Optional[List[Path]] = None
# Read secret key/value pairs from yaml files in a directory
secrets_dir: Optional[Path] = None
# Force delete the secret without recovery
force_delete: Optional[bool] = True
# Provided by api on create
secret_arn: Optional[str] = None
secret_name: Optional[str] = None
secret_value: Optional[dict] = None
cached_secret: Optional[Dict[str, Any]] = None
def read_secrets_from_files(self) -> Dict[str, Any]:
"""Reads secrets from files"""
from phi.utils.yaml_io import read_yaml_file
secret_dict: Dict[str, Any] = {}
if self.secret_files:
for f in self.secret_files:
_s = read_yaml_file(f)
if _s is not None:
secret_dict.update(_s)
if self.secrets_dir:
for f in self.secrets_dir.glob("*.yaml"):
_s = read_yaml_file(f)
if _s is not None:
secret_dict.update(_s)
for f in self.secrets_dir.glob("*.yml"):
_s = read_yaml_file(f)
if _s is not None:
secret_dict.update(_s)
return secret_dict
def _create(self, aws_client: AwsApiClient) -> bool:
"""Creates the SecretsManager
Args:
aws_client: The AwsApiClient for the current secret
"""
print_info(f"Creating {self.get_resource_type()}: {self.get_resource_name()}")
# Step 1: Read secrets from files
secret_dict: Dict[str, Any] = self.read_secrets_from_files()
# Step 2: Add secret_string if provided
if self.secret_string is not None:
secret_dict.update(json.loads(self.secret_string))
# Step 3: Build secret_string
secret_string: Optional[str] = json.dumps(secret_dict) if len(secret_dict) > 0 else None
# Step 4: Build SecretsManager configuration
# create a dict of args which are not null, otherwise aws type validation fails
not_null_args: Dict[str, Any] = {}
if self.client_request_token:
not_null_args["ClientRequestToken"] = self.client_request_token
if self.description:
not_null_args["Description"] = self.description
if self.kms_key_id:
not_null_args["KmsKeyId"] = self.kms_key_id
if self.secret_binary:
not_null_args["SecretBinary"] = self.secret_binary
if secret_string:
not_null_args["SecretString"] = secret_string
if self.tags:
not_null_args["Tags"] = self.tags
if self.add_replica_regions:
not_null_args["AddReplicaRegions"] = self.add_replica_regions
if self.force_overwrite_replica_secret:
not_null_args["ForceOverwriteReplicaSecret"] = self.force_overwrite_replica_secret
# Step 3: Create SecretsManager
service_client = self.get_service_client(aws_client)
try:
created_resource = service_client.create_secret(
Name=self.name,
**not_null_args,
)
logger.debug(f"SecretsManager: {created_resource}")
# Validate SecretsManager creation
self.secret_arn = created_resource.get("ARN", None)
self.secret_name = created_resource.get("Name", None)
logger.debug(f"secret_arn: {self.secret_arn}")
logger.debug(f"secret_name: {self.secret_name}")
if self.secret_arn is not None:
self.cached_secret = secret_dict
self.active_resource = created_resource
return True
except Exception as e:
logger.error(f"{self.get_resource_type()} could not be created.")
logger.error(e)
return False
def _read(self, aws_client: AwsApiClient) -> Optional[Any]:
"""Returns the SecretsManager
Args:
aws_client: The AwsApiClient for the current secret
"""
logger.debug(f"Reading {self.get_resource_type()}: {self.get_resource_name()}")
from botocore.exceptions import ClientError
service_client = self.get_service_client(aws_client)
try:
describe_response = service_client.describe_secret(SecretId=self.name)
logger.debug(f"SecretsManager: {describe_response}")
self.secret_arn = describe_response.get("ARN", None)
self.secret_name = describe_response.get("Name", None)
describe_response.get("DeletedDate", None)
logger.debug(f"secret_arn: {self.secret_arn}")
logger.debug(f"secret_name: {self.secret_name}")
# logger.debug(f"secret_deleted_date: {secret_deleted_date}")
if self.secret_arn is not None:
# print_info(f"SecretsManager available: {self.name}")
self.active_resource = describe_response
except ClientError as ce:
logger.debug(f"ClientError: {ce}")
except Exception as e:
logger.error(f"Error reading {self.get_resource_type()}.")
logger.error(e)
return self.active_resource
def _delete(self, aws_client: AwsApiClient) -> bool:
"""Deletes the SecretsManager
Args:
aws_client: The AwsApiClient for the current secret
"""
print_info(f"Deleting {self.get_resource_type()}: {self.get_resource_name()}")
service_client = self.get_service_client(aws_client)
self.active_resource = None
self.secret_value = None
try:
delete_response = service_client.delete_secret(
SecretId=self.name, ForceDeleteWithoutRecovery=self.force_delete
)
logger.debug(f"SecretsManager: {delete_response}")
return True
except Exception as e:
logger.error(f"{self.get_resource_type()} could not be deleted.")
logger.error("Please try again or delete resources manually.")
logger.error(e)
return False
def _update(self, aws_client: AwsApiClient) -> bool:
"""Update SecretsManager"""
print_info(f"Updating {self.get_resource_type()}: {self.get_resource_name()}")
# Initialize final secret_dict
secret_dict: Dict[str, Any] = {}
# Step 1: Read secrets from AWS SecretsManager
existing_secret_dict = self.get_secrets_as_dict()
# logger.debug(f"existing_secret_dict: {existing_secret_dict}")
if existing_secret_dict is not None:
secret_dict.update(existing_secret_dict)
# Step 2: Read secrets from files
new_secret_dict: Dict[str, Any] = self.read_secrets_from_files()
if len(new_secret_dict) > 0:
secret_dict.update(new_secret_dict)
# Step 3: Add secret_string is provided
if self.secret_string is not None:
secret_dict.update(json.loads(self.secret_string))
# Step 3: Update AWS SecretsManager
service_client = self.get_service_client(aws_client)
self.active_resource = None
self.secret_value = None
try:
create_response = service_client.update_secret(
SecretId=self.name,
SecretString=json.dumps(secret_dict),
)
logger.debug(f"SecretsManager: {create_response}")
return True
except Exception as e:
logger.error(f"{self.get_resource_type()} could not be Updated.")
logger.error(e)
return False
def get_secrets_as_dict(self, aws_client: Optional[AwsApiClient] = None) -> Optional[Dict[str, Any]]:
"""Get secret value
Args:
aws_client: The AwsApiClient for the current secret
"""
from botocore.exceptions import ClientError
if self.cached_secret is not None:
return self.cached_secret
logger.debug(f"Getting {self.get_resource_type()}: {self.get_resource_name()}")
client: AwsApiClient = aws_client or self.get_aws_client()
service_client = self.get_service_client(client)
try:
secret_value = service_client.get_secret_value(SecretId=self.name)
# logger.debug(f"SecretsManager: {secret_value}")
if secret_value is None:
logger.warning(f"Secret Empty: {self.name}")
return None
self.secret_value = secret_value
self.secret_arn = secret_value.get("ARN", None)
self.secret_name = secret_value.get("Name", None)
secret_string = secret_value.get("SecretString", None)
if secret_string is not None:
self.cached_secret = json.loads(secret_string)
return self.cached_secret
secret_binary = secret_value.get("SecretBinary", None)
if secret_binary is not None:
self.cached_secret = json.loads(secret_binary.decode("utf-8"))
return self.cached_secret
except ClientError as ce:
logger.debug(f"ClientError: {ce}")
except Exception as e:
logger.error(f"Error reading {self.get_resource_type()}.")
logger.error(e)
return None
def get_secret_value(self, secret_name: str, aws_client: Optional[AwsApiClient] = None) -> Optional[Any]:
secret_dict = self.get_secrets_as_dict(aws_client=aws_client)
if secret_dict is not None:
return secret_dict.get(secret_name, None)
return None