From 27682765e7e4a36ff9a691b23a75f4fb066ee299 Mon Sep 17 00:00:00 2001 From: d10s <79284025+D10S0VSkY-OSS@users.noreply.github.com> Date: Wed, 27 Dec 2023 21:48:11 +0100 Subject: [PATCH 01/18] =?UTF-8?q?=F0=9F=94=A7refactor:=20aws=20account=20a?= =?UTF-8?q?dd=20filter=20and=20extra=20vairables=20params?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/aws/api/container/create.py | 9 +- sld-api-backend/src/aws/api/container/get.py | 14 +- sld-api-backend/src/aws/api/v1/aws.py | 2 +- .../src/aws/domain/entities/aws.py | 43 +++-- .../src/aws/infrastructure/models.py | 5 +- .../src/aws/infrastructure/repositories.py | 150 +++++++++--------- .../src/azure/infrastructure/models.py | 3 +- .../src/deploy/api/container/deploy/get.py | 1 - .../src/deploy/infrastructure/repositories.py | 4 +- .../src/gcp/infrastructure/models.py | 3 +- .../worker/security/providers_credentials.py | 93 +++-------- 11 files changed, 144 insertions(+), 183 deletions(-) diff --git a/sld-api-backend/src/aws/api/container/create.py b/sld-api-backend/src/aws/api/container/create.py index d33358ec..5a2f2e00 100644 --- a/sld-api-backend/src/aws/api/container/create.py +++ b/sld-api-backend/src/aws/api/container/create.py @@ -22,13 +22,16 @@ async def create_new_aws_profile( status_code=409, detail="The squad or environment field must have a value that is not a string.", ) - db_aws_account = crud_aws.get_squad_aws_profile( - db=db, squad=aws.squad, environment=aws.environment + filters = schemas_aws.AwsAccountFilter() + filters.squad = aws.squad + filters.environment = aws.environment + db_aws_account = crud_aws.get_all_aws_profile( + db=db, filters=filters ) if db_aws_account: raise HTTPException(status_code=409, detail="Account already exists") try: - result = crud_aws.create_aws_profile(db=db, aws=aws) + crud_aws.create_aws_profile(db=db, aws=aws) crud_activity.create_activity_log( db=db, username=current_user.username, diff --git a/sld-api-backend/src/aws/api/container/get.py b/sld-api-backend/src/aws/api/container/get.py index 10bf4462..0986bf58 100644 --- a/sld-api-backend/src/aws/api/container/get.py +++ b/sld-api-backend/src/aws/api/container/get.py @@ -2,18 +2,20 @@ from sqlalchemy.orm import Session from src.aws.infrastructure import repositories as crud_aws +from src.aws.domain.entities import aws as schemas_aws from src.shared.security import deps from src.users.domain.entities import users as schemas_users from src.users.infrastructure import repositories as crud_users async def get_all_aws_accounts( - current_user: schemas_users.User = Depends(deps.get_current_active_user), + skip: int = 0, + limit: int = 100, db: Session = Depends(deps.get_db), + current_user: schemas_users.User = Depends(deps.get_current_active_user), + filters: schemas_aws.AwsAccountFilter = Depends(schemas_aws.AwsAccountFilter), + ): - # Check if the user has privileges if not crud_users.is_master(db, current_user): - return crud_aws.get_squad_aws_profile( - db=db, squad=current_user.squad, environment=None - ) - return crud_aws.get_all_aws_profile(db=db) + filters.squad = current_user.squad + return crud_aws.get_all_aws_profile(db=db, filters=filters, skip=skip, limit=limit) diff --git a/sld-api-backend/src/aws/api/v1/aws.py b/sld-api-backend/src/aws/api/v1/aws.py index 649c03f4..0c15f0f1 100644 --- a/sld-api-backend/src/aws/api/v1/aws.py +++ b/sld-api-backend/src/aws/api/v1/aws.py @@ -17,7 +17,7 @@ async def create_new_aws_profile( @router.get("/", status_code=200, response_model=list[schemas_aws.AwsAccountResponse]) async def get_all_aws_accounts( - get_aws_profile: schemas_aws.AwsAsumeProfile = Depends(get.get_all_aws_accounts), + get_aws_profile: schemas_aws.AwsAccountResponse = Depends(get.get_all_aws_accounts), ): return get_aws_profile diff --git a/sld-api-backend/src/aws/domain/entities/aws.py b/sld-api-backend/src/aws/domain/entities/aws.py index 6cbfe477..6b486ba0 100644 --- a/sld-api-backend/src/aws/domain/entities/aws.py +++ b/sld-api-backend/src/aws/domain/entities/aws.py @@ -1,23 +1,19 @@ -from typing import Optional +from typing import Optional, Dict, Any -from pydantic import BaseModel, Field, constr +from pydantic import BaseModel, constr class AwsBase(BaseModel): squad: constr(strip_whitespace=True) environment: constr(strip_whitespace=True) access_key_id: constr(strip_whitespace=True) - secret_access_key: Optional[constr(strip_whitespace=True)] = Field( - None, example="string" - ) + secret_access_key: constr(strip_whitespace=True) default_region: constr(strip_whitespace=True) + extra_variables: Optional[Dict[str, Any]] = None - default_region: constr(strip_whitespace=True) class AwsAsumeProfile(AwsBase): - profile_name: Optional[constr(strip_whitespace=True)] = None role_arn: Optional[constr(strip_whitespace=True)] = None - source_profile: Optional[constr(strip_whitespace=True)] = None class Aws(AwsBase): @@ -26,10 +22,31 @@ class Aws(AwsBase): class Config: from_attributes = True + class AwsAccountResponse(BaseModel): id: int - squad: constr(strip_whitespace=True) - environment: constr(strip_whitespace=True) - profile_name: Optional[constr(strip_whitespace=True)] = None - role_arn: Optional[constr(strip_whitespace=True)] = None - source_profile: Optional[constr(strip_whitespace=True)] = None \ No newline at end of file + squad: str + environment: str + default_region: Optional[str] + role_arn: Optional[str] + extra_variables: Optional[Dict[str, Any]] + + class Config: + from_attributes = True + + +class AwsAccountResponseRepo(AwsAccountResponse): + access_key_id: str + secret_access_key: str + + class Config: + from_attributes = True + + +class AwsAccountFilter(BaseModel): + id: Optional[int] = None + squad: Optional[str] = None + access_key_id: Optional[str] = None + environment: Optional[str] = None + default_region: Optional[str] = None + role_arn: Optional[str] = None diff --git a/sld-api-backend/src/aws/infrastructure/models.py b/sld-api-backend/src/aws/infrastructure/models.py index a84abb38..6c34e3d5 100644 --- a/sld-api-backend/src/aws/infrastructure/models.py +++ b/sld-api-backend/src/aws/infrastructure/models.py @@ -1,7 +1,7 @@ import datetime from config.database import Base -from sqlalchemy import Column, DateTime, Integer, String, UniqueConstraint +from sqlalchemy import Column, DateTime, Integer, String, UniqueConstraint, JSON class Aws_provider(Base): @@ -12,8 +12,9 @@ class Aws_provider(Base): access_key_id = Column(String(200), nullable=False) secret_access_key = Column(String(200), nullable=False) default_region = Column(String(200)) - profile_name = Column(String(200), nullable=False) + profile_name = Column(String(200), nullable=True) role_arn = Column(String(200), nullable=True) source_profile = Column(String(200), nullable=True) + extra_variables = Column(JSON, nullable=True) created_at = Column(DateTime, default=datetime.datetime.now()) __table_args__ = (UniqueConstraint("squad", "environment"),) diff --git a/sld-api-backend/src/aws/infrastructure/repositories.py b/sld-api-backend/src/aws/infrastructure/repositories.py index 22497605..ea83e6e7 100644 --- a/sld-api-backend/src/aws/infrastructure/repositories.py +++ b/sld-api-backend/src/aws/infrastructure/repositories.py @@ -1,10 +1,13 @@ import datetime +from typing import List from sqlalchemy.orm import Session +from sqlalchemy import desc, or_ + import src.aws.infrastructure.models as models from src.aws.domain.entities import aws as schemas_aws -from src.shared.security.vault import vault_decrypt, vault_encrypt +from src.shared.security.vault import vault_encrypt @vault_encrypt @@ -15,35 +18,21 @@ def encrypt(secreto): raise err -@vault_decrypt -def decrypt(secreto): - try: - return secreto - except Exception as err: - raise err - - def create_aws_profile(db: Session, aws: schemas_aws.AwsAsumeProfile): encrypt_access_key_id = encrypt(aws.access_key_id) encrypt_secret_access_key = encrypt(aws.secret_access_key) + encrypted_extra_variables = {key: encrypt(val) for key, val in aws.extra_variables.items()} if aws.extra_variables else None + db_aws = models.Aws_provider( access_key_id=encrypt_access_key_id, secret_access_key=encrypt_secret_access_key, environment=aws.environment, default_region=aws.default_region, - profile_name=aws.profile_name, role_arn=aws.role_arn, - source_profile=aws.source_profile, + extra_variables=encrypted_extra_variables, created_at=datetime.datetime.now(), squad=aws.squad, ) - check_None = [None, "string"] - if db_aws.role_arn in check_None: - db_aws.role_arn = "" - if db_aws.profile_name in check_None: - db_aws.profile_name = "" - if db_aws.source_profile in check_None: - db_aws.source_profile = "" try: db.add(db_aws) db.commit() @@ -53,84 +42,89 @@ def create_aws_profile(db: Session, aws: schemas_aws.AwsAsumeProfile): raise err -def get_credentials_aws_profile(db: Session, environment: str, squad: str): - get_access_key = ( - db.query(models.Aws_provider.access_key_id) - .filter(models.Aws_provider.environment == environment) - .filter(models.Aws_provider.squad == squad) - .first() - ) - get_secret_access_key = ( - db.query(models.Aws_provider.secret_access_key) - .filter(models.Aws_provider.environment == environment) - .filter(models.Aws_provider.squad == squad) - .first() - ) - default_region = ( - db.query(models.Aws_provider.default_region) - .filter(models.Aws_provider.environment == environment) - .filter(models.Aws_provider.squad == squad) - .first() - ) - profile_name = ( - db.query(models.Aws_provider.profile_name) +def get_credentials_aws_profile(db: Session, environment: str, squad: str) -> schemas_aws.AwsAccountResponseRepo: + aws_provider_data = ( + db.query(models.Aws_provider) .filter(models.Aws_provider.environment == environment) .filter(models.Aws_provider.squad == squad) .first() ) - role_arn = ( - db.query(models.Aws_provider.role_arn) - .filter(models.Aws_provider.environment == environment) - .filter(models.Aws_provider.squad == squad) - .first() - ) - source_profile = ( - db.query(models.Aws_provider.source_profile) - .filter(models.Aws_provider.environment == environment) - .filter(models.Aws_provider.squad == squad) - .first() + return schemas_aws.AwsAccountResponseRepo( + id=aws_provider_data.id, + squad=aws_provider_data.squad, + environment=aws_provider_data.environment, + access_key_id=aws_provider_data.access_key_id, + secret_access_key=aws_provider_data.secret_access_key, + role_arn=aws_provider_data.role_arn, + default_region=aws_provider_data.default_region, + extra_variables=aws_provider_data.extra_variables, ) - try: - return { - "access_key": decrypt(get_access_key[0]), - "secret_access_key": decrypt(get_secret_access_key[0]), - "default_region": default_region[0], - "profile_name": profile_name[0], - "role_arn": role_arn[0], - "source_profile": source_profile[0], - } - except Exception as err: - raise err -def get_squad_aws_profile(db: Session, squad: str, environment: str): +def get_squad_aws_profile( + db: Session, squad: str, filters: schemas_aws.AwsAccountFilter, skip: int = 0, limit: int = 100 +) -> List[schemas_aws.AwsAccountResponse]: try: - if environment != None: - return ( - db.query(models.Aws_provider) - .filter(models.Aws_provider.squad == squad) - .filter(models.Aws_provider.environment == environment) - .first() - ) - result = [] - for i in squad: - result.extend( - db.query(models.Aws_provider) - .filter(models.Aws_provider.squad == i) - .all() + query = ( + db.query(models.Aws_provider) + .filter(models.Aws_provider.squad == squad) + ) + + for field, value in filters.model_dump().items(): + if value is not None: + query = query.filter(getattr(models.Aws_provider, field) == value) + + results = query.order_by(desc(models.Aws_provider.id)).offset(skip).limit(limit).all() + + aws_profiles = [] + for result in results: + aws_profile = schemas_aws.AwsAccountResponse( + id=result.id, + squad=result.squad, + environment=result.environment, + default_region=result.default_region, + role_arn=result.role_arn, + extra_variables=result.extra_variables, ) - return set(result) + aws_profiles.append(aws_profile) + return aws_profiles except Exception as err: raise err -def get_all_aws_profile(db: Session): +def get_all_aws_profile( + db: Session, filters: schemas_aws.AwsAccountFilter, skip: int = 0, limit: int = 100 +) -> List[schemas_aws.AwsAccountResponse]: try: - return db.query(models.Aws_provider).all() + query = db.query(models.Aws_provider) + + for field, value in filters.model_dump().items(): + if value is not None: + if field == 'squad' and isinstance(value, list): + or_conditions = [getattr(models.Aws_provider, field).like(f"%{v}%") for v in value] + query = query.filter(or_(*or_conditions)) + else: + query = query.filter(getattr(models.Aws_provider, field) == value) + + results = query.order_by(desc(models.Aws_provider.id)).offset(skip).limit(limit).all() + + aws_profiles = [] + for result in results: + aws_profile = schemas_aws.AwsAccountResponse( + id=result.id, + squad=result.squad, + environment=result.environment, + default_region=result.default_region, + role_arn=result.role_arn, + extra_variables=result.extra_variables, + ) + aws_profiles.append(aws_profile) + return aws_profiles except Exception as err: raise err + def delete_aws_profile_by_id(db: Session, aws_profile_id: int): try: db.query(models.Aws_provider).filter( diff --git a/sld-api-backend/src/azure/infrastructure/models.py b/sld-api-backend/src/azure/infrastructure/models.py index 4dfde48a..87ed2e50 100644 --- a/sld-api-backend/src/azure/infrastructure/models.py +++ b/sld-api-backend/src/azure/infrastructure/models.py @@ -1,7 +1,7 @@ import datetime from config.database import Base -from sqlalchemy import Column, DateTime, Integer, String, UniqueConstraint +from sqlalchemy import Column, DateTime, Integer, String, UniqueConstraint, JSON class Azure_provider(Base): @@ -13,5 +13,6 @@ class Azure_provider(Base): client_secret = Column(String(200), nullable=False) subscription_id = Column(String(200), nullable=False) tenant_id = Column(String(200), nullable=False) + extra_variables = Column(JSON, nullable=True) created_at = Column(DateTime, default=datetime.datetime.now()) __table_args__ = (UniqueConstraint("squad", "environment"),) diff --git a/sld-api-backend/src/deploy/api/container/deploy/get.py b/sld-api-backend/src/deploy/api/container/deploy/get.py index c6393921..ced0f3b0 100644 --- a/sld-api-backend/src/deploy/api/container/deploy/get.py +++ b/sld-api-backend/src/deploy/api/container/deploy/get.py @@ -22,7 +22,6 @@ async def get_all_deploys( filters: DeployFilter = Depends(DeployFilter), ) -> List[DeployFilterResponse]: try: - # Si el usuario no es un maestro, aplicar el filtro de escuadrón if not crud_users.is_master(db, current_user): filters.squad = current_user.squad diff --git a/sld-api-backend/src/deploy/infrastructure/repositories.py b/sld-api-backend/src/deploy/infrastructure/repositories.py index ee991fd9..7f803f70 100644 --- a/sld-api-backend/src/deploy/infrastructure/repositories.py +++ b/sld-api-backend/src/deploy/infrastructure/repositories.py @@ -210,17 +210,15 @@ def get_deploys(db: Session, filters: DeployFilter, skip: int = 0, limit: int = results = query.order_by(desc(models.Deploy.id)).offset(skip).limit(limit).all() - # Crear una lista de DeployFilterResponse a partir de los resultados deploy_responses = [] for deploy, icon_path in results: deploy_dict = deploy.__dict__ - deploy_dict['icon_path'] = icon_path # Agregar el icon_path al diccionario + deploy_dict['icon_path'] = icon_path deploy_responses.append(DeployFilterResponse(**deploy_dict)) return deploy_responses - class MetricsFetcher: def __init__(self, db: Session): self.db = db diff --git a/sld-api-backend/src/gcp/infrastructure/models.py b/sld-api-backend/src/gcp/infrastructure/models.py index 2dbbaca0..138c9939 100644 --- a/sld-api-backend/src/gcp/infrastructure/models.py +++ b/sld-api-backend/src/gcp/infrastructure/models.py @@ -1,7 +1,7 @@ import datetime from config.database import Base -from sqlalchemy import Column, DateTime, Integer, String, UniqueConstraint +from sqlalchemy import Column, DateTime, Integer, String, UniqueConstraint, JSON class Gcloud_provider(Base): @@ -10,5 +10,6 @@ class Gcloud_provider(Base): environment = Column(String(200), nullable=False) squad = Column(String(200), nullable=False) gcloud_keyfile_json = Column(String(5000), nullable=False) + extra_variables = Column(JSON, nullable=True) created_at = Column(DateTime, default=datetime.datetime.now()) __table_args__ = (UniqueConstraint("squad", "environment"),) diff --git a/sld-api-backend/src/worker/security/providers_credentials.py b/sld-api-backend/src/worker/security/providers_credentials.py index a9ee9bff..f3536eeb 100644 --- a/sld-api-backend/src/worker/security/providers_credentials.py +++ b/sld-api-backend/src/worker/security/providers_credentials.py @@ -5,8 +5,17 @@ import os from config.api import settings +from src.shared.security.vault import vault_decrypt +@vault_decrypt +def decrypt(secreto): + try: + return secreto + except Exception as err: + raise err + + class SecretsProviders: def __init__(self, secret_provider: dict) -> None: self.secret_provider = secret_provider @@ -25,74 +34,6 @@ def createLocalFolder(dir_path: str): raise -def aws_config(secreto): - try: - config = configparser.ConfigParser(strict=False) - # Check if pass me profile - if secreto.get("profile_name"): - # Create folder in home user - createLocalFolder(settings.AWS_CONGIG_DEFAULT_FOLDER) - # Read config - config.read(settings.AWS_SHARED_CONFIG_FILE) - profile_name = secreto.get("profile_name") - if not config.has_section(f"profile {profile_name}"): - config.add_section(f"profile {profile_name}") - config.set(f"profile {profile_name}", "role_arn", secreto.get("role_arn")) - config.set( - f"profile {profile_name}", - "region", - secreto.get("default_region"), - ) - config.set( - f"profile {profile_name}", - "source_profile", - secreto.get("source_profile"), - ) - with open(settings.AWS_SHARED_CONFIG_FILE, "w") as configfile: - config.write(configfile) - logging.info( - "created config done" - ) - del secreto - del profile_name - del configfile - del config - return True - except Exception as err: - return False - logging.warning(err) - - -def aws_credentials(secreto): - try: - config = configparser.ConfigParser(strict=False) - if secreto.get("source_profile"): - config.read(settings.AWS_SHARED_CREDENTIALS_FILE) - source_profile = secreto.get("source_profile") - if not config.has_section(source_profile): - config.add_section(source_profile) - config.set(source_profile, "region", secreto.get("default_region")) - config.set(source_profile, "aws_access_key_id", secreto.get("access_key")) - config.set( - source_profile, - "aws_secret_access_key", - secreto.get("secret_access_key"), - ) - with open(settings.AWS_SHARED_CREDENTIALS_FILE, "w") as credentialsfile: - config.write(credentialsfile) - logging.info( - "created credentials done" - ) - del secreto - del source_profile - del credentialsfile - del config - return True - except Exception as err: - return False - logging.warning(err) - - def secret( stack_name, environment, @@ -102,12 +43,16 @@ def secret( ): if any(i in stack_name.lower() for i in settings.AWS_PREFIX): try: - if not aws_config(secreto) or not aws_credentials(secreto): - os.environ["AWS_ACCESS_KEY_ID"] = secreto.get("access_key") - os.environ["AWS_SECRET_ACCESS_KEY"] = secreto.get("secret_access_key") - logging.info( - f"Set aws account without asume role {squad}, {environment}, {stack_name}, {name}" - ) + os.environ["AWS_ACCESS_KEY_ID"] = decrypt(secreto.get("access_key_id")) + os.environ["AWS_SECRET_ACCESS_KEY"] = decrypt(secreto.get("secret_access_key")) + os.environ["AWS_DEFAULT_REGION"] = secreto.get("default_region") + if secreto.get("role_arn"): + logging.info("Set role_arn for assume role") + os.environ["TF_VAR_role_arn"] = secreto.get("role_arn") + logging.info(f"TF_VAR_role_arn = {secreto.get('role_arn')}") + logging.info( + f'Set aws account {squad}, {environment}, {stack_name}, {secreto.get("default_region")}, {name}' + ) except Exception as err: logging.warning(err) From cf35ff5d6af91be2ce9e8bae8e0635b4dd78d335 Mon Sep 17 00:00:00 2001 From: d10s <79284025+D10S0VSkY-OSS@users.noreply.github.com> Date: Thu, 28 Dec 2023 01:34:24 +0100 Subject: [PATCH 02/18] =?UTF-8?q?=F0=9F=94=A7refactor:=20aws=20add=20updat?= =?UTF-8?q?e=20account?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/aws/api/container/create.py | 3 +- sld-api-backend/src/aws/api/container/get.py | 2 +- .../src/aws/api/container/update.py | 38 +++++++++ sld-api-backend/src/aws/api/v1/aws.py | 13 ++- .../src/aws/domain/entities/aws.py | 15 +++- .../src/aws/infrastructure/repositories.py | 80 +++++++++++-------- .../src/deploy/api/container/deploy/create.py | 2 +- .../src/worker/domain/entities/actions.py | 2 +- .../worker/security/providers_credentials.py | 12 ++- 9 files changed, 118 insertions(+), 49 deletions(-) create mode 100644 sld-api-backend/src/aws/api/container/update.py diff --git a/sld-api-backend/src/aws/api/container/create.py b/sld-api-backend/src/aws/api/container/create.py index 5a2f2e00..7efbefc8 100644 --- a/sld-api-backend/src/aws/api/container/create.py +++ b/sld-api-backend/src/aws/api/container/create.py @@ -31,13 +31,12 @@ async def create_new_aws_profile( if db_aws_account: raise HTTPException(status_code=409, detail="Account already exists") try: - crud_aws.create_aws_profile(db=db, aws=aws) crud_activity.create_activity_log( db=db, username=current_user.username, squad=current_user.squad, action=f"Create AWS account {aws.squad} {aws.environment}", ) - return {"result": f"Create AWS account {aws.squad} {aws.environment}"} + return crud_aws.create_aws_profile(db=db, aws=aws) except Exception as err: raise HTTPException(status_code=400, detail=str(err)) diff --git a/sld-api-backend/src/aws/api/container/get.py b/sld-api-backend/src/aws/api/container/get.py index 0986bf58..0235faa9 100644 --- a/sld-api-backend/src/aws/api/container/get.py +++ b/sld-api-backend/src/aws/api/container/get.py @@ -15,7 +15,7 @@ async def get_all_aws_accounts( current_user: schemas_users.User = Depends(deps.get_current_active_user), filters: schemas_aws.AwsAccountFilter = Depends(schemas_aws.AwsAccountFilter), -): +) -> list[schemas_aws.AwsAccountResponse]: if not crud_users.is_master(db, current_user): filters.squad = current_user.squad return crud_aws.get_all_aws_profile(db=db, filters=filters, skip=skip, limit=limit) diff --git a/sld-api-backend/src/aws/api/container/update.py b/sld-api-backend/src/aws/api/container/update.py new file mode 100644 index 00000000..8e17cdcc --- /dev/null +++ b/sld-api-backend/src/aws/api/container/update.py @@ -0,0 +1,38 @@ +from fastapi import Depends, HTTPException +from sqlalchemy.orm import Session + +from src.activityLogs.infrastructure import repositories as crud_activity +from src.aws.domain.entities import aws as schemas_aws +from src.aws.infrastructure import repositories as crud_aws +from src.shared.security import deps +from src.users.domain.entities import users as schemas_users +from src.users.infrastructure import repositories as crud_users + + +async def update_aws_account( + deploy_id: int, + aws: schemas_aws.AwsAsumeProfile, + current_user: schemas_users.User = Depends(deps.get_current_active_user), + db: Session = Depends(deps.get_db), +): + # Check if the user has privileges + if not crud_users.is_master(db, current_user): + raise HTTPException(status_code=403, detail="Not enough permissions") + if "string" in [aws.squad, aws.environment]: + raise HTTPException( + status_code=409, + detail="The squad or environment field must have a value that is not a string.", + ) + + try: + filters = schemas_aws.AwsAccountFilter() + filters.id = deploy_id + db_aws_account = crud_aws.get_all_aws_profile(db=db, filters=filters) + + if db_aws_account: + # If the AWS profile already exists, perform an update + aws_id_to_update = db_aws_account[0].id # Assuming you want to update the first matching profile + return await crud_aws.update_aws_profile(db=db, aws_id=aws_id_to_update, updated_aws=aws) + + except Exception as err: + raise HTTPException(status_code=400, detail=str(err)) diff --git a/sld-api-backend/src/aws/api/v1/aws.py b/sld-api-backend/src/aws/api/v1/aws.py index 0c15f0f1..373ff096 100644 --- a/sld-api-backend/src/aws/api/v1/aws.py +++ b/sld-api-backend/src/aws/api/v1/aws.py @@ -1,13 +1,13 @@ from fastapi import APIRouter, Depends -from src.aws.api.container import create, delete, get +from src.aws.api.container import create, delete, get, update from src.aws.domain.entities import aws as schemas_aws router = APIRouter() @router.post("/", status_code=200) -async def create_new_aws_profile( +async def create_new_aws_account( create_aws_profile: schemas_aws.AwsAsumeProfile = Depends( create.create_new_aws_profile ), @@ -15,6 +15,15 @@ async def create_new_aws_profile( return create_aws_profile +@router.patch("/{aws_account_id}", status_code=200) +async def update_aws_account( + update_account: schemas_aws.AwsAsumeProfile = Depends( + update.update_aws_account + ), +): + return update_account + + @router.get("/", status_code=200, response_model=list[schemas_aws.AwsAccountResponse]) async def get_all_aws_accounts( get_aws_profile: schemas_aws.AwsAccountResponse = Depends(get.get_all_aws_accounts), diff --git a/sld-api-backend/src/aws/domain/entities/aws.py b/sld-api-backend/src/aws/domain/entities/aws.py index 6b486ba0..14c52432 100644 --- a/sld-api-backend/src/aws/domain/entities/aws.py +++ b/sld-api-backend/src/aws/domain/entities/aws.py @@ -1,6 +1,6 @@ from typing import Optional, Dict, Any -from pydantic import BaseModel, constr +from pydantic import BaseModel, constr, SecretStr class AwsBase(BaseModel): @@ -23,21 +23,28 @@ class Config: from_attributes = True -class AwsAccountResponse(BaseModel): +class AwsAccountResponseBase(BaseModel): id: int squad: str environment: str default_region: Optional[str] role_arn: Optional[str] - extra_variables: Optional[Dict[str, Any]] class Config: from_attributes = True -class AwsAccountResponseRepo(AwsAccountResponse): +class AwsAccountResponse(AwsAccountResponseBase): + extra_variables: Optional[Dict[str, SecretStr]] + + class Config: + from_attributes = True + + +class AwsAccountResponseRepo(AwsAccountResponseBase): access_key_id: str secret_access_key: str + extra_variables: Optional[Dict[str, Any]] = None class Config: from_attributes = True diff --git a/sld-api-backend/src/aws/infrastructure/repositories.py b/sld-api-backend/src/aws/infrastructure/repositories.py index ea83e6e7..e204c827 100644 --- a/sld-api-backend/src/aws/infrastructure/repositories.py +++ b/sld-api-backend/src/aws/infrastructure/repositories.py @@ -18,7 +18,7 @@ def encrypt(secreto): raise err -def create_aws_profile(db: Session, aws: schemas_aws.AwsAsumeProfile): +def create_aws_profile(db: Session, aws: schemas_aws.AwsAsumeProfile) -> schemas_aws.AwsAccountResponse: encrypt_access_key_id = encrypt(aws.access_key_id) encrypt_secret_access_key = encrypt(aws.secret_access_key) encrypted_extra_variables = {key: encrypt(val) for key, val in aws.extra_variables.items()} if aws.extra_variables else None @@ -37,11 +37,55 @@ def create_aws_profile(db: Session, aws: schemas_aws.AwsAsumeProfile): db.add(db_aws) db.commit() db.refresh(db_aws) - return db_aws + return schemas_aws.AwsAccountResponse( + id=db_aws.id, + squad=db_aws.squad, + environment=db_aws.environment, + default_region=db_aws.default_region, + role_arn=db_aws.role_arn, + extra_variables=db_aws.extra_variables, + ) except Exception as err: raise err +async def update_aws_profile(db: Session, aws_id: int, updated_aws: schemas_aws.AwsAsumeProfile) -> schemas_aws.AwsAccountResponse: + db_aws = db.query(models.Aws_provider).filter(models.Aws_provider.id == aws_id).first() + + if db_aws: + # Update only the fields that are present in the updated_aws object + if updated_aws.access_key_id: + db_aws.access_key_id = encrypt(updated_aws.access_key_id) + if updated_aws.secret_access_key: + db_aws.secret_access_key = encrypt(updated_aws.secret_access_key) + if updated_aws.extra_variables: + db_aws.extra_variables = {key: encrypt(val) for key, val in updated_aws.extra_variables.items()} + + # Update the remaining fields + db_aws.environment = updated_aws.environment + db_aws.default_region = updated_aws.default_region + db_aws.role_arn = updated_aws.role_arn + db_aws.squad = updated_aws.squad + + try: + db.commit() + db.refresh(db_aws) + return schemas_aws.AwsAccountResponse( + id=db_aws.id, + squad=db_aws.squad, + environment=db_aws.environment, + default_region=db_aws.default_region, + role_arn=db_aws.role_arn, + extra_variables=db_aws.extra_variables, + ) + except Exception as err: + raise err + else: + # Handle the case where the specified AWS profile ID doesn't exist + raise ValueError(f"AWS profile with id {aws_id} not found") + + + def get_credentials_aws_profile(db: Session, environment: str, squad: str) -> schemas_aws.AwsAccountResponseRepo: aws_provider_data = ( db.query(models.Aws_provider) @@ -61,37 +105,6 @@ def get_credentials_aws_profile(db: Session, environment: str, squad: str) -> sc ) -def get_squad_aws_profile( - db: Session, squad: str, filters: schemas_aws.AwsAccountFilter, skip: int = 0, limit: int = 100 -) -> List[schemas_aws.AwsAccountResponse]: - try: - query = ( - db.query(models.Aws_provider) - .filter(models.Aws_provider.squad == squad) - ) - - for field, value in filters.model_dump().items(): - if value is not None: - query = query.filter(getattr(models.Aws_provider, field) == value) - - results = query.order_by(desc(models.Aws_provider.id)).offset(skip).limit(limit).all() - - aws_profiles = [] - for result in results: - aws_profile = schemas_aws.AwsAccountResponse( - id=result.id, - squad=result.squad, - environment=result.environment, - default_region=result.default_region, - role_arn=result.role_arn, - extra_variables=result.extra_variables, - ) - aws_profiles.append(aws_profile) - return aws_profiles - except Exception as err: - raise err - - def get_all_aws_profile( db: Session, filters: schemas_aws.AwsAccountFilter, skip: int = 0, limit: int = 100 ) -> List[schemas_aws.AwsAccountResponse]: @@ -124,7 +137,6 @@ def get_all_aws_profile( raise err - def delete_aws_profile_by_id(db: Session, aws_profile_id: int): try: db.query(models.Aws_provider).filter( diff --git a/sld-api-backend/src/deploy/api/container/deploy/create.py b/sld-api-backend/src/deploy/api/container/deploy/create.py index dc69a1f2..123916a8 100644 --- a/sld-api-backend/src/deploy/api/container/deploy/create.py +++ b/sld-api-backend/src/deploy/api/container/deploy/create.py @@ -20,7 +20,7 @@ from src.tasks.infrastructure import repositories as crud_tasks from src.users.domain.entities import users as schemas_users from src.users.infrastructure import repositories as crud_users -from src.worker.domain.entities.worker import DeployParams, DownloadGitRepoParams +from src.worker.domain.entities.worker import DeployParams async def deploy_infra_by_stack_name( diff --git a/sld-api-backend/src/worker/domain/entities/actions.py b/sld-api-backend/src/worker/domain/entities/actions.py index 1752c70b..053b4b27 100644 --- a/sld-api-backend/src/worker/domain/entities/actions.py +++ b/sld-api-backend/src/worker/domain/entities/actions.py @@ -13,4 +13,4 @@ class StructActionsBase(BaseModel): secreto: Dict variables_file: Optional[str] project_path: Optional[str] - task_id: str \ No newline at end of file + task_id: str diff --git a/sld-api-backend/src/worker/security/providers_credentials.py b/sld-api-backend/src/worker/security/providers_credentials.py index f3536eeb..6147001f 100644 --- a/sld-api-backend/src/worker/security/providers_credentials.py +++ b/sld-api-backend/src/worker/security/providers_credentials.py @@ -14,7 +14,13 @@ def decrypt(secreto): return secreto except Exception as err: raise err - + + +def export_environment_variables(dictionary): + if 'extra_variables' in dictionary and isinstance(dictionary['extra_variables'], dict): + for key, value in dictionary['extra_variables'].items(): + os.environ[key] = decrypt(value) + class SecretsProviders: def __init__(self, secret_provider: dict) -> None: @@ -43,6 +49,7 @@ def secret( ): if any(i in stack_name.lower() for i in settings.AWS_PREFIX): try: + export_environment_variables(secreto) os.environ["AWS_ACCESS_KEY_ID"] = decrypt(secreto.get("access_key_id")) os.environ["AWS_SECRET_ACCESS_KEY"] = decrypt(secreto.get("secret_access_key")) os.environ["AWS_DEFAULT_REGION"] = secreto.get("default_region") @@ -76,9 +83,6 @@ def secret( R.export() -# os.environ["GOOGLE_CLOUD_KEYFILE_JSON"] = gcloud_keyfile - - def unsecret(stack_name, environment, squad, name, secreto): if any(i in stack_name.lower() for i in settings.AWS_PREFIX): try: From 2c605b175a64d5af70acbc9cbbe5ce0421095bc6 Mon Sep 17 00:00:00 2001 From: d10s <79284025+D10S0VSkY-OSS@users.noreply.github.com> Date: Thu, 28 Dec 2023 01:55:00 +0100 Subject: [PATCH 03/18] =?UTF-8?q?=F0=9F=90=9Bfix:=20disbale=20pagination?= =?UTF-8?q?=20cloud=20accounts=20and=20add=20edit=20schedule=20when=20dest?= =?UTF-8?q?roy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sld-dashboard/app/base/static/assets/js/pagination.js | 4 ---- sld-dashboard/app/home/routes.py | 4 +--- sld-dashboard/app/home/templates/aws-list.html | 8 ++++---- sld-dashboard/app/home/templates/azure-list.html | 6 +++--- .../app/home/templates/custom-provider-list.html | 6 +++--- sld-dashboard/app/home/templates/deploys-list.html | 6 +++++- sld-dashboard/app/home/templates/gcp-list.html | 6 +++--- 7 files changed, 19 insertions(+), 21 deletions(-) diff --git a/sld-dashboard/app/base/static/assets/js/pagination.js b/sld-dashboard/app/base/static/assets/js/pagination.js index e81c2bc7..edf1049e 100644 --- a/sld-dashboard/app/base/static/assets/js/pagination.js +++ b/sld-dashboard/app/base/static/assets/js/pagination.js @@ -29,10 +29,6 @@ $(document).ready(function(){ displayPage(1); } - // Event Delegation for edit buttons and other interactive elements - $(document).on('click', '.edit-button', function() { - // Aquí iría el código para manejar la edición - }); // Existing search functionality $("#myInput").on("keyup", function() { diff --git a/sld-dashboard/app/home/routes.py b/sld-dashboard/app/home/routes.py index 7ed0e5ab..a750e7d9 100644 --- a/sld-dashboard/app/home/routes.py +++ b/sld-dashboard/app/home/routes.py @@ -1325,8 +1325,6 @@ def list_activity(limit): # Users - - @blueprint.route("/users-new", methods=["GET", "POST"]) @login_required def new_user(): @@ -1580,7 +1578,7 @@ def delete_aws_account(aws_account_id): if response.get("status_code") == 200: flash( - f"Account Deleted" + "Account Deleted" ) elif response.get("status_code") == 409: flash(response["json"].get("detail"), "error") diff --git a/sld-dashboard/app/home/templates/aws-list.html b/sld-dashboard/app/home/templates/aws-list.html index ec68ea70..e0bb93fa 100644 --- a/sld-dashboard/app/home/templates/aws-list.html +++ b/sld-dashboard/app/home/templates/aws-list.html @@ -134,14 +134,14 @@

All aws accounts

- +
  • Next
  • - + +--> {% include 'includes/footer.html' %} @@ -150,6 +150,6 @@

    All aws accounts

    {% block javascripts %} - + {% endblock javascripts %} diff --git a/sld-dashboard/app/home/templates/azure-list.html b/sld-dashboard/app/home/templates/azure-list.html index da16a91a..bb959cb5 100644 --- a/sld-dashboard/app/home/templates/azure-list.html +++ b/sld-dashboard/app/home/templates/azure-list.html @@ -130,14 +130,14 @@

    All azure accounts

    - +
  • Next
  • +--> {% include 'includes/footer.html' %} @@ -146,5 +146,5 @@

    All azure accounts

    {% block javascripts %} - + {% endblock javascripts %} \ No newline at end of file diff --git a/sld-dashboard/app/home/templates/custom-provider-list.html b/sld-dashboard/app/home/templates/custom-provider-list.html index d5885c35..a8511dd9 100644 --- a/sld-dashboard/app/home/templates/custom-provider-list.html +++ b/sld-dashboard/app/home/templates/custom-provider-list.html @@ -126,14 +126,14 @@

    All custom providers accounts

    - +
  • Next
  • +--> {% include 'includes/footer.html' %} @@ -143,5 +143,5 @@

    All custom providers accounts

    {% block javascripts %} - + {% endblock javascripts %} \ No newline at end of file diff --git a/sld-dashboard/app/home/templates/deploys-list.html b/sld-dashboard/app/home/templates/deploys-list.html index 5ba77b90..eb5a0e41 100644 --- a/sld-dashboard/app/home/templates/deploys-list.html +++ b/sld-dashboard/app/home/templates/deploys-list.html @@ -216,7 +216,7 @@

    All Deploys

    - Start: + Apply: {{ deploy.start_time }}
    @@ -260,6 +260,10 @@

    All Deploys

    Clone + + + Schedule + Destroy diff --git a/sld-dashboard/app/home/templates/gcp-list.html b/sld-dashboard/app/home/templates/gcp-list.html index bda398a7..11a5345c 100644 --- a/sld-dashboard/app/home/templates/gcp-list.html +++ b/sld-dashboard/app/home/templates/gcp-list.html @@ -126,14 +126,14 @@

    All gcp accounts

    - +
  • Next
  • +--> {% include 'includes/footer.html' %} @@ -142,5 +142,5 @@

    All gcp accounts

    {% block javascripts %} - + {% endblock javascripts %} \ No newline at end of file From d9e60bec077e28fddc357b637573adb67401e349 Mon Sep 17 00:00:00 2001 From: d10s <79284025+D10S0VSkY-OSS@users.noreply.github.com> Date: Thu, 28 Dec 2023 01:56:20 +0100 Subject: [PATCH 04/18] =?UTF-8?q?=E2=AC=86=20Bump:=20apply=20hotfix=20clou?= =?UTF-8?q?d=20account=20v3.4.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- play-with-sld/kubernetes/k8s/sld-dashboard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/play-with-sld/kubernetes/k8s/sld-dashboard.yml b/play-with-sld/kubernetes/k8s/sld-dashboard.yml index c0820bb6..b8064192 100644 --- a/play-with-sld/kubernetes/k8s/sld-dashboard.yml +++ b/play-with-sld/kubernetes/k8s/sld-dashboard.yml @@ -17,7 +17,7 @@ spec: subdomain: primary containers: - name: sld-dashboard - image: d10s0vsky/sld-dashboard:v3.4.0 + image: d10s0vsky/sld-dashboard:v3.4.1 env: - name: PATH value: "/home/sld/.asdf/shims:/home/sld/.asdf/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" From ac4dacdeeb74793b32af15502aa7f713da6d3d70 Mon Sep 17 00:00:00 2001 From: d10s <79284025+D10S0VSkY-OSS@users.noreply.github.com> Date: Fri, 29 Dec 2023 00:44:45 +0100 Subject: [PATCH 05/18] =?UTF-8?q?=F0=9F=94=A7refactor:=20aws=20backend?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/aws/api/container/create.py | 5 +- .../src/aws/api/container/delete.py | 10 ++- sld-api-backend/src/aws/api/container/get.py | 2 +- .../src/aws/api/container/update.py | 34 ++++---- .../src/aws/domain/entities/aws.py | 8 ++ .../src/aws/infrastructure/models.py | 1 + .../src/aws/infrastructure/repositories.py | 81 +++++++------------ .../src/azure/infrastructure/models.py | 1 + .../custom_providers/infrastructure/models.py | 1 + .../src/deploy/api/container/deploy/create.py | 2 +- .../src/deploy/api/container/deploy/delete.py | 2 +- .../deploy/api/container/deploy/destroy.py | 2 +- .../src/deploy/api/container/deploy/update.py | 2 +- .../src/deploy/api/container/plan/create.py | 2 +- .../src/deploy/api/container/plan/get.py | 2 +- .../src/gcp/infrastructure/models.py | 1 + .../src/shared/helpers/get_data.py | 4 +- 17 files changed, 73 insertions(+), 87 deletions(-) diff --git a/sld-api-backend/src/aws/api/container/create.py b/sld-api-backend/src/aws/api/container/create.py index 7efbefc8..c753e34a 100644 --- a/sld-api-backend/src/aws/api/container/create.py +++ b/sld-api-backend/src/aws/api/container/create.py @@ -14,7 +14,6 @@ async def create_new_aws_profile( current_user: schemas_users.User = Depends(deps.get_current_active_user), db: Session = Depends(deps.get_db), ): - # Check if the user has privileges if not crud_users.is_master(db, current_user): raise HTTPException(status_code=403, detail="Not enough permissions") if "string" in [aws.squad, aws.environment]: @@ -25,7 +24,7 @@ async def create_new_aws_profile( filters = schemas_aws.AwsAccountFilter() filters.squad = aws.squad filters.environment = aws.environment - db_aws_account = crud_aws.get_all_aws_profile( + db_aws_account = await crud_aws.get_all_aws_profile( db=db, filters=filters ) if db_aws_account: @@ -37,6 +36,6 @@ async def create_new_aws_profile( squad=current_user.squad, action=f"Create AWS account {aws.squad} {aws.environment}", ) - return crud_aws.create_aws_profile(db=db, aws=aws) + return await crud_aws.create_aws_profile(db=db, aws=aws) except Exception as err: raise HTTPException(status_code=400, detail=str(err)) diff --git a/sld-api-backend/src/aws/api/container/delete.py b/sld-api-backend/src/aws/api/container/delete.py index 7fb5f7cc..022f1d2b 100644 --- a/sld-api-backend/src/aws/api/container/delete.py +++ b/sld-api-backend/src/aws/api/container/delete.py @@ -3,6 +3,7 @@ from src.activityLogs.infrastructure import repositories as crud_activity from src.aws.infrastructure import repositories as crud_aws +from src.aws.domain.entities import aws as schemas_aws from src.shared.security import deps from src.users.domain.entities import users as schemas_users from src.users.infrastructure import repositories as crud_users @@ -12,12 +13,17 @@ async def aws_account_by_id( aws_account_id: int, current_user: schemas_users.User = Depends(deps.get_current_active_user), db: Session = Depends(deps.get_db), -): +) -> schemas_aws.AwsAsumeProfile: if not crud_users.is_master(db, current_user): raise HTTPException(status_code=403, detail="Not enough permissions") + filters = schemas_aws.AwsAccountFilter() + filters.id = aws_account_id + db_aws_account = await crud_aws.get_all_aws_profile(db=db, filters=filters) + if not db_aws_account: + raise HTTPException(status_code=404, detail="Account not found") - result = crud_aws.delete_aws_profile_by_id(db=db, aws_profile_id=aws_account_id) + result = await crud_aws.delete_aws_profile_by_id(db=db, aws_account_id=aws_account_id) crud_activity.create_activity_log( db=db, username=current_user.username, diff --git a/sld-api-backend/src/aws/api/container/get.py b/sld-api-backend/src/aws/api/container/get.py index 0235faa9..7e78c920 100644 --- a/sld-api-backend/src/aws/api/container/get.py +++ b/sld-api-backend/src/aws/api/container/get.py @@ -18,4 +18,4 @@ async def get_all_aws_accounts( ) -> list[schemas_aws.AwsAccountResponse]: if not crud_users.is_master(db, current_user): filters.squad = current_user.squad - return crud_aws.get_all_aws_profile(db=db, filters=filters, skip=skip, limit=limit) + return await crud_aws.get_all_aws_profile(db=db, filters=filters, skip=skip, limit=limit) diff --git a/sld-api-backend/src/aws/api/container/update.py b/sld-api-backend/src/aws/api/container/update.py index 8e17cdcc..12929b27 100644 --- a/sld-api-backend/src/aws/api/container/update.py +++ b/sld-api-backend/src/aws/api/container/update.py @@ -10,29 +10,25 @@ async def update_aws_account( - deploy_id: int, - aws: schemas_aws.AwsAsumeProfile, + aws_account_id: int, + aws: schemas_aws.AwsAccountUpdate, current_user: schemas_users.User = Depends(deps.get_current_active_user), db: Session = Depends(deps.get_db), -): - # Check if the user has privileges +) -> schemas_aws.AwsAsumeProfile: if not crud_users.is_master(db, current_user): raise HTTPException(status_code=403, detail="Not enough permissions") - if "string" in [aws.squad, aws.environment]: - raise HTTPException( - status_code=409, - detail="The squad or environment field must have a value that is not a string.", - ) - try: filters = schemas_aws.AwsAccountFilter() - filters.id = deploy_id - db_aws_account = crud_aws.get_all_aws_profile(db=db, filters=filters) - - if db_aws_account: - # If the AWS profile already exists, perform an update - aws_id_to_update = db_aws_account[0].id # Assuming you want to update the first matching profile - return await crud_aws.update_aws_profile(db=db, aws_id=aws_id_to_update, updated_aws=aws) - + filters.id = aws_account_id + db_aws_account = await crud_aws.get_all_aws_profile(db=db, filters=filters) + if not db_aws_account: + raise HTTPException(status_code=404, detail="Account not found") + crud_activity.create_activity_log( + db=db, + username=current_user.username, + squad=current_user.squad, + action=f"Update AWS account {aws.squad} {aws.environment}", + ) + return await crud_aws.update_aws_profile(db=db, aws_account_id=aws_account_id, updated_aws=aws) except Exception as err: - raise HTTPException(status_code=400, detail=str(err)) + raise err diff --git a/sld-api-backend/src/aws/domain/entities/aws.py b/sld-api-backend/src/aws/domain/entities/aws.py index 14c52432..d4424746 100644 --- a/sld-api-backend/src/aws/domain/entities/aws.py +++ b/sld-api-backend/src/aws/domain/entities/aws.py @@ -1,3 +1,4 @@ +import datetime from typing import Optional, Dict, Any from pydantic import BaseModel, constr, SecretStr @@ -29,6 +30,8 @@ class AwsAccountResponseBase(BaseModel): environment: str default_region: Optional[str] role_arn: Optional[str] + created_at: Optional[datetime.datetime] = None + updated_at: Optional[datetime.datetime] = None class Config: from_attributes = True @@ -57,3 +60,8 @@ class AwsAccountFilter(BaseModel): environment: Optional[str] = None default_region: Optional[str] = None role_arn: Optional[str] = None + + +class AwsAccountUpdate(AwsAccountFilter): + secret_access_key: Optional[str] = None + extra_variables: Optional[Dict[str, Any]] = None diff --git a/sld-api-backend/src/aws/infrastructure/models.py b/sld-api-backend/src/aws/infrastructure/models.py index 6c34e3d5..ab9c4a5c 100644 --- a/sld-api-backend/src/aws/infrastructure/models.py +++ b/sld-api-backend/src/aws/infrastructure/models.py @@ -17,4 +17,5 @@ class Aws_provider(Base): source_profile = Column(String(200), nullable=True) extra_variables = Column(JSON, nullable=True) created_at = Column(DateTime, default=datetime.datetime.now()) + updated_at = Column(DateTime, nullable=True) __table_args__ = (UniqueConstraint("squad", "environment"),) diff --git a/sld-api-backend/src/aws/infrastructure/repositories.py b/sld-api-backend/src/aws/infrastructure/repositories.py index e204c827..83b34ae8 100644 --- a/sld-api-backend/src/aws/infrastructure/repositories.py +++ b/sld-api-backend/src/aws/infrastructure/repositories.py @@ -18,7 +18,7 @@ def encrypt(secreto): raise err -def create_aws_profile(db: Session, aws: schemas_aws.AwsAsumeProfile) -> schemas_aws.AwsAccountResponse: +async def create_aws_profile(db: Session, aws: schemas_aws.AwsAsumeProfile) -> schemas_aws.AwsAccountResponse: encrypt_access_key_id = encrypt(aws.access_key_id) encrypt_secret_access_key = encrypt(aws.secret_access_key) encrypted_extra_variables = {key: encrypt(val) for key, val in aws.extra_variables.items()} if aws.extra_variables else None @@ -37,23 +37,16 @@ def create_aws_profile(db: Session, aws: schemas_aws.AwsAsumeProfile) -> schemas db.add(db_aws) db.commit() db.refresh(db_aws) - return schemas_aws.AwsAccountResponse( - id=db_aws.id, - squad=db_aws.squad, - environment=db_aws.environment, - default_region=db_aws.default_region, - role_arn=db_aws.role_arn, - extra_variables=db_aws.extra_variables, - ) + return schemas_aws.AwsAccountResponse.model_validate(obj=db_aws) except Exception as err: + db.rollback() raise err -async def update_aws_profile(db: Session, aws_id: int, updated_aws: schemas_aws.AwsAsumeProfile) -> schemas_aws.AwsAccountResponse: - db_aws = db.query(models.Aws_provider).filter(models.Aws_provider.id == aws_id).first() - +async def update_aws_profile(db: Session, aws_account_id: int, updated_aws: schemas_aws.AwsAccountUpdate) -> schemas_aws.AwsAccountResponse: + + db_aws = db.query(models.Aws_provider).filter(models.Aws_provider.id == aws_account_id).first() if db_aws: - # Update only the fields that are present in the updated_aws object if updated_aws.access_key_id: db_aws.access_key_id = encrypt(updated_aws.access_key_id) if updated_aws.secret_access_key: @@ -61,51 +54,34 @@ async def update_aws_profile(db: Session, aws_id: int, updated_aws: schemas_aws. if updated_aws.extra_variables: db_aws.extra_variables = {key: encrypt(val) for key, val in updated_aws.extra_variables.items()} - # Update the remaining fields db_aws.environment = updated_aws.environment db_aws.default_region = updated_aws.default_region db_aws.role_arn = updated_aws.role_arn db_aws.squad = updated_aws.squad + db_aws.updated_at = datetime.datetime.now() try: db.commit() db.refresh(db_aws) - return schemas_aws.AwsAccountResponse( - id=db_aws.id, - squad=db_aws.squad, - environment=db_aws.environment, - default_region=db_aws.default_region, - role_arn=db_aws.role_arn, - extra_variables=db_aws.extra_variables, - ) + return schemas_aws.AwsAccountResponse.model_validate(db_aws) except Exception as err: + db.rollback() raise err else: - # Handle the case where the specified AWS profile ID doesn't exist - raise ValueError(f"AWS profile with id {aws_id} not found") - + raise ValueError(f"AWS profile with id {aws_account_id} not found") -def get_credentials_aws_profile(db: Session, environment: str, squad: str) -> schemas_aws.AwsAccountResponseRepo: +async def get_credentials_aws_profile(db: Session, environment: str, squad: str) -> schemas_aws.AwsAccountResponseRepo: aws_provider_data = ( db.query(models.Aws_provider) .filter(models.Aws_provider.environment == environment) .filter(models.Aws_provider.squad == squad) .first() ) - return schemas_aws.AwsAccountResponseRepo( - id=aws_provider_data.id, - squad=aws_provider_data.squad, - environment=aws_provider_data.environment, - access_key_id=aws_provider_data.access_key_id, - secret_access_key=aws_provider_data.secret_access_key, - role_arn=aws_provider_data.role_arn, - default_region=aws_provider_data.default_region, - extra_variables=aws_provider_data.extra_variables, - ) + return schemas_aws.AwsAccountResponseRepo.model_validate(obj=aws_provider_data) -def get_all_aws_profile( +async def get_all_aws_profile( db: Session, filters: schemas_aws.AwsAccountFilter, skip: int = 0, limit: int = 100 ) -> List[schemas_aws.AwsAccountResponse]: try: @@ -130,6 +106,8 @@ def get_all_aws_profile( default_region=result.default_region, role_arn=result.role_arn, extra_variables=result.extra_variables, + created_at=result.created_at, + updated_at=result.updated_at, ) aws_profiles.append(aws_profile) return aws_profiles @@ -137,23 +115,18 @@ def get_all_aws_profile( raise err -def delete_aws_profile_by_id(db: Session, aws_profile_id: int): +async def delete_aws_profile_by_id(db: Session, aws_account_id: int) -> schemas_aws.AwsAccountResponse: try: - db.query(models.Aws_provider).filter( - models.Aws_provider.id == aws_profile_id - ).delete() - db.commit() - return {aws_profile_id: "deleted", "aws_profile_id": aws_profile_id} - except Exception as err: - raise err - - -def get_cloud_account_by_id(db: Session, provider_id: int): - try: - return ( - db.query(models.Aws_provider) - .filter(models.Aws_provider.id == provider_id) - .first() - ) + aws_profile = db.query(models.Aws_provider).filter( + models.Aws_provider.id == aws_account_id + ).first() + if aws_profile: + db.delete(aws_profile) + db.commit() + response_data = schemas_aws.AwsAccountResponse.model_validate(aws_profile) + return response_data + else: + raise f"AWS profile with id {aws_account_id} not found" except Exception as err: + db.rollback() raise err diff --git a/sld-api-backend/src/azure/infrastructure/models.py b/sld-api-backend/src/azure/infrastructure/models.py index 87ed2e50..95a88ec2 100644 --- a/sld-api-backend/src/azure/infrastructure/models.py +++ b/sld-api-backend/src/azure/infrastructure/models.py @@ -15,4 +15,5 @@ class Azure_provider(Base): tenant_id = Column(String(200), nullable=False) extra_variables = Column(JSON, nullable=True) created_at = Column(DateTime, default=datetime.datetime.now()) + updated_at = Column(DateTime, nullable=True) __table_args__ = (UniqueConstraint("squad", "environment"),) diff --git a/sld-api-backend/src/custom_providers/infrastructure/models.py b/sld-api-backend/src/custom_providers/infrastructure/models.py index ce4e2f1c..649febdd 100644 --- a/sld-api-backend/src/custom_providers/infrastructure/models.py +++ b/sld-api-backend/src/custom_providers/infrastructure/models.py @@ -11,4 +11,5 @@ class Custom_provider(Base): squad = Column(String(200), nullable=False) configuration = Column(JSON, nullable=False) created_at = Column(DateTime, default=datetime.datetime.now()) + updated_at = Column(DateTime, nullable=True) __table_args__ = (UniqueConstraint("squad", "environment"),) diff --git a/sld-api-backend/src/deploy/api/container/deploy/create.py b/sld-api-backend/src/deploy/api/container/deploy/create.py index 123916a8..c5ae9b73 100644 --- a/sld-api-backend/src/deploy/api/container/deploy/create.py +++ b/sld-api-backend/src/deploy/api/container/deploy/create.py @@ -41,7 +41,7 @@ async def deploy_infra_by_stack_name( status_code=403, detail=f"Not enough permissions in {squad}" ) # Get credentials by providers supported - secreto = check_prefix( + secreto = await check_prefix( db, stack_name=deploy.stack_name, environment=deploy.environment, squad=squad ) # Get info from stack data diff --git a/sld-api-backend/src/deploy/api/container/deploy/delete.py b/sld-api-backend/src/deploy/api/container/deploy/delete.py index ab78fe89..eb3811da 100644 --- a/sld-api-backend/src/deploy/api/container/deploy/delete.py +++ b/sld-api-backend/src/deploy/api/container/deploy/delete.py @@ -38,7 +38,7 @@ async def delete_infra_by_id( project_path = deploy_data.project_path variables = deploy_data.variables # Get credentials by providers supported - secreto = check_prefix( + secreto = await check_prefix( db, stack_name=stack_name, environment=environment, squad=squad ) # Get info from stack data diff --git a/sld-api-backend/src/deploy/api/container/deploy/destroy.py b/sld-api-backend/src/deploy/api/container/deploy/destroy.py index 2f42c217..fec8a8dc 100644 --- a/sld-api-backend/src/deploy/api/container/deploy/destroy.py +++ b/sld-api-backend/src/deploy/api/container/deploy/destroy.py @@ -43,7 +43,7 @@ async def destroy_infra( project_path = deploy_data.project_path name = deploy_data.name # Get credentials by providers supported - secreto = check_prefix( + secreto = await check_prefix( db, stack_name=stack_name, environment=environment, squad=squad ) # Get info from stack data diff --git a/sld-api-backend/src/deploy/api/container/deploy/update.py b/sld-api-backend/src/deploy/api/container/deploy/update.py index 2fa022db..ca07bf89 100644 --- a/sld-api-backend/src/deploy/api/container/deploy/update.py +++ b/sld-api-backend/src/deploy/api/container/deploy/update.py @@ -45,7 +45,7 @@ async def deploy_by_id( environment = deploy_data.environment name = deploy_data.name # Get credentials by providers supported - secreto = check_prefix( + secreto = await check_prefix( db, stack_name=stack_name, environment=environment, squad=squad ) # Get info from stack data diff --git a/sld-api-backend/src/deploy/api/container/plan/create.py b/sld-api-backend/src/deploy/api/container/plan/create.py index ac91453f..39eefb3b 100644 --- a/sld-api-backend/src/deploy/api/container/plan/create.py +++ b/sld-api-backend/src/deploy/api/container/plan/create.py @@ -34,7 +34,7 @@ async def plan_infra_by_stack_name( status_code=403, detail=f"Not enough permissions in {squad}" ) # Get credentials by providers supported - secreto = check_prefix( + secreto = await check_prefix( db, stack_name=deploy.stack_name, environment=deploy.environment, squad=squad ) # Get info from stack data diff --git a/sld-api-backend/src/deploy/api/container/plan/get.py b/sld-api-backend/src/deploy/api/container/plan/get.py index 0efb5aa3..ce9ac6d7 100644 --- a/sld-api-backend/src/deploy/api/container/plan/get.py +++ b/sld-api-backend/src/deploy/api/container/plan/get.py @@ -29,7 +29,7 @@ async def get_plan_by_id_deploy( status_code=403, detail=f"Not enough permissions in {deploy_data.squad}" ) # Get credentials by providers supported - secreto = check_prefix( + secreto = await check_prefix( db, stack_name=deploy_data.stack_name, environment=deploy_data.environment, diff --git a/sld-api-backend/src/gcp/infrastructure/models.py b/sld-api-backend/src/gcp/infrastructure/models.py index 138c9939..b1bb3c99 100644 --- a/sld-api-backend/src/gcp/infrastructure/models.py +++ b/sld-api-backend/src/gcp/infrastructure/models.py @@ -12,4 +12,5 @@ class Gcloud_provider(Base): gcloud_keyfile_json = Column(String(5000), nullable=False) extra_variables = Column(JSON, nullable=True) created_at = Column(DateTime, default=datetime.datetime.now()) + updated_at = Column(DateTime, nullable=True) __table_args__ = (UniqueConstraint("squad", "environment"),) diff --git a/sld-api-backend/src/shared/helpers/get_data.py b/sld-api-backend/src/shared/helpers/get_data.py index 643f8362..c434bf79 100644 --- a/sld-api-backend/src/shared/helpers/get_data.py +++ b/sld-api-backend/src/shared/helpers/get_data.py @@ -181,10 +181,10 @@ def check_cron_schedule(cron_time: str): return True -def check_prefix(db, stack_name: str, environment: str, squad: str): +async def check_prefix(db, stack_name: str, environment: str, squad: str): try: if any(i in stack_name.lower() for i in settings.AWS_PREFIX): - secreto = crud_aws.get_credentials_aws_profile( + secreto = await crud_aws.get_credentials_aws_profile( db=db, environment=environment, squad=squad ) return secreto From 722272ff653f59ca098c140a9ec16675c9dd0684 Mon Sep 17 00:00:00 2001 From: d10s <79284025+D10S0VSkY-OSS@users.noreply.github.com> Date: Fri, 29 Dec 2023 00:49:28 +0100 Subject: [PATCH 06/18] =?UTF-8?q?=F0=9F=94=A7refactor:=20aws=20backend=20a?= =?UTF-8?q?ctivity=20logs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sld-api-backend/src/aws/api/container/create.py | 3 ++- sld-api-backend/src/aws/api/container/update.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/sld-api-backend/src/aws/api/container/create.py b/sld-api-backend/src/aws/api/container/create.py index c753e34a..b948eaa3 100644 --- a/sld-api-backend/src/aws/api/container/create.py +++ b/sld-api-backend/src/aws/api/container/create.py @@ -30,12 +30,13 @@ async def create_new_aws_profile( if db_aws_account: raise HTTPException(status_code=409, detail="Account already exists") try: + result = await crud_aws.create_aws_profile(db=db, aws=aws) crud_activity.create_activity_log( db=db, username=current_user.username, squad=current_user.squad, action=f"Create AWS account {aws.squad} {aws.environment}", ) - return await crud_aws.create_aws_profile(db=db, aws=aws) + return result except Exception as err: raise HTTPException(status_code=400, detail=str(err)) diff --git a/sld-api-backend/src/aws/api/container/update.py b/sld-api-backend/src/aws/api/container/update.py index 12929b27..e379483b 100644 --- a/sld-api-backend/src/aws/api/container/update.py +++ b/sld-api-backend/src/aws/api/container/update.py @@ -23,12 +23,13 @@ async def update_aws_account( db_aws_account = await crud_aws.get_all_aws_profile(db=db, filters=filters) if not db_aws_account: raise HTTPException(status_code=404, detail="Account not found") + result = await crud_aws.update_aws_profile(db=db, aws_account_id=aws_account_id, updated_aws=aws) crud_activity.create_activity_log( db=db, username=current_user.username, squad=current_user.squad, action=f"Update AWS account {aws.squad} {aws.environment}", ) - return await crud_aws.update_aws_profile(db=db, aws_account_id=aws_account_id, updated_aws=aws) + return result except Exception as err: raise err From 441199167d56f88828fa8ecf1363d9dd0609affa Mon Sep 17 00:00:00 2001 From: d10s <79284025+D10S0VSkY-OSS@users.noreply.github.com> Date: Fri, 29 Dec 2023 02:44:46 +0100 Subject: [PATCH 07/18] =?UTF-8?q?=F0=9F=94=A7refactor:=20aws=20ui=20add=20?= =?UTF-8?q?extra=20variables=20for=20new=20accounts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/aws/infrastructure/repositories.py | 18 +++---- .../src/deploy/api/container/plan/update.py | 2 +- sld-api-backend/test/config/api.py | 15 ++---- sld-dashboard/app/home/forms.py | 46 ++++++++-------- sld-dashboard/app/home/routes.py | 6 ++- .../app/home/templates/aws-list.html | 25 +++++++-- sld-dashboard/app/home/templates/aws-new.html | 54 +++++++++++++++---- 7 files changed, 109 insertions(+), 57 deletions(-) diff --git a/sld-api-backend/src/aws/infrastructure/repositories.py b/sld-api-backend/src/aws/infrastructure/repositories.py index 83b34ae8..0333b764 100644 --- a/sld-api-backend/src/aws/infrastructure/repositories.py +++ b/sld-api-backend/src/aws/infrastructure/repositories.py @@ -44,7 +44,7 @@ async def create_aws_profile(db: Session, aws: schemas_aws.AwsAsumeProfile) -> s async def update_aws_profile(db: Session, aws_account_id: int, updated_aws: schemas_aws.AwsAccountUpdate) -> schemas_aws.AwsAccountResponse: - + db_aws = db.query(models.Aws_provider).filter(models.Aws_provider.id == aws_account_id).first() if db_aws: if updated_aws.access_key_id: @@ -72,13 +72,13 @@ async def update_aws_profile(db: Session, aws_account_id: int, updated_aws: sche async def get_credentials_aws_profile(db: Session, environment: str, squad: str) -> schemas_aws.AwsAccountResponseRepo: - aws_provider_data = ( + db_aws = ( db.query(models.Aws_provider) .filter(models.Aws_provider.environment == environment) .filter(models.Aws_provider.squad == squad) .first() ) - return schemas_aws.AwsAccountResponseRepo.model_validate(obj=aws_provider_data) + return schemas_aws.AwsAccountResponseRepo.model_validate(obj=db_aws) async def get_all_aws_profile( @@ -95,10 +95,10 @@ async def get_all_aws_profile( else: query = query.filter(getattr(models.Aws_provider, field) == value) - results = query.order_by(desc(models.Aws_provider.id)).offset(skip).limit(limit).all() + db_aws = query.order_by(desc(models.Aws_provider.id)).offset(skip).limit(limit).all() aws_profiles = [] - for result in results: + for result in db_aws: aws_profile = schemas_aws.AwsAccountResponse( id=result.id, squad=result.squad, @@ -117,13 +117,13 @@ async def get_all_aws_profile( async def delete_aws_profile_by_id(db: Session, aws_account_id: int) -> schemas_aws.AwsAccountResponse: try: - aws_profile = db.query(models.Aws_provider).filter( + db_aws = db.query(models.Aws_provider).filter( models.Aws_provider.id == aws_account_id ).first() - if aws_profile: - db.delete(aws_profile) + if db_aws: + db.delete(db_aws) db.commit() - response_data = schemas_aws.AwsAccountResponse.model_validate(aws_profile) + response_data = schemas_aws.AwsAccountResponse.model_validate(db_aws) return response_data else: raise f"AWS profile with id {aws_account_id} not found" diff --git a/sld-api-backend/src/deploy/api/container/plan/update.py b/sld-api-backend/src/deploy/api/container/plan/update.py index e2332f1d..cd72f1a2 100644 --- a/sld-api-backend/src/deploy/api/container/plan/update.py +++ b/sld-api-backend/src/deploy/api/container/plan/update.py @@ -40,7 +40,7 @@ async def update_plan_by_id( status_code=403, detail=f"Not enough permissions in {squad}" ) # Get credentials by providers supported - secreto = check_prefix( + secreto = await check_prefix( db, stack_name=stack_name, environment=environment, squad=squad ) # Get info from stack data diff --git a/sld-api-backend/test/config/api.py b/sld-api-backend/test/config/api.py index 2afa5d74..f46cbedf 100644 --- a/sld-api-backend/test/config/api.py +++ b/sld-api-backend/test/config/api.py @@ -120,9 +120,7 @@ class Settings(BaseSettings): "access_key_id": os.getenv("AWS_ACCESS_KEY_ID"), "secret_access_key": os.getenv("AWS_SECRET_ACCESS_KEY"), "default_region": "eu-west-1", - "profile_name": "string", - "role_arn": "string", - "source_profile": "string", + "extra_variables": {"TF_VAR_aws_account_id": "1234567890", "TF_VAR_aws_secret": "1234"}, } AWS_TEST_ACCOUNT_PRO: dict = { "squad": "squad1", @@ -130,9 +128,8 @@ class Settings(BaseSettings): "access_key_id": os.getenv("AWS_ACCESS_KEY_ID"), "secret_access_key": os.getenv("AWS_SECRET_ACCESS_KEY"), "default_region": "eu-west-1", - "profile_name": "string", - "role_arn": "string", - "source_profile": "string", + "role_arn": "arn:aws:iam::1234567890:role/role_name", + "extra_variables": {"TF_VAR_aws_account_id": "1234567890", "TF_VAR_aws_secret": "1234"}, } AWS_TEST_ACCOUNT_SQUAD2: dict = { "squad": "squad2", @@ -140,9 +137,8 @@ class Settings(BaseSettings): "access_key_id": os.getenv("AWS_ACCESS_KEY_ID"), "secret_access_key": os.getenv("AWS_SECRET_ACCESS_KEY"), "default_region": "eu-west-1", - "profile_name": "string", - "role_arn": "string", - "source_profile": "string", + "role_arn": "arn:aws:iam::1234567890:role/role_name", + "extra_variables": {"TF_VAR_aws_account_id": "1234567890", "TF_VAR_aws_secret": "1234", "TF_VAR_db_password": "1234"}, } AWS_TEST_ACCOUNT_SQUAD2_PRO: dict = { "squad": "squad2", @@ -151,7 +147,6 @@ class Settings(BaseSettings): "secret_access_key": os.getenv("AWS_SECRET_ACCESS_KEY"), "default_region": "eu-west-1", "profile_name": "string", - "role_arn": "string", "source_profile": "string", } DEPLOY_URI: str = "?tf_ver=1.0.7" diff --git a/sld-dashboard/app/home/forms.py b/sld-dashboard/app/home/forms.py index 28289f50..c3663018 100644 --- a/sld-dashboard/app/home/forms.py +++ b/sld-dashboard/app/home/forms.py @@ -1,12 +1,26 @@ # -*- encoding: utf-8 -*- from flask_wtf import FlaskForm from wtforms import (BooleanField, PasswordField, StringField, TextAreaField, SelectField, - validators) + FormField, FieldList, validators) from wtforms.fields import EmailField from wtforms.validators import DataRequired -# login and registration +class DictField(StringField): + def process_formdata(self, valuelist): + if valuelist: + data = valuelist[0] + try: + # Try to parse the input as a dictionary + self.data = dict(eval(data)) + except (SyntaxError, ValueError): + self.data = None + raise ValueError("Invalid dictionary format") + + +class ExtraVariableForm(FlaskForm): + key = StringField('Key') + value = StringField('Value') class StackForm(FlaskForm): @@ -193,6 +207,13 @@ class AwsForm(FlaskForm): validators.DataRequired(message="Squad Name requerid."), ], ) + environment = StringField( + "Environment *", + [ + validators.length(min=2, max=250, message="Environment out of reange."), + validators.DataRequired(message="Environment requerid."), + ], + ) access_key_id = StringField( "Access_key_id *", [ @@ -216,31 +237,14 @@ class AwsForm(FlaskForm): validators.DataRequired(message="default_region."), ], ) - profile_name = StringField( - "Profile_name", - [ - validators.length(min=4, max=50, message="profile_name out of reange."), - ], - ) role_arn = StringField( "Role_arn", [ validators.length(min=4, max=50, message="Role arn out of reange."), ], ) - source_profile = StringField( - "Source_profile", - [ - validators.length(min=4, max=50, message="source_profile out of reange."), - ], - ) - environment = StringField( - "Environment *", - [ - validators.length(min=2, max=250, message="Branch out of reange."), - validators.DataRequired(message="Environment requerid."), - ], - ) + extra_variables = FieldList(FormField(ExtraVariableForm), label='Extra Variables') + class GcpForm(FlaskForm): diff --git a/sld-dashboard/app/home/routes.py b/sld-dashboard/app/home/routes.py index a750e7d9..62c3b326 100644 --- a/sld-dashboard/app/home/routes.py +++ b/sld-dashboard/app/home/routes.py @@ -1506,15 +1506,17 @@ def new_aws_account(): # Check if token no expired check_unauthorized_token(token) if request.method == "POST": + key_list = request.values.getlist("sld_key") + value_list = request.values.getlist("sld_value") new_user: dict = { "squad": form.squad.data.replace(" ",""), "environment": form.environment.data.replace(" ",""), "access_key_id": form.access_key_id.data.replace(" ",""), "secret_access_key": form.secret_access_key.data.replace(" ",""), "default_region": form.default_region.data.replace(" ",""), - "profile_name": form.profile_name.data.replace(" ",""), "role_arn": form.role_arn.data.replace(" ",""), - "source_profile": form.source_profile.data.replace(" ",""), + "extra_variables": dict(list(zip(key_list, value_list))) + } response = request_url( verb="POST", diff --git a/sld-dashboard/app/home/templates/aws-list.html b/sld-dashboard/app/home/templates/aws-list.html index e0bb93fa..a27df862 100644 --- a/sld-dashboard/app/home/templates/aws-list.html +++ b/sld-dashboard/app/home/templates/aws-list.html @@ -71,9 +71,8 @@

    All aws accounts

    Squad Environment Default Region - Profile Name Role Arn - Source Profile + Extra Variables @@ -87,9 +86,25 @@

    All aws accounts

    {{ aws_account.squad }} {{ aws_account.environment }} {{ aws_account.default_region }} - {{ aws_account.profile_name }} - {{ aws_account.role_arn }} - {{ aws_account.source_profile }} + + + {% if aws_account.role_arn %} + {{ aws_account.role_arn }} + {% else %} + - + {% endif %} + + + + + {% if aws_account.extra_variables %} + {% set truncated_variables = aws_account.extra_variables.keys() | join(', ') | truncate(30, True, '...') %} + {{ truncated_variables }} + {% else %} + - + {% endif %} + +
    {% if "yoda" in current_user.role %} diff --git a/sld-dashboard/app/home/templates/aws-new.html b/sld-dashboard/app/home/templates/aws-new.html index ce2a05a7..e452c33b 100644 --- a/sld-dashboard/app/home/templates/aws-new.html +++ b/sld-dashboard/app/home/templates/aws-new.html @@ -67,18 +67,27 @@

    Add New AWS Account

    {{ render_field(form.default_region, class='form-control',value='eu-west-1') }}
    -
    - {{ render_field(form.profile_name, class='form-control', - placeholder='profile_name') }} -
    {{ render_field(form.role_arn, class='form-control', placeholder='role_arn') }}
    -
    - {{ render_field(form.source_profile, class='form-control', - placeholder='source_profile') }} -
    + +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + @@ -99,4 +108,31 @@

    Add New AWS Account

    {% endblock content %} -{% block javascripts %}{% endblock javascripts %} \ No newline at end of file +{% block javascripts %} + +{% endblock javascripts %} \ No newline at end of file From 1244981708213a17f7c11ffa7cb39aab81cd220f Mon Sep 17 00:00:00 2001 From: d10s <79284025+D10S0VSkY-OSS@users.noreply.github.com> Date: Fri, 29 Dec 2023 23:42:21 +0100 Subject: [PATCH 08/18] =?UTF-8?q?=F0=9F=94=A7refactor:=20aws=20update=20ex?= =?UTF-8?q?tra=20varibles=20add=20feature=20for=20edit=20key=20value?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/aws/api/container/delete.py | 23 +-- .../src/aws/infrastructure/repositories.py | 31 +++- sld-api-backend/src/shared/domain/__init__.py | 0 .../src/shared/domain/exeptions/__init__.py | 0 .../src/shared/domain/exeptions/in_use.py | 4 + sld-dashboard/app/home/routes.py | 59 ++++++- .../app/home/templates/aws-edit.html | 145 ++++++++++++++++++ .../app/home/templates/aws-list.html | 2 +- sld-dashboard/app/home/templates/aws-new.html | 9 +- 9 files changed, 251 insertions(+), 22 deletions(-) create mode 100644 sld-api-backend/src/shared/domain/__init__.py create mode 100644 sld-api-backend/src/shared/domain/exeptions/__init__.py create mode 100644 sld-api-backend/src/shared/domain/exeptions/in_use.py create mode 100644 sld-dashboard/app/home/templates/aws-edit.html diff --git a/sld-api-backend/src/aws/api/container/delete.py b/sld-api-backend/src/aws/api/container/delete.py index 022f1d2b..d940ec90 100644 --- a/sld-api-backend/src/aws/api/container/delete.py +++ b/sld-api-backend/src/aws/api/container/delete.py @@ -7,6 +7,7 @@ from src.shared.security import deps from src.users.domain.entities import users as schemas_users from src.users.infrastructure import repositories as crud_users +from src.shared.domain.exeptions.in_use import ResourceInUseError async def aws_account_by_id( @@ -22,12 +23,16 @@ async def aws_account_by_id( db_aws_account = await crud_aws.get_all_aws_profile(db=db, filters=filters) if not db_aws_account: raise HTTPException(status_code=404, detail="Account not found") - - result = await crud_aws.delete_aws_profile_by_id(db=db, aws_account_id=aws_account_id) - crud_activity.create_activity_log( - db=db, - username=current_user.username, - squad=current_user.squad, - action=f"Delete AWS account {aws_account_id}", - ) - return result + try: + result = await crud_aws.delete_aws_profile_by_id(db=db, aws_account_id=aws_account_id) + crud_activity.create_activity_log( + db=db, + username=current_user.username, + squad=current_user.squad, + action=f"Delete AWS account {aws_account_id}", + ) + return result + except ResourceInUseError as err: + raise HTTPException(status_code=409, detail=str(err)) + except Exception as err: + raise err diff --git a/sld-api-backend/src/aws/infrastructure/repositories.py b/sld-api-backend/src/aws/infrastructure/repositories.py index 0333b764..5e2b3cf3 100644 --- a/sld-api-backend/src/aws/infrastructure/repositories.py +++ b/sld-api-backend/src/aws/infrastructure/repositories.py @@ -6,8 +6,10 @@ import src.aws.infrastructure.models as models +from src.deploy.infrastructure.models import Deploy from src.aws.domain.entities import aws as schemas_aws -from src.shared.security.vault import vault_encrypt +from src.shared.domain.exeptions.in_use import ResourceInUseError +from src.shared.security.vault import vault_encrypt, vault_decrypt @vault_encrypt @@ -17,6 +19,13 @@ def encrypt(secreto): except Exception as err: raise err +@vault_decrypt +def decrypt(secreto): + try: + return secreto + except Exception as err: + raise err + async def create_aws_profile(db: Session, aws: schemas_aws.AwsAsumeProfile) -> schemas_aws.AwsAccountResponse: encrypt_access_key_id = encrypt(aws.access_key_id) @@ -44,16 +53,25 @@ async def create_aws_profile(db: Session, aws: schemas_aws.AwsAsumeProfile) -> s async def update_aws_profile(db: Session, aws_account_id: int, updated_aws: schemas_aws.AwsAccountUpdate) -> schemas_aws.AwsAccountResponse: - db_aws = db.query(models.Aws_provider).filter(models.Aws_provider.id == aws_account_id).first() + if db_aws: if updated_aws.access_key_id: db_aws.access_key_id = encrypt(updated_aws.access_key_id) if updated_aws.secret_access_key: db_aws.secret_access_key = encrypt(updated_aws.secret_access_key) + if updated_aws.extra_variables: - db_aws.extra_variables = {key: encrypt(val) for key, val in updated_aws.extra_variables.items()} + current_extra_variables = db_aws.extra_variables or {} + for key, value in current_extra_variables.items(): + current_extra_variables[key] = decrypt(value) + + for key, value in updated_aws.extra_variables.items(): + if "***" not in value: + current_extra_variables[key] = value + encrypted_extra_variables = {key: encrypt(value) for key, value in current_extra_variables.items()} + db_aws.extra_variables = encrypted_extra_variables db_aws.environment = updated_aws.environment db_aws.default_region = updated_aws.default_region db_aws.role_arn = updated_aws.role_arn @@ -120,6 +138,13 @@ async def delete_aws_profile_by_id(db: Session, aws_account_id: int) -> schemas_ db_aws = db.query(models.Aws_provider).filter( models.Aws_provider.id == aws_account_id ).first() + db_deploy = ( + db.query(Deploy) + .filter(Deploy.squad == db_aws.squad) + .filter(Deploy.environment == db_aws.environment) + .first()) + if db_deploy: + raise ResourceInUseError(aws_account_id) if db_aws: db.delete(db_aws) db.commit() diff --git a/sld-api-backend/src/shared/domain/__init__.py b/sld-api-backend/src/shared/domain/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sld-api-backend/src/shared/domain/exeptions/__init__.py b/sld-api-backend/src/shared/domain/exeptions/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sld-api-backend/src/shared/domain/exeptions/in_use.py b/sld-api-backend/src/shared/domain/exeptions/in_use.py new file mode 100644 index 00000000..14be10c8 --- /dev/null +++ b/sld-api-backend/src/shared/domain/exeptions/in_use.py @@ -0,0 +1,4 @@ +class ResourceInUseError(Exception): + def __init__(self, resource_name: str): + self.resource_name = resource_name + super().__init__(f"Resource {resource_name} is being used and cannot be deleted") diff --git a/sld-dashboard/app/home/routes.py b/sld-dashboard/app/home/routes.py index 62c3b326..b14b313b 100644 --- a/sld-dashboard/app/home/routes.py +++ b/sld-dashboard/app/home/routes.py @@ -1444,7 +1444,7 @@ def edit_user(user_id): json=data, ) if response.get("status_code") == 200: - flash(f"User Updated ") + flash("User Updated ") else: flash(response["json"].get("detail"), "error") return redirect( @@ -1508,7 +1508,7 @@ def new_aws_account(): if request.method == "POST": key_list = request.values.getlist("sld_key") value_list = request.values.getlist("sld_value") - new_user: dict = { + aws_account_request: dict = { "squad": form.squad.data.replace(" ",""), "environment": form.environment.data.replace(" ",""), "access_key_id": form.access_key_id.data.replace(" ",""), @@ -1522,7 +1522,7 @@ def new_aws_account(): verb="POST", uri="accounts/aws/", headers={"Authorization": f"Bearer {token}"}, - json=new_user, + json=aws_account_request, ) if response.get("status_code") == 200: flash( @@ -1544,6 +1544,59 @@ def new_aws_account(): return redirect(url_for("base_blueprint.logout")) +@blueprint.route("/aws-edit", methods=["GET", "POST"], defaults={"account_id": None}) +@blueprint.route("/aws-edit/", methods=["GET", "POST"]) +@login_required +def edit_aws_account(account_id): + try: + form = AwsForm(request.form) + token = decrypt(r.get(current_user.id)) + # Check if token no expired + check_unauthorized_token(token) + endpoint = f"accounts/aws/?id={account_id}" + response = request_url( + verb="GET", uri=f"{endpoint}", headers={"Authorization": f"Bearer {token}"} + ) + vars_json = response["json"][0] + if request.method == "POST": + key_list = request.values.getlist("sld_key") + value_list = request.values.getlist("sld_value") + print(request.values) + aws_account_request: dict = { + "squad": form.squad.data.replace(" ",""), + "environment": form.environment.data.replace(" ",""), + "access_key_id": form.access_key_id.data.replace(" ","") if "*" not in form.access_key_id.data else None, + "secret_access_key": form.secret_access_key.data.replace(" ","") if "*" not in form.secret_access_key.data else None, + "default_region": form.default_region.data.replace(" ",""), + "role_arn": form.role_arn.data.replace(" ",""), + "extra_variables": dict(list(zip(key_list, value_list))), + } + response = request_url( + verb="PATCH", + uri=f"accounts/aws/{account_id}", + headers={"Authorization": f"Bearer {token}"}, + json=aws_account_request, + ) + if response.get("status_code") == 200: + flash( + f"Updated aws account for environment {form.environment.data} in {form.squad.data} " + ) + elif response.get("status_code") == 409: + flash(response["json"].get("detail"), "error") + else: + flash(response["json"], "error") + + return render_template( + "/aws-edit.html", + title="Edit aws account", + form=form, + active="edit_aws_account", + data_json=vars_json, + external_api_dns=external_api_dns, + ) + except ValueError: + return redirect(url_for("base_blueprint.logout")) + @blueprint.route("/aws-list") @login_required def list_aws_account(): diff --git a/sld-dashboard/app/home/templates/aws-edit.html b/sld-dashboard/app/home/templates/aws-edit.html new file mode 100644 index 00000000..0619059e --- /dev/null +++ b/sld-dashboard/app/home/templates/aws-edit.html @@ -0,0 +1,145 @@ +{% extends "layouts/base.html" %} + +{% from "helpers/_forms.html" import render_field %} + +{% block title %} New AWS Account {% endblock %} + + +{% block stylesheets %}{% endblock stylesheets %} +{% block content %} +
    + + {% include 'includes/navigation.html' %} +
    + Volt logo +
    + +
    + +
    +
    +

    Edit AWS Account

    +

    Edit account by squad and environment

    +
    +
    + Accounts Docs +
    +
    +
    + +
    +
    +
    +
    +
    +
    + + {{data_json}} +
    +
    +
    + {{ render_field(form.squad, class='form-control', value=data_json.squad) }} +
    + +
    + {{ render_field(form.environment, class='form-control', value=data_json.environment) }} +
    + +
    + {{ render_field(form.access_key_id, class='form-control', value="*************", placeholder='aws access_key_id') }} +
    + +
    + {{ render_field(form.secret_access_key, class='form-control',value="************", placeholder='aws secret_access_key') }} +
    + +
    + {{ render_field(form.default_region, class='form-control',value=data_json.default_region) }} +
    + +
    + {{ render_field(form.role_arn, class='form-control',value=data_json.role_arn,placeholder='role_arn') }} +
    + +
    +
    +
    +
    + + {% for key, value in data_json.extra_variables.items() %} +
    +
    + +
    +
    + +
    +
    + +
    +
    + {% endfor %} +
    + +
    + +
    +
    +
    + + + +
    + + +
    +
    +
    +
    +
    + + {% include 'includes/footer.html' %} + +
    + +{% endblock content %} + + +{% block javascripts %} + +{% endblock javascripts %} \ No newline at end of file diff --git a/sld-dashboard/app/home/templates/aws-list.html b/sld-dashboard/app/home/templates/aws-list.html index a27df862..1c789aea 100644 --- a/sld-dashboard/app/home/templates/aws-list.html +++ b/sld-dashboard/app/home/templates/aws-list.html @@ -42,6 +42,7 @@

    All aws accounts

    aria-label="Search" aria-describedby="basic-addon2"> +
    {% endif %}
    diff --git a/sld-dashboard/app/home/templates/aws-new.html b/sld-dashboard/app/home/templates/aws-new.html index e452c33b..c7788913 100644 --- a/sld-dashboard/app/home/templates/aws-new.html +++ b/sld-dashboard/app/home/templates/aws-new.html @@ -49,18 +49,15 @@

    Add New AWS Account

    - {{ render_field(form.environment, class='form-control', placeholder='Environment - Name') }} + {{ render_field(form.environment, class='form-control', placeholder='Environment Name') }}
    - {{ render_field(form.access_key_id, class='form-control', placeholder='aws - access_key_id') }} + {{ render_field(form.access_key_id, class='form-control', placeholder='aws access_key_id') }}
    - {{ render_field(form.secret_access_key, class='form-control', placeholder='aws - secret_access_key') }} + {{ render_field(form.secret_access_key, class='form-control', placeholder='aws secret_access_key') }}
    From 3198ed60bfe5f7ae5c693ad80f708b9291ecba9b Mon Sep 17 00:00:00 2001 From: d10s <79284025+D10S0VSkY-OSS@users.noreply.github.com> Date: Sat, 30 Dec 2023 01:27:05 +0100 Subject: [PATCH 09/18] =?UTF-8?q?=F0=9F=94=A7refactor:=20aws=20update=20ch?= =?UTF-8?q?eck=20if=20use=20by=20deployment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/aws/api/container/update.py | 3 + .../src/aws/infrastructure/repositories.py | 11 +++- sld-dashboard/app/home/routes.py | 4 +- .../app/home/templates/aws-edit.html | 59 +++++++++++-------- .../app/home/templates/aws-list.html | 3 + 5 files changed, 50 insertions(+), 30 deletions(-) diff --git a/sld-api-backend/src/aws/api/container/update.py b/sld-api-backend/src/aws/api/container/update.py index e379483b..20fa4825 100644 --- a/sld-api-backend/src/aws/api/container/update.py +++ b/sld-api-backend/src/aws/api/container/update.py @@ -7,6 +7,7 @@ from src.shared.security import deps from src.users.domain.entities import users as schemas_users from src.users.infrastructure import repositories as crud_users +from src.shared.domain.exeptions.in_use import ResourceInUseError async def update_aws_account( @@ -31,5 +32,7 @@ async def update_aws_account( action=f"Update AWS account {aws.squad} {aws.environment}", ) return result + except ResourceInUseError as err: + raise HTTPException(status_code=409, detail=str(err)) except Exception as err: raise err diff --git a/sld-api-backend/src/aws/infrastructure/repositories.py b/sld-api-backend/src/aws/infrastructure/repositories.py index 5e2b3cf3..39fd107e 100644 --- a/sld-api-backend/src/aws/infrastructure/repositories.py +++ b/sld-api-backend/src/aws/infrastructure/repositories.py @@ -54,6 +54,13 @@ async def create_aws_profile(db: Session, aws: schemas_aws.AwsAsumeProfile) -> s async def update_aws_profile(db: Session, aws_account_id: int, updated_aws: schemas_aws.AwsAccountUpdate) -> schemas_aws.AwsAccountResponse: db_aws = db.query(models.Aws_provider).filter(models.Aws_provider.id == aws_account_id).first() + db_deploy = ( + db.query(Deploy) + .filter(Deploy.squad == db_aws.squad) + .filter(Deploy.environment == db_aws.environment) + .first()) + if db_deploy and updated_aws.squad != db_aws.squad or updated_aws.environment != db_aws.environment: + raise ResourceInUseError(aws_account_id) if db_aws: if updated_aws.access_key_id: @@ -65,11 +72,11 @@ async def update_aws_profile(db: Session, aws_account_id: int, updated_aws: sche current_extra_variables = db_aws.extra_variables or {} for key, value in current_extra_variables.items(): current_extra_variables[key] = decrypt(value) + current_extra_variables = {key: value for key, value in current_extra_variables.items() if key in updated_aws.extra_variables} for key, value in updated_aws.extra_variables.items(): - if "***" not in value: + if key and value and "***" not in value: current_extra_variables[key] = value - encrypted_extra_variables = {key: encrypt(value) for key, value in current_extra_variables.items()} db_aws.extra_variables = encrypted_extra_variables db_aws.environment = updated_aws.environment diff --git a/sld-dashboard/app/home/routes.py b/sld-dashboard/app/home/routes.py index b14b313b..e51e492b 100644 --- a/sld-dashboard/app/home/routes.py +++ b/sld-dashboard/app/home/routes.py @@ -1561,7 +1561,6 @@ def edit_aws_account(account_id): if request.method == "POST": key_list = request.values.getlist("sld_key") value_list = request.values.getlist("sld_value") - print(request.values) aws_account_request: dict = { "squad": form.squad.data.replace(" ",""), "environment": form.environment.data.replace(" ",""), @@ -1581,6 +1580,8 @@ def edit_aws_account(account_id): flash( f"Updated aws account for environment {form.environment.data} in {form.squad.data} " ) + return redirect(url_for("home_blueprint.route_template", template="aws-list")) + elif response.get("status_code") == 409: flash(response["json"].get("detail"), "error") else: @@ -1613,7 +1614,6 @@ def list_aws_account(): return render_template( "aws-list.html", name="Name", aws=content, external_api_dns=external_api_dns ) - except ValueError: return redirect(url_for("base_blueprint.logout")) diff --git a/sld-dashboard/app/home/templates/aws-edit.html b/sld-dashboard/app/home/templates/aws-edit.html index 0619059e..78df6b18 100644 --- a/sld-dashboard/app/home/templates/aws-edit.html +++ b/sld-dashboard/app/home/templates/aws-edit.html @@ -42,7 +42,6 @@

    Edit AWS Account

    - {{data_json}}
    @@ -74,19 +73,19 @@

    Edit AWS Account

    - {% for key, value in data_json.extra_variables.items() %} -
    -
    - -
    -
    - -
    -
    - -
    -
    - {% endfor %} +{% for key, value in data_json.extra_variables.items() %} +
    +
    + +
    +
    + +
    +
    + +
    +
    +{% endfor %}
    @@ -95,10 +94,12 @@

    Edit AWS Account

    - + - @@ -119,18 +120,17 @@

    Edit AWS Account

    + + {% endblock javascripts %} \ No newline at end of file diff --git a/sld-dashboard/app/home/templates/aws-list.html b/sld-dashboard/app/home/templates/aws-list.html index 1c789aea..2161dc0e 100644 --- a/sld-dashboard/app/home/templates/aws-list.html +++ b/sld-dashboard/app/home/templates/aws-list.html @@ -117,6 +117,9 @@

    All aws accounts

    Toggle Dropdown '; html += '
    '; - html += ''; + html += ''; html += '
    '; html += '
    '; html += '
    '; @@ -139,9 +142,14 @@

    Edit AWS Account

    // remove row $(document).on('click', '.removeRow', function () { - $(this).closest('.dynamic-row').remove(); + if ($('.dynamic-row').length > 1) { + $(this).closest('.dynamic-row').remove(); + } else { + $(this).closest('.dynamic-row').find('input[type="text"]').val(''); + } }); + + {% endblock javascripts %} \ No newline at end of file From 4b915da39e3dec6c60045c67aa0ec44cff7be042 Mon Sep 17 00:00:00 2001 From: d10s <79284025+D10S0VSkY-OSS@users.noreply.github.com> Date: Sat, 30 Dec 2023 18:00:00 +0100 Subject: [PATCH 12/18] =?UTF-8?q?=F0=9F=94=A7refactor:=20azure=20backend?= =?UTF-8?q?=20accounts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/aws/domain/entities/aws.py | 15 +- .../src/aws/infrastructure/repositories.py | 22 +-- .../src/azure/api/container/create.py | 15 +- .../src/azure/api/container/delete.py | 33 ++-- .../src/azure/api/container/get.py | 12 +- .../src/azure/api/container/update.py | 38 ++++ sld-api-backend/src/azure/api/v1/azure.py | 19 +- .../src/azure/domain/entities/azure.py | 45 ++++- .../src/azure/infrastructure/repositories.py | 165 ++++++++++-------- .../src/shared/infrastructure/respository.py | 20 +++ 10 files changed, 253 insertions(+), 131 deletions(-) create mode 100644 sld-api-backend/src/azure/api/container/update.py create mode 100644 sld-api-backend/src/shared/infrastructure/respository.py diff --git a/sld-api-backend/src/aws/domain/entities/aws.py b/sld-api-backend/src/aws/domain/entities/aws.py index d4424746..c76e06e3 100644 --- a/sld-api-backend/src/aws/domain/entities/aws.py +++ b/sld-api-backend/src/aws/domain/entities/aws.py @@ -17,8 +17,8 @@ class AwsAsumeProfile(AwsBase): role_arn: Optional[constr(strip_whitespace=True)] = None -class Aws(AwsBase): - id: int +class AwsId(BaseModel): + id: Optional[int] = None class Config: from_attributes = True @@ -53,15 +53,18 @@ class Config: from_attributes = True -class AwsAccountFilter(BaseModel): - id: Optional[int] = None +class AwsAccount(BaseModel): squad: Optional[str] = None - access_key_id: Optional[str] = None environment: Optional[str] = None default_region: Optional[str] = None role_arn: Optional[str] = None + access_key_id: Optional[str] = None + + +class AwsAccountFilter(AwsAccount, AwsId): + pass -class AwsAccountUpdate(AwsAccountFilter): +class AwsAccountUpdate(AwsAccount): secret_access_key: Optional[str] = None extra_variables: Optional[Dict[str, Any]] = None diff --git a/sld-api-backend/src/aws/infrastructure/repositories.py b/sld-api-backend/src/aws/infrastructure/repositories.py index dd63148c..7b81bec2 100644 --- a/sld-api-backend/src/aws/infrastructure/repositories.py +++ b/sld-api-backend/src/aws/infrastructure/repositories.py @@ -6,9 +6,8 @@ import src.aws.infrastructure.models as models -from src.deploy.infrastructure.models import Deploy from src.aws.domain.entities import aws as schemas_aws -from src.shared.domain.exeptions.in_use import ResourceInUseError +from src.shared.infrastructure.respository import check_deploy_in_use from src.shared.security.vault import vault_encrypt, vault_decrypt @@ -19,6 +18,7 @@ def encrypt(secreto): except Exception as err: raise err + @vault_decrypt def decrypt(secreto): try: @@ -27,20 +27,6 @@ def decrypt(secreto): raise err -async def check_deploy_in_use(db, db_aws, aws_account_id, updated_aws=None): - db_deploy = ( - db.query(Deploy) - .filter(Deploy.squad == db_aws.squad) - .filter(Deploy.environment == db_aws.environment) - .first()) - - if db_deploy: - if updated_aws and (updated_aws.squad != db_aws.squad or updated_aws.environment != db_aws.environment): - raise ResourceInUseError(aws_account_id) - elif not updated_aws: - raise ResourceInUseError(aws_account_id) - - async def create_aws_profile(db: Session, aws: schemas_aws.AwsAsumeProfile) -> schemas_aws.AwsAccountResponse: encrypt_access_key_id = encrypt(aws.access_key_id) encrypt_secret_access_key = encrypt(aws.secret_access_key) @@ -68,7 +54,7 @@ async def create_aws_profile(db: Session, aws: schemas_aws.AwsAsumeProfile) -> s async def update_aws_profile(db: Session, aws_account_id: int, updated_aws: schemas_aws.AwsAccountUpdate) -> schemas_aws.AwsAccountResponse: db_aws = db.query(models.Aws_provider).filter(models.Aws_provider.id == aws_account_id).first() - await check_deploy_in_use(db=db, db_aws=db_aws, updated_aws=updated_aws, aws_account_id=aws_account_id) + await check_deploy_in_use(db=db, db_provider=db_aws, cloud_provider="aws", updated=updated_aws, account_id=aws_account_id) if db_aws: if updated_aws.access_key_id: @@ -152,7 +138,7 @@ async def delete_aws_profile_by_id(db: Session, aws_account_id: int) -> schemas_ db_aws = db.query(models.Aws_provider).filter( models.Aws_provider.id == aws_account_id ).first() - await check_deploy_in_use(db, db_aws, aws_account_id) + await check_deploy_in_use(db=db, db_provider=db_aws, cloud_provider="aws", account_id=aws_account_id) if db_aws: db.delete(db_aws) db.commit() diff --git a/sld-api-backend/src/azure/api/container/create.py b/sld-api-backend/src/azure/api/container/create.py index b3f27563..4a8c4135 100644 --- a/sld-api-backend/src/azure/api/container/create.py +++ b/sld-api-backend/src/azure/api/container/create.py @@ -13,7 +13,7 @@ async def create_new_azure_profile( azure: schemas_azure.AzureBase, current_user: schemas_users.User = Depends(deps.get_current_active_user), db: Session = Depends(deps.get_db), -): +) -> schemas_azure.AzureAccountResponse: if not crud_users.is_master(db, current_user): raise HTTPException(status_code=403, detail="Not enough permissions") @@ -22,19 +22,22 @@ async def create_new_azure_profile( status_code=409, detail="The squad or environment field must have a value that is not a string.", ) - db_azure_account = crud_azure.get_squad_azure_profile( - db=db, squad=azure.squad, environment=azure.environment + filters = schemas_azure.AzureAccountFilter() + filters.squad = azure.squad + filters.environment = azure.environment + db_aws_account = await crud_azure.get_all_azure_profile( + db=db, filters=filters ) - if db_azure_account: + if db_aws_account: raise HTTPException(status_code=409, detail="Account already exists") try: - result = crud_azure.create_azure_profile(db=db, azure=azure) + result = await crud_azure.create_azure_profile(db=db, azure=azure) crud_activity.create_activity_log( db=db, username=current_user.username, squad=current_user.squad, action=f"Create Azure Account {azure.subscription_id}", ) - return {"result": f"Create Azure account {azure.squad} {azure.environment}"} + return result except Exception as err: raise HTTPException(status_code=400, detail=str(err)) diff --git a/sld-api-backend/src/azure/api/container/delete.py b/sld-api-backend/src/azure/api/container/delete.py index 9b128ef2..7b4552b6 100644 --- a/sld-api-backend/src/azure/api/container/delete.py +++ b/sld-api-backend/src/azure/api/container/delete.py @@ -5,25 +5,34 @@ from src.azure.infrastructure import repositories as crud_azure from src.shared.security import deps from src.users.domain.entities import users as schemas_users +from src.azure.domain.entities import azure as schemas_azure from src.users.infrastructure import repositories as crud_users +from src.shared.domain.exeptions.in_use import ResourceInUseError async def azure_account_by_id( azure_account_id: int, current_user: schemas_users.User = Depends(deps.get_current_active_user), db: Session = Depends(deps.get_db), -): +) -> schemas_azure.AzureAccountResponse: if not crud_users.is_master(db, current_user): raise HTTPException(status_code=403, detail="Not enough permissions") - - result = crud_azure.delete_azure_profile_by_id( - db=db, azure_profile_id=azure_account_id - ) - crud_activity.create_activity_log( - db=db, - username=current_user.username, - squad=current_user.squad, - action=f"Delete Azure account {azure_account_id}", - ) - return result + filters = schemas_azure.AzureAccountFilter() + filters.id = azure_account_id + db_Azure_account = await crud_azure.get_all_azure_profile(db=db, filters=filters) + if not db_Azure_account: + raise HTTPException(status_code=404, detail="Account not found") + try: + result = await crud_azure.delete_azure_profile_by_id(db=db, azure_account_id=azure_account_id) + crud_activity.create_activity_log( + db=db, + username=current_user.username, + squad=current_user.squad, + action=f"Delete Azure account {azure_account_id}", + ) + return result + except ResourceInUseError as err: + raise HTTPException(status_code=409, detail=str(err)) + except Exception as err: + raise err diff --git a/sld-api-backend/src/azure/api/container/get.py b/sld-api-backend/src/azure/api/container/get.py index 34a543ba..ca6c099a 100644 --- a/sld-api-backend/src/azure/api/container/get.py +++ b/sld-api-backend/src/azure/api/container/get.py @@ -5,14 +5,16 @@ from src.shared.security import deps from src.users.domain.entities import users as schemas_users from src.users.infrastructure import repositories as crud_users +from src.azure.domain.entities import azure as schemas_azure async def get_all_azure_accounts( - current_user: schemas_users.User = Depends(deps.get_current_active_user), + skip: int = 0, + limit: int = 100, db: Session = Depends(deps.get_db), + current_user: schemas_users.User = Depends(deps.get_current_active_user), + filters: schemas_azure.AzureAccountFilter = Depends(schemas_azure.AzureAccountFilter), ): if not crud_users.is_master(db, current_user): - return crud_azure.get_squad_azure_profile( - db=db, squad=current_user.squad, environment=None - ) - return crud_azure.get_all_azure_profile(db=db) + filters.squad = current_user.squad + return await crud_azure.get_all_azure_profile(db=db, filters=filters, skip=skip, limit=limit) diff --git a/sld-api-backend/src/azure/api/container/update.py b/sld-api-backend/src/azure/api/container/update.py new file mode 100644 index 00000000..33377849 --- /dev/null +++ b/sld-api-backend/src/azure/api/container/update.py @@ -0,0 +1,38 @@ +from fastapi import Depends, HTTPException +from sqlalchemy.orm import Session + +from src.activityLogs.infrastructure import repositories as crud_activity +from src.azure.domain.entities import azure as schemas_azure +from src.azure.infrastructure import repositories as crud_azure +from src.shared.security import deps +from src.users.domain.entities import users as schemas_users +from src.users.infrastructure import repositories as crud_users +from src.shared.domain.exeptions.in_use import ResourceInUseError + + +async def update_azure_account( + azure_account_id: int, + azure: schemas_azure.AzureAccountUpdate, + current_user: schemas_users.User = Depends(deps.get_current_active_user), + db: Session = Depends(deps.get_db), +) -> schemas_azure.AzureAccountResponse: + if not crud_users.is_master(db, current_user): + raise HTTPException(status_code=403, detail="Not enough permissions") + try: + filters = schemas_azure.AzureAccountFilter() + filters.id = azure_account_id + db_azure_account = await crud_azure.get_all_azure_profile(db=db, filters=filters) + if not db_azure_account: + raise HTTPException(status_code=404, detail="Account not found") + result = await crud_azure.update_azure_profile(db=db, azure_account_id=azure_account_id, updated_azure=azure) + crud_activity.create_activity_log( + db=db, + username=current_user.username, + squad=current_user.squad, + action=f"Update azure account {azure.squad} {azure.environment}", + ) + return result + except ResourceInUseError as err: + raise HTTPException(status_code=409, detail=str(err)) + except Exception as err: + raise err diff --git a/sld-api-backend/src/azure/api/v1/azure.py b/sld-api-backend/src/azure/api/v1/azure.py index 8a9654ef..53734c6f 100644 --- a/sld-api-backend/src/azure/api/v1/azure.py +++ b/sld-api-backend/src/azure/api/v1/azure.py @@ -1,6 +1,6 @@ from fastapi import APIRouter, Depends -from src.azure.api.container import create, delete, get +from src.azure.api.container import create, delete, get, update from src.azure.domain.entities import azure as schemas_azure router = APIRouter() @@ -8,22 +8,31 @@ @router.post("/", status_code=200) async def create_new_azure_profile( - create_azure_account: schemas_azure.AzureBase = Depends( + create_azure_account: schemas_azure.AzureAccountResponse = Depends( create.create_new_azure_profile ), ): return create_azure_account -@router.get("/", status_code=200, response_model=list[schemas_azure.AzureResponse]) +@router.patch("/{azure_account_id}", status_code=200) +async def update_aws_account( + update_account: schemas_azure.AzureAccountResponse = Depends( + update.update_azure_account + ), +): + return update_account + + +@router.get("/", status_code=200, response_model=list[schemas_azure.AzureAccountResponse]) async def get_all_azure_accounts( - get_azure_account: schemas_azure.AzureBase = Depends(get.get_all_azure_accounts), + get_azure_account: schemas_azure.AzureAccountResponse = Depends(get.get_all_azure_accounts), ): return get_azure_account @router.delete("/{azure_account_id}") async def delete_azure_account_by_id( - delete_azure_account: schemas_azure.AzureBase = Depends(delete.azure_account_by_id), + delete_azure_account: schemas_azure.AzureAccountResponse = Depends(delete.azure_account_by_id), ): return delete_azure_account diff --git a/sld-api-backend/src/azure/domain/entities/azure.py b/sld-api-backend/src/azure/domain/entities/azure.py index e82c19d6..14ca7e77 100644 --- a/sld-api-backend/src/azure/domain/entities/azure.py +++ b/sld-api-backend/src/azure/domain/entities/azure.py @@ -1,4 +1,5 @@ -from pydantic import BaseModel, constr +from pydantic import BaseModel, constr, SecretStr +from typing import Optional, Dict, Any class AzureBase(BaseModel): @@ -8,18 +9,52 @@ class AzureBase(BaseModel): client_id: constr(strip_whitespace=True) client_secret: constr(strip_whitespace=True) tenant_id: constr(strip_whitespace=True) + extra_variables: Optional[Dict[str, Any]] = None -class Azure(AzureBase): - id: int +class AzureId(BaseModel): + id: Optional[int] = None class Config: from_attributes = True -class AzureResponse(BaseModel): - id: int +class AzureAccountResponseBase(AzureId): squad: constr(strip_whitespace=True) environment: constr(strip_whitespace=True) subscription_id: constr(strip_whitespace=True) tenant_id: constr(strip_whitespace=True) + + +class AzureAccountResponse(AzureAccountResponseBase): + extra_variables: Optional[Dict[str, SecretStr]] + + class Config: + from_attributes = True + + +class AzureAccountResponseRepo(AzureAccountResponseBase): + client_id: str + client_secret: str + extra_variables: Optional[Dict[str, Any]] = None + + class Config: + from_attributes = True + + +class AzureAccount(BaseModel): + squad: Optional[str] = None + access_key_id: Optional[str] = None + environment: Optional[str] = None + client_id: Optional[str] = None + subscription_id: Optional[str] = None + tenant_id: Optional[str] = None + + +class AzureAccountFilter(AzureAccount, AzureId): + pass + + +class AzureAccountUpdate(AzureAccount): + client_secret: Optional[str] = None + extra_variables: Optional[Dict[str, Any]] = None diff --git a/sld-api-backend/src/azure/infrastructure/repositories.py b/sld-api-backend/src/azure/infrastructure/repositories.py index 168eba48..44837de9 100644 --- a/sld-api-backend/src/azure/infrastructure/repositories.py +++ b/sld-api-backend/src/azure/infrastructure/repositories.py @@ -1,10 +1,12 @@ import datetime -from sqlalchemy import exc +from sqlalchemy import desc, or_ from sqlalchemy.orm import Session +from typing import List import src.azure.infrastructure.models as models from src.azure.domain.entities import azure as schemas_azure +from src.shared.infrastructure.respository import check_deploy_in_use from src.shared.security.vault import vault_decrypt, vault_encrypt @@ -24,7 +26,8 @@ def decrypt(secreto): raise err -def create_azure_profile(db: Session, azure: schemas_azure.AzureBase): +async def create_azure_profile(db: Session, azure: schemas_azure.AzureBase) -> schemas_azure.AzureAccountResponse: + encrypted_extra_variables = {key: encrypt(val) for key, val in azure.extra_variables.items()} if azure.extra_variables else None encrypt_client_id = encrypt(azure.client_id) encrypt_client_secret = encrypt(azure.client_secret) @@ -36,99 +39,113 @@ def create_azure_profile(db: Session, azure: schemas_azure.AzureBase): tenant_id=azure.tenant_id, created_at=datetime.datetime.now(), squad=azure.squad, + extra_variables=encrypted_extra_variables, ) + try: db.add(db_azure) db.commit() db.refresh(db_azure) - return db_azure - except exc.IntegrityError as err: - raise ValueError(str(err.__dict__["orig"])) + return schemas_azure.AzureAccountResponse.model_validate(obj=db_azure) except Exception as err: + db.rollback() raise err -def get_credentials_azure_profile(db: Session, environment: str, squad: str): - get_client_id = ( - db.query(models.Azure_provider.client_id) - .filter(models.Azure_provider.environment == environment) - .filter(models.Azure_provider.squad == squad) - .first() - ) - get_client_secret = ( - db.query(models.Azure_provider.client_secret) - .filter(models.Azure_provider.environment == environment) - .filter(models.Azure_provider.squad == squad) - .first() - ) - subscription_id = ( - db.query(models.Azure_provider.subscription_id) +async def update_azure_profile(db: Session, azure_account_id: int, updated_azure: schemas_azure.AzureAccountUpdate) -> schemas_azure.AzureAccountResponse: + db_azure = db.query(models.Azure_provider).filter(models.Azure_provider.id == azure_account_id).first() + await check_deploy_in_use(db=db, db_provider=db_azure, cloud_provider="azure", updated=updated_azure, account_id=azure_account_id) + + if db_azure: + if updated_azure.client_id: + db_azure.client_id = encrypt(updated_azure.client_id) + if updated_azure.client_secret: + db_azure.client_secret = encrypt(updated_azure.client_secret) + if updated_azure.extra_variables: + current_extra_variables = db_azure.extra_variables or {} + for key, value in current_extra_variables.items(): + current_extra_variables[key] = decrypt(value) + current_extra_variables = {key: value for key, value in current_extra_variables.items() if key in updated_azure.extra_variables} + + for key, value in updated_azure.extra_variables.items(): + if key and value and "***" not in value: + current_extra_variables[key] = value + encrypted_extra_variables = {key: encrypt(value) for key, value in current_extra_variables.items()} + db_azure.extra_variables = encrypted_extra_variables + db_azure.environment = updated_azure.environment + db_azure.subscription_id = updated_azure.subscription_id + db_azure.tenant_id = updated_azure.tenant_id + db_azure.squad = updated_azure.squad + db_azure.updated_at = datetime.datetime.now() + + try: + db.commit() + db.refresh(db_azure) + return schemas_azure.AzureAccountResponse.model_validate(db_azure) + except Exception as err: + db.rollback() + raise err + else: + raise ValueError(f"azure profile with id {azure_account_id} not found") + + +async def get_credentials_azure_profile(db: Session, environment: str, squad: str) -> schemas_azure.AzureAccountResponseRepo: + db_azure = ( + db.query(models.Azure_provider) .filter(models.Azure_provider.environment == environment) .filter(models.Azure_provider.squad == squad) .first() ) - tenant_id = ( - db.query(models.Azure_provider.tenant_id) - .filter(models.Azure_provider.environment == environment) - .filter(models.Azure_provider.squad == squad) - .first() - ) - try: - return { - "client_id": decrypt(get_client_id[0]), - "client_secret": decrypt(get_client_secret[0]), - "subscription_id": subscription_id[0], - "tenant_id": tenant_id[0], - } - except Exception as err: - raise err + return schemas_azure.AzureAccountResponseRepo.model_validate(obj=db_azure) -def get_squad_azure_profile(db: Session, squad: str, environment: str): +async def get_all_azure_profile( + db: Session, filters: schemas_azure.AzureAccountFilter, skip: int = 0, limit: int = 100 +) -> List[schemas_azure.AzureAccountResponse]: try: - if environment != None: - return ( - db.query(models.Azure_provider) - .filter(models.Azure_provider.squad == squad) - .filter(models.Azure_provider.environment == environment) - .first() + query = db.query(models.Azure_provider) + + for field, value in filters.model_dump().items(): + if value is not None: + if field == 'squad' and isinstance(value, list): + or_conditions = [getattr(models.Azure_provider, field).like(f"%{v}%") for v in value] + query = query.filter(or_(*or_conditions)) + else: + query = query.filter(getattr(models.Azure_provider, field) == value) + + db_azure = query.order_by(desc(models.Azure_provider.id)).offset(skip).limit(limit).all() + + azure_profiles = [] + for result in db_azure: + azure_profile = schemas_azure.AzureAccountResponse( + id=result.id, + squad=result.squad, + environment=result.environment, + subscription_id=result.subscription_id, + tenant_id=result.tenant_id, + extra_variables=result.extra_variables, + created_at=result.created_at, + updated_at=result.updated_at, ) - result = [] - for i in squad: - result.extend( - db.query(models.Azure_provider) - .filter(models.Azure_provider.squad == i) - .all() - ) - return set(result) - except Exception as err: - raise err - - -def get_all_azure_profile(db: Session): - try: - return db.query(models.Azure_provider).all() - except Exception as err: - raise err - - -def delete_azure_profile_by_id(db: Session, azure_profile_id: int): - try: - db.query(models.Azure_provider).filter( - models.Azure_provider.id == azure_profile_id - ).delete() - db.commit() - return {azure_profile_id: "deleted", "azure_profile_id": azure_profile_id} + azure_profiles.append(azure_profile) + return azure_profiles except Exception as err: raise err -def get_cloud_account_by_id(db: Session, provider_id: int): +async def delete_azure_profile_by_id(db: Session, azure_account_id: int) -> schemas_azure.AzureAccountResponse: try: - return ( - db.query(models.Azure_provider) - .filter(models.Azure_provider.id == provider_id) - .first() - ) + db_azure = db.query(models.Azure_provider).filter( + models.Azure_provider.id == azure_account_id + ).first() + await check_deploy_in_use(db=db, db_provider=db_azure, cloud_provider="azure", account_id=azure_account_id) + if db_azure: + db.delete(db_azure) + db.commit() + response_data = schemas_azure.AzureAccountResponse.model_validate(db_azure) + return response_data + else: + raise f"Azure profile with id {azure_account_id} not found" except Exception as err: + db.rollback() raise err diff --git a/sld-api-backend/src/shared/infrastructure/respository.py b/sld-api-backend/src/shared/infrastructure/respository.py new file mode 100644 index 00000000..55d21f14 --- /dev/null +++ b/sld-api-backend/src/shared/infrastructure/respository.py @@ -0,0 +1,20 @@ + +from src.deploy.infrastructure.models import Deploy +from src.shared.domain.exeptions.in_use import ResourceInUseError + + +async def check_deploy_in_use(db, db_provider, account_id, cloud_provider, updated=None): + db_deploy = ( + db.query(Deploy) + .filter(Deploy.squad == db_provider.squad) + .filter(Deploy.environment == db_provider.environment) + .filter(Deploy.stack_name.like(f'{cloud_provider}%')) + .first() + ) + + if db_deploy: + if updated and (updated.squad != db_provider.squad or updated.environment != db_provider.environment): + raise ResourceInUseError(account_id) + elif not updated: + raise ResourceInUseError(account_id) + From 88c42cbd801dcef7720bc2089a8903ec56baab1e Mon Sep 17 00:00:00 2001 From: d10s <79284025+D10S0VSkY-OSS@users.noreply.github.com> Date: Sat, 30 Dec 2023 20:35:55 +0100 Subject: [PATCH 13/18] =?UTF-8?q?=F0=9F=94=A7refactor:=20gcp=20backend=20a?= =?UTF-8?q?ccount?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sld-api-backend/src/azure/api/v1/azure.py | 8 +- .../src/azure/domain/entities/azure.py | 3 + .../src/gcp/api/container/create.py | 22 +-- .../src/gcp/api/container/delete.py | 40 +++-- sld-api-backend/src/gcp/api/container/get.py | 17 +- .../src/gcp/api/container/update.py | 38 +++++ sld-api-backend/src/gcp/api/v1/gcp.py | 26 ++- .../src/gcp/domain/entities/gcp.py | 60 ++++++- .../src/gcp/infrastructure/repositories.py | 148 +++++++++++------- 9 files changed, 258 insertions(+), 104 deletions(-) create mode 100644 sld-api-backend/src/gcp/api/container/update.py diff --git a/sld-api-backend/src/azure/api/v1/azure.py b/sld-api-backend/src/azure/api/v1/azure.py index 53734c6f..3db5ed6b 100644 --- a/sld-api-backend/src/azure/api/v1/azure.py +++ b/sld-api-backend/src/azure/api/v1/azure.py @@ -7,7 +7,7 @@ @router.post("/", status_code=200) -async def create_new_azure_profile( +async def create_new_azure_subscription( create_azure_account: schemas_azure.AzureAccountResponse = Depends( create.create_new_azure_profile ), @@ -16,7 +16,7 @@ async def create_new_azure_profile( @router.patch("/{azure_account_id}", status_code=200) -async def update_aws_account( +async def update_azure_subscription( update_account: schemas_azure.AzureAccountResponse = Depends( update.update_azure_account ), @@ -25,14 +25,14 @@ async def update_aws_account( @router.get("/", status_code=200, response_model=list[schemas_azure.AzureAccountResponse]) -async def get_all_azure_accounts( +async def get_all_azure_subscription( get_azure_account: schemas_azure.AzureAccountResponse = Depends(get.get_all_azure_accounts), ): return get_azure_account @router.delete("/{azure_account_id}") -async def delete_azure_account_by_id( +async def delete_azure_subscription_by_account_id( delete_azure_account: schemas_azure.AzureAccountResponse = Depends(delete.azure_account_by_id), ): return delete_azure_account diff --git a/sld-api-backend/src/azure/domain/entities/azure.py b/sld-api-backend/src/azure/domain/entities/azure.py index 14ca7e77..d3014f84 100644 --- a/sld-api-backend/src/azure/domain/entities/azure.py +++ b/sld-api-backend/src/azure/domain/entities/azure.py @@ -1,5 +1,6 @@ from pydantic import BaseModel, constr, SecretStr from typing import Optional, Dict, Any +import datetime class AzureBase(BaseModel): @@ -24,6 +25,8 @@ class AzureAccountResponseBase(AzureId): environment: constr(strip_whitespace=True) subscription_id: constr(strip_whitespace=True) tenant_id: constr(strip_whitespace=True) + created_at: Optional[datetime.datetime] = None + updated_at: Optional[datetime.datetime] = None class AzureAccountResponse(AzureAccountResponseBase): diff --git a/sld-api-backend/src/gcp/api/container/create.py b/sld-api-backend/src/gcp/api/container/create.py index 48338998..971316d3 100644 --- a/sld-api-backend/src/gcp/api/container/create.py +++ b/sld-api-backend/src/gcp/api/container/create.py @@ -9,9 +9,8 @@ from src.users.infrastructure import repositories as crud_users -async def new_gcloud_profile( +async def new_gcp_account( gcp: schemas_gcp.GcloudBase, - response: Response, current_user: schemas_users.User = Depends(deps.get_current_active_user), db: Session = Depends(deps.get_db), ): @@ -22,24 +21,25 @@ async def new_gcloud_profile( status_code=409, detail="The squad or environment field must have a value that is not a string.", ) - db_gcp_account = crud_gcp.get_squad_gcloud_profile( - db=db, squad=gcp.squad, environment=gcp.environment + filters = schemas_gcp.GcloudAccountFilter() + filters.squad = gcp.squad + filters.environment = gcp.environment + db_aws_account = await crud_gcp.get_all_gcloud_profile( + db=db, filters=filters ) - if db_gcp_account: + if db_aws_account: raise HTTPException(status_code=409, detail="Account already exists") try: - result = crud_gcp.create_gcloud_profile( + result = await crud_gcp.create_gcloud_profile( db=db, - squad=gcp.squad, - environment=gcp.environment, - gcloud_keyfile_json=gcp.gcloud_keyfile_json, + gcp=gcp, ) crud_activity.create_activity_log( db=db, username=current_user.username, squad=current_user.squad, - action=f"Create GCP account {result.id}", + action=f"Create GCP account {gcp.squad} {gcp.environment}", ) - return {"result": f"Create GCP account {gcp.squad} {gcp.environment}"} + return result except Exception as err: raise HTTPException(status_code=400, detail=err) diff --git a/sld-api-backend/src/gcp/api/container/delete.py b/sld-api-backend/src/gcp/api/container/delete.py index d2a47368..0efacb09 100644 --- a/sld-api-backend/src/gcp/api/container/delete.py +++ b/sld-api-backend/src/gcp/api/container/delete.py @@ -3,26 +3,36 @@ from src.activityLogs.infrastructure import repositories as crud_activity from src.gcp.infrastructure import repositories as crud_gcp +from src.gcp.domain.entities import gcp as schemas_gcp from src.shared.security import deps from src.users.domain.entities import users as schemas_users from src.users.infrastructure import repositories as crud_users +from src.shared.domain.exeptions.in_use import ResourceInUseError -async def gcloud_account_by_id( - gcloud_account_id, +async def gcp_account_by_id( + gcp_account_id: int, current_user: schemas_users.User = Depends(deps.get_current_active_user), db: Session = Depends(deps.get_db), -): - if not crud_users.is_master(db, current_user): - raise HTTPException(status_code=400, detail="Not enough permissions") +) -> schemas_gcp.GcloudResponse: - result = crud_gcp.delete_gcloud_profile_by_id( - db=db, gcloud_profile_id=gcloud_account_id - ) - crud_activity.create_activity_log( - db=db, - username=current_user.username, - squad=current_user.squad, - action=f"Delete GCP account {gcloud_account_id} squad", - ) - return result + if not crud_users.is_master(db, current_user): + raise HTTPException(status_code=403, detail="Not enough permissions") + filters = schemas_gcp.GcloudAccountFilter() + filters.id = gcp_account_id + db_gcp_account = await crud_gcp.get_all_gcloud_profile(db=db, filters=filters) + if not db_gcp_account: + raise HTTPException(status_code=404, detail="Account not found") + try: + result = await crud_gcp.delete_gcloud_profile_by_id(db=db, gcp_account_id=gcp_account_id) + crud_activity.create_activity_log( + db=db, + username=current_user.username, + squad=current_user.squad, + action=f"Delete AWS account {gcp_account_id}", + ) + return result + except ResourceInUseError as err: + raise HTTPException(status_code=409, detail=str(err)) + except Exception as err: + raise err diff --git a/sld-api-backend/src/gcp/api/container/get.py b/sld-api-backend/src/gcp/api/container/get.py index 23c745e6..6bb41df5 100644 --- a/sld-api-backend/src/gcp/api/container/get.py +++ b/sld-api-backend/src/gcp/api/container/get.py @@ -2,17 +2,20 @@ from sqlalchemy.orm import Session from src.gcp.infrastructure import repositories as crud_gcp +from src.gcp.domain.entities import gcp as schemas_gcp from src.shared.security import deps from src.users.domain.entities import users as schemas_users from src.users.infrastructure import repositories as crud_users -async def all_gcloud_accounts( - current_user: schemas_users.User = Depends(deps.get_current_active_user), +async def get_all_gcp_accounts( + skip: int = 0, + limit: int = 100, db: Session = Depends(deps.get_db), -): + current_user: schemas_users.User = Depends(deps.get_current_active_user), + filters: schemas_gcp.GcloudAccountFilter = Depends(schemas_gcp.GcloudAccountFilter), + +) -> list[schemas_gcp.GcloudResponse]: if not crud_users.is_master(db, current_user): - return crud_gcp.get_squad_gcloud_profile( - db=db, squad=current_user.squad, environment=None - ) - return crud_gcp.get_all_gcloud_profile(db=db) + filters.squad = current_user.squad + return await crud_gcp.get_all_gcloud_profile(db=db, filters=filters, skip=skip, limit=limit) diff --git a/sld-api-backend/src/gcp/api/container/update.py b/sld-api-backend/src/gcp/api/container/update.py new file mode 100644 index 00000000..87c4d7c8 --- /dev/null +++ b/sld-api-backend/src/gcp/api/container/update.py @@ -0,0 +1,38 @@ +from fastapi import Depends, HTTPException +from sqlalchemy.orm import Session + +from src.activityLogs.infrastructure import repositories as crud_activity +from src.gcp.domain.entities import gcp as schemas_gcp +from src.gcp.infrastructure import repositories as crud_gcp +from src.shared.security import deps +from src.users.domain.entities import users as schemas_users +from src.users.infrastructure import repositories as crud_users +from src.shared.domain.exeptions.in_use import ResourceInUseError + + +async def update_gcp_account( + gcp_account_id: int, + gcp: schemas_gcp.GcloudAccountUpdate, + current_user: schemas_users.User = Depends(deps.get_current_active_user), + db: Session = Depends(deps.get_db), +) -> schemas_gcp.GcloudResponse: + if not crud_users.is_master(db, current_user): + raise HTTPException(status_code=403, detail="Not enough permissions") + try: + filters = schemas_gcp.GcloudAccountFilter() + filters.id = gcp_account_id + db_gcp_account = await crud_gcp.get_all_gcloud_profile(db=db, filters=filters) + if not db_gcp_account: + raise HTTPException(status_code=404, detail="Account not found") + result = await crud_gcp.update_gcloud_profile(db=db, gcp_account_id=gcp_account_id, updated_gcp=gcp) + crud_activity.create_activity_log( + db=db, + username=current_user.username, + squad=current_user.squad, + action=f"Update AWS account {gcp.squad} {gcp.environment}", + ) + return result + except ResourceInUseError as err: + raise HTTPException(status_code=409, detail=str(err)) + except Exception as err: + raise err diff --git a/sld-api-backend/src/gcp/api/v1/gcp.py b/sld-api-backend/src/gcp/api/v1/gcp.py index a618cd04..aa5b8706 100644 --- a/sld-api-backend/src/gcp/api/v1/gcp.py +++ b/sld-api-backend/src/gcp/api/v1/gcp.py @@ -1,27 +1,37 @@ from fastapi import APIRouter, Depends -from src.gcp.api.container import create, delete, get +from src.gcp.api.container import create, delete, get, update from src.gcp.domain.entities import gcp as schemas_gcp router = APIRouter() @router.post("/", status_code=200) -async def create_new_gcloud_profile( - create_gcp_account: schemas_gcp.GcloudBase = Depends(create.new_gcloud_profile), +async def create_new_gcp_project( + create_gcp_account: schemas_gcp.GcloudBase = Depends(create.new_gcp_account), ): return create_gcp_account +@router.patch("/{gcp_account_id}", status_code=200) +async def update_gcp_project( + update_account: schemas_gcp.GcloudResponse = Depends( + update.update_gcp_account + ), +): + return update_account + + + @router.get("/", status_code=200, response_model=list[schemas_gcp.GcloudResponse]) -async def get_all_gcloud_accounts( - get_gcp_account: schemas_gcp.GcloudBase = Depends(get.all_gcloud_accounts), +async def get_all_gcp_project( + get_gcp_account: schemas_gcp.GcloudBase = Depends(get.get_all_gcp_accounts), ): return get_gcp_account -@router.delete("/{gcloud_account_id}") -async def delete_gcloud_account_by_id( - get_gcp_account: schemas_gcp.GcloudBase = Depends(delete.gcloud_account_by_id), +@router.delete("/{gcp_account_id}") +async def delete_gcp_project_by_id( + get_gcp_account: schemas_gcp.GcloudBase = Depends(delete.gcp_account_by_id), ): return get_gcp_account diff --git a/sld-api-backend/src/gcp/domain/entities/gcp.py b/sld-api-backend/src/gcp/domain/entities/gcp.py index 897b872c..94464955 100644 --- a/sld-api-backend/src/gcp/domain/entities/gcp.py +++ b/sld-api-backend/src/gcp/domain/entities/gcp.py @@ -1,10 +1,39 @@ -from pydantic import BaseModel, constr +from pydantic import BaseModel, constr, SecretStr +from typing import Optional, Dict, Any, List +import datetime + + +class GCloudJson(BaseModel): + type: str + project_id: str + private_key_id: str + private_key: str + client_email: str + client_id: str + auth_uri: str + token_uri: str + auth_provider_x509_cert_url: str + client_x509_cert_url: str + universe_domain: str + scopes: Optional[List[str]] = None + subject_type: Optional[str] = None + token_introspection_uri: Optional[str] = None + revoke_uri: Optional[str] = None class GcloudBase(BaseModel): squad: constr(strip_whitespace=True) environment: constr(strip_whitespace=True) - gcloud_keyfile_json: dict + gcloud_keyfile_json: Dict[str, str] + extra_variables: Optional[Dict[str, Any]] = None + + +class GcloudAccount(GcloudBase): + pass + + +class GcloudAccountUpdate(GcloudBase): + pass class Gcloud(GcloudBase): @@ -13,7 +42,34 @@ class Gcloud(GcloudBase): class Config: from_attributes = True + class GcloudResponse(BaseModel): id: int squad: str environment: str + extra_variables: Optional[Dict[str, SecretStr]] + created_at: Optional[datetime.datetime] = None + updated_at: Optional[datetime.datetime] = None + + class Config: + from_attributes = True + + +class GcloudResponseRepo(BaseModel): + id: int + squad: str + environment: str + gcloud_keyfile_json: GCloudJson + extra_variables: Optional[Dict[str, Any]] = None + + class Config: + from_attributes = True + + +class GcloudAccountFilter(BaseModel): + id: Optional[int] = None + squad: Optional[str] = None + environment: Optional[str] = None + + class Config: + from_attributes = True diff --git a/sld-api-backend/src/gcp/infrastructure/repositories.py b/sld-api-backend/src/gcp/infrastructure/repositories.py index 0bd52dd8..f4e2e6d5 100644 --- a/sld-api-backend/src/gcp/infrastructure/repositories.py +++ b/sld-api-backend/src/gcp/infrastructure/repositories.py @@ -1,10 +1,12 @@ import datetime - -from sqlalchemy import exc +from typing import List from sqlalchemy.orm import Session +from sqlalchemy import desc, or_ import src.gcp.infrastructure.models as models +from src.gcp.domain.entities import gcp as schemas_gcp from src.shared.security.vault import vault_decrypt, vault_encrypt +from src.shared.infrastructure.respository import check_deploy_in_use @vault_encrypt @@ -23,86 +25,118 @@ def decrypt(secreto): raise err -def create_gcloud_profile( - db: Session, squad: str, environment: str, gcloud_keyfile_json: dict -): - encrypt_glcoud_keyfile_json = encrypt(str(gcloud_keyfile_json)) +async def create_gcloud_profile(db: Session, gcp: schemas_gcp.GcloudBase) -> schemas_gcp.GcloudResponse: + encrypted_extra_variables = {key: encrypt(val) for key, val in gcp.extra_variables.items()} if gcp.extra_variables else None + encrypt_glcoud_keyfile_json = encrypt(str(gcp.gcloud_keyfile_json)) - db_gcloud = models.Gcloud_provider( + db_gcp = models.Gcloud_provider( + squad=gcp.squad, + environment=gcp.environment, gcloud_keyfile_json=encrypt_glcoud_keyfile_json, - environment=environment, + extra_variables=encrypted_extra_variables, created_at=datetime.datetime.now(), - squad=squad, ) try: - db.add(db_gcloud) + db.add(db_gcp) db.commit() - db.refresh(db_gcloud) - return db_gcloud - except exc.IntegrityError as err: - raise ValueError(str(err.__dict__["orig"])) + db.refresh(db_gcp) + return schemas_gcp.GcloudResponse.model_validate(obj=db_gcp) except Exception as err: + db.rollback() raise err -def get_credentials_gcloud_profile(db: Session, environment: str, squad: str): +async def get_credentials_gcloud_profile(db: Session, environment: str, squad: str) -> schemas_gcp.GcloudResponseRepo: try: - get_gcloud_keyfile = ( - db.query(models.Gcloud_provider.gcloud_keyfile_json) + db_gcp = ( + db.query(models.Gcloud_provider) .filter(models.Gcloud_provider.environment == environment) .filter(models.Gcloud_provider.squad == squad) .first() ) - return {"gcloud_keyfile_json": decrypt(get_gcloud_keyfile[0])} + return schemas_gcp.GcloudResponseRepo.model_validate(obj=db_gcp) except Exception as err: raise err -def get_squad_gcloud_profile(db: Session, squad: str, environment: str): +async def update_gcloud_profile(db: Session, gcp_account_id: int, updated_gcp: schemas_gcp.GcloudAccountUpdate) -> schemas_gcp.GcloudResponse: + db_gcp = db.query(models.Gcloud_provider).filter(models.Gcloud_provider.id == gcp_account_id).first() + await check_deploy_in_use(db=db, db_provider=db_gcp, cloud_provider="gcp", updated=updated_gcp, account_id=gcp_account_id) + + if db_gcp: + if updated_gcp.gcloud_keyfile_json: + db_gcp.gcloud_keyfile_json = encrypt(str(updated_gcp.gcloud_keyfile_json)) + if updated_gcp.extra_variables: + current_extra_variables = db_gcp.extra_variables or {} + for key, value in current_extra_variables.items(): + current_extra_variables[key] = decrypt(value) + current_extra_variables = {key: value for key, value in current_extra_variables.items() if key in updated_gcp.extra_variables} + + for key, value in updated_gcp.extra_variables.items(): + if key and value and "***" not in value: + current_extra_variables[key] = value + encrypted_extra_variables = {key: encrypt(value) for key, value in current_extra_variables.items()} + db_gcp.extra_variables = encrypted_extra_variables + db_gcp.environment = updated_gcp.environment + db_gcp.squad = updated_gcp.squad + db_gcp.updated_at = datetime.datetime.now() + + try: + db.commit() + db.refresh(db_gcp) + return schemas_gcp.GcloudResponse.model_validate(db_gcp) + except Exception as err: + db.rollback() + raise err + else: + raise ValueError(f"GCP profile with id {gcp_account_id} not found") + + +async def get_all_gcloud_profile( + db: Session, filters: schemas_gcp.GcloudAccountFilter, skip: int = 0, limit: int = 100 +) -> List[schemas_gcp.GcloudResponse]: try: - if environment != None: - return ( - db.query(models.Gcloud_provider) - .filter(models.Gcloud_provider.squad == squad) - .filter(models.Gcloud_provider.environment == environment) - .first() - ) - result = [] - for i in squad: - result.extend( - db.query(models.Gcloud_provider) - .filter(models.Gcloud_provider.squad == i) - .all() + query = db.query(models.Gcloud_provider) + + for field, value in filters.model_dump().items(): + if value is not None: + if field == 'squad' and isinstance(value, list): + or_conditions = [getattr(models.Gcloud_provider, field).like(f"%{v}%") for v in value] + query = query.filter(or_(*or_conditions)) + else: + query = query.filter(getattr(models.Gcloud_provider, field) == value) + + db_gcp = query.order_by(desc(models.Gcloud_provider.id)).offset(skip).limit(limit).all() + + gcp_profiles = [] + for result in db_gcp: + gcp_profile = schemas_gcp.GcloudResponse( + id=result.id, + squad=result.squad, + environment=result.environment, + extra_variables=result.extra_variables, + created_at=result.created_at, + updated_at=result.updated_at, ) - return set(result) + gcp_profiles.append(gcp_profile) + return gcp_profiles except Exception as err: raise err -def get_all_gcloud_profile(db: Session): +async def delete_gcloud_profile_by_id(db: Session, gcp_account_id: int) -> schemas_gcp.GcloudResponse: try: - return db.query(models.Gcloud_provider).all() - except Exception as err: - raise err - - -def delete_gcloud_profile_by_id(db: Session, gcloud_profile_id: int): - try: - db.query(models.Gcloud_provider).filter( - models.Gcloud_provider.id == gcloud_profile_id - ).delete() - db.commit() - return {gcloud_profile_id: "deleted", "gcloud_profile_id": gcloud_profile_id} - except Exception as err: - raise err - - -def get_cloud_account_by_id(db: Session, provider_id: int): - try: - return ( - db.query(models.Gcloud_provider) - .filter(models.Gcloud_provider.id == provider_id) - .first() - ) + db_gcp = db.query(models.Gcloud_provider).filter( + models.Gcloud_provider.id == gcp_account_id + ).first() + await check_deploy_in_use(db=db, db_provider=db_gcp, cloud_provider="gcp", account_id=gcp_account_id) + if db_gcp: + db.delete(db_gcp) + db.commit() + response_data = schemas_gcp.GcloudResponse.model_validate(db_gcp) + return response_data + else: + raise f"GCP profile with id {gcp_account_id} not found" except Exception as err: + db.rollback() raise err From e4c001a173f8fac1ba89a954709d6536f8c7e8ba Mon Sep 17 00:00:00 2001 From: d10s <79284025+D10S0VSkY-OSS@users.noreply.github.com> Date: Sat, 30 Dec 2023 20:56:46 +0100 Subject: [PATCH 14/18] =?UTF-8?q?=F0=9F=94=A7refactor:=20decrypt=20secrets?= =?UTF-8?q?=20to=20worker?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sld-api-backend/src/gcp/domain/entities/gcp.py | 2 +- sld-api-backend/src/shared/helpers/get_data.py | 4 ++-- .../src/worker/security/providers_credentials.py | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sld-api-backend/src/gcp/domain/entities/gcp.py b/sld-api-backend/src/gcp/domain/entities/gcp.py index 94464955..a39eb08c 100644 --- a/sld-api-backend/src/gcp/domain/entities/gcp.py +++ b/sld-api-backend/src/gcp/domain/entities/gcp.py @@ -59,7 +59,7 @@ class GcloudResponseRepo(BaseModel): id: int squad: str environment: str - gcloud_keyfile_json: GCloudJson + gcloud_keyfile_json: str extra_variables: Optional[Dict[str, Any]] = None class Config: diff --git a/sld-api-backend/src/shared/helpers/get_data.py b/sld-api-backend/src/shared/helpers/get_data.py index c434bf79..6c94f762 100644 --- a/sld-api-backend/src/shared/helpers/get_data.py +++ b/sld-api-backend/src/shared/helpers/get_data.py @@ -189,12 +189,12 @@ async def check_prefix(db, stack_name: str, environment: str, squad: str): ) return secreto elif any(i in stack_name.lower() for i in settings.GCLOUD_PREFIX): - secreto = crud_gcp.get_credentials_gcloud_profile( + secreto = await crud_gcp.get_credentials_gcloud_profile( db=db, environment=environment, squad=squad ) return secreto elif any(i in stack_name.lower() for i in settings.AZURE_PREFIX): - secreto = crud_azure.get_credentials_azure_profile( + secreto = await crud_azure.get_credentials_azure_profile( db=db, environment=environment, squad=squad ) return secreto diff --git a/sld-api-backend/src/worker/security/providers_credentials.py b/sld-api-backend/src/worker/security/providers_credentials.py index 6147001f..bb519436 100644 --- a/sld-api-backend/src/worker/security/providers_credentials.py +++ b/sld-api-backend/src/worker/security/providers_credentials.py @@ -65,15 +65,15 @@ def secret( elif any(i in stack_name.lower() for i in settings.GCLOUD_PREFIX): gcloud_keyfile = f"/tmp/{stack_name}/{environment}/{squad}/{name}/gcp_{environment}_{stack_name}_{name}.json" - gcloud_keyfile_data = ast.literal_eval(secreto.get("gcloud_keyfile_json")) + gcloud_keyfile_data = ast.literal_eval(decrypt(secreto.get("gcloud_keyfile_json"))) with open(gcloud_keyfile, "w") as gcloud_file: json.dump(gcloud_keyfile_data, gcloud_file, indent=4) os.environ["GOOGLE_CLOUD_KEYFILE_JSON"] = gcloud_keyfile elif any(i in stack_name.lower() for i in settings.AZURE_PREFIX): - os.environ["ARM_CLIENT_ID"] = secreto.get("client_id") - os.environ["ARM_CLIENT_SECRET"] = secreto.get("client_secret") + os.environ["ARM_CLIENT_ID"] = decrypt(secreto.get("client_id")) + os.environ["ARM_CLIENT_SECRET"] = decrypt(secreto.get("client_secret")) os.environ["ARM_SUBSCRIPTION_ID"] = secreto.get("subscription_id") os.environ["ARM_TENANT_ID"] = secreto.get("tenant_id") From 115920acf09eb2546f5a88b85dae19cb7c9e2900 Mon Sep 17 00:00:00 2001 From: d10s <79284025+D10S0VSkY-OSS@users.noreply.github.com> Date: Sat, 30 Dec 2023 21:50:19 +0100 Subject: [PATCH 15/18] =?UTF-8?q?=F0=9F=94=A7refactor:=20gcp=20export=20ex?= =?UTF-8?q?tra=5Fvaribles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sld-api-backend/src/worker/security/providers_credentials.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sld-api-backend/src/worker/security/providers_credentials.py b/sld-api-backend/src/worker/security/providers_credentials.py index bb519436..7cd93369 100644 --- a/sld-api-backend/src/worker/security/providers_credentials.py +++ b/sld-api-backend/src/worker/security/providers_credentials.py @@ -64,6 +64,7 @@ def secret( logging.warning(err) elif any(i in stack_name.lower() for i in settings.GCLOUD_PREFIX): + export_environment_variables(secreto) gcloud_keyfile = f"/tmp/{stack_name}/{environment}/{squad}/{name}/gcp_{environment}_{stack_name}_{name}.json" gcloud_keyfile_data = ast.literal_eval(decrypt(secreto.get("gcloud_keyfile_json"))) with open(gcloud_keyfile, "w") as gcloud_file: @@ -72,6 +73,7 @@ def secret( os.environ["GOOGLE_CLOUD_KEYFILE_JSON"] = gcloud_keyfile elif any(i in stack_name.lower() for i in settings.AZURE_PREFIX): + export_environment_variables(secreto) os.environ["ARM_CLIENT_ID"] = decrypt(secreto.get("client_id")) os.environ["ARM_CLIENT_SECRET"] = decrypt(secreto.get("client_secret")) os.environ["ARM_SUBSCRIPTION_ID"] = secreto.get("subscription_id") From 89e1f5c9c347e77dbf5230c3ce10b54c5f7cc1aa Mon Sep 17 00:00:00 2001 From: d10s <79284025+D10S0VSkY-OSS@users.noreply.github.com> Date: Sun, 31 Dec 2023 00:52:00 +0100 Subject: [PATCH 16/18] =?UTF-8?q?=F0=9F=94=A7refactor:=20azure=20export=20?= =?UTF-8?q?extra=5Fvaribles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/gcp/domain/entities/gcp.py | 11 +- sld-dashboard/app/home/forms.py | 63 +++- sld-dashboard/app/home/routes.py | 306 +++++++++++------- .../app/home/templates/azure-edit.html | 163 ++++++++++ .../app/home/templates/azure-list.html | 14 + .../app/home/templates/azure-new.html | 53 ++- .../app/home/templates/gcp-edit.html | 149 +++++++++ .../app/home/templates/gcp-list.html | 14 + sld-dashboard/app/home/templates/gcp-new.html | 51 ++- 9 files changed, 698 insertions(+), 126 deletions(-) create mode 100644 sld-dashboard/app/home/templates/azure-edit.html create mode 100644 sld-dashboard/app/home/templates/gcp-edit.html diff --git a/sld-api-backend/src/gcp/domain/entities/gcp.py b/sld-api-backend/src/gcp/domain/entities/gcp.py index a39eb08c..2ea7326b 100644 --- a/sld-api-backend/src/gcp/domain/entities/gcp.py +++ b/sld-api-backend/src/gcp/domain/entities/gcp.py @@ -32,10 +32,6 @@ class GcloudAccount(GcloudBase): pass -class GcloudAccountUpdate(GcloudBase): - pass - - class Gcloud(GcloudBase): id: int @@ -73,3 +69,10 @@ class GcloudAccountFilter(BaseModel): class Config: from_attributes = True + + +class GcloudAccountUpdate(BaseModel): + squad: Optional[str] = None + environment: Optional[str] = None + gcloud_keyfile_json: Optional[Dict[str, Any]] = None + extra_variables: Optional[Dict[str, Any]] = None diff --git a/sld-dashboard/app/home/forms.py b/sld-dashboard/app/home/forms.py index c3663018..703370bf 100644 --- a/sld-dashboard/app/home/forms.py +++ b/sld-dashboard/app/home/forms.py @@ -246,7 +246,6 @@ class AwsForm(FlaskForm): extra_variables = FieldList(FormField(ExtraVariableForm), label='Extra Variables') - class GcpForm(FlaskForm): squad = StringField( "Squad", @@ -263,12 +262,33 @@ class GcpForm(FlaskForm): ], ) gcloud_keyfile_json = TextAreaField( - "gcloud keyfile json", + "gcloud keyfile json *", [validators.DataRequired(message="The gcloud keyfile json is required.")], render_kw={"rows": 20}, ) +class GcpFormUpdate(FlaskForm): + squad = StringField( + "Squad", + [ + validators.length(min=4, max=50, message="Squad out of reange."), + validators.DataRequired(message="Squad Name requerid."), + ], + ) + environment = StringField( + "Environment", + [ + validators.length(min=2, max=250, message="Branch out of reange."), + validators.DataRequired(message="Environment requerid."), + ], + ) + gcloud_keyfile_json = TextAreaField( + "gcloud keyfile json", + render_kw={"rows": 20}, + ) + + class AzureForm(FlaskForm): squad = StringField( "Squad", @@ -314,6 +334,45 @@ class AzureForm(FlaskForm): ) +class AzureFormUpdate(FlaskForm): + squad = StringField( + "Squad", + [ + validators.length(min=4, max=50, message="Squad out of reange."), + ], + ) + environment = StringField( + "Environment", + [ + validators.length(min=2, max=250, message="Branch out of reange."), + ], + ) + subscription_id = StringField( + "Subscription id", + [ + validators.length(min=4, max=50, message="Subscription id out of reange."), + ], + ) + client_id = StringField( + "Client id", + [ + validators.length(min=4, max=50, message="Client_id out of reange."), + ], + ) + client_secret = StringField( + "Client secret", + [ + validators.length(min=4, max=50, message="Client secret out of reange."), + ], + ) + tenant_id = StringField( + "tenant_id", + [ + validators.length(min=4, max=50, message="Tenant id out of reange."), + ], + ) + + class CustomProviderForm(FlaskForm): squad = StringField( "Squad", diff --git a/sld-dashboard/app/home/routes.py b/sld-dashboard/app/home/routes.py index e51e492b..6426aa54 100644 --- a/sld-dashboard/app/home/routes.py +++ b/sld-dashboard/app/home/routes.py @@ -19,8 +19,8 @@ from app.helpers.converter import convert_to_dict from app.helpers.security import vault_decrypt from app.home import blueprint -from app.home.forms import (AwsForm, AzureForm, DeployForm, GcpForm, StackForm, - UserForm, CustomProviderForm) +from app.home.forms import (AwsForm, AzureForm, AzureFormUpdate, DeployForm, GcpForm, GcpFormUpdate, + StackForm, UserForm, CustomProviderForm) from flask import flash, redirect, render_template, request, url_for from flask_login import current_user, login_required from jinja2 import TemplateNotFound @@ -464,7 +464,7 @@ def edit_deploy(deploy_id): "project_path": form.project_path.data.replace(" ",""), "variables": ast.literal_eval(variables), } - if not "deploy" in request.form.get("button"): + if "deploy" not in request.form.get("button"): endpoint = f"plan/{deploy_id}" # Deploy response = request_url( @@ -474,7 +474,7 @@ def edit_deploy(deploy_id): json=data, ) if response.get("status_code") == 202: - flash(f"Updating deploy") + flash("Updating deploy") else: flash(response.get("json").get("detail"), "error") return redirect( @@ -687,28 +687,28 @@ def clone_deploy(deploy_id): form = DeployForm(request.form) aws_response = request_url( verb="GET", - uri=f"accounts/aws/", + uri="accounts/aws/", headers={"Authorization": f"Bearer {token}"}, ) aws_content = aws_response.get("json") # Get data from gcp accounts gcp_response = request_url( verb="GET", - uri=f"accounts/gcp/", + uri="accounts/gcp/", headers={"Authorization": f"Bearer {token}"}, ) gcp_content = gcp_response.get("json") # Get data from azure accounts azure_response = request_url( verb="GET", - uri=f"accounts/azure/", + uri="accounts/azure/", headers={"Authorization": f"Bearer {token}"}, ) azure_content = azure_response.get("json") # Get data from custom providers accounts custom_response = request_url( verb="GET", - uri=f"accounts/custom_providers/", + uri="accounts/custom_providers/", headers={"Authorization": f"Bearer {token}"}, ) custom_content = custom_response.get("json") @@ -769,8 +769,8 @@ def clone_deploy(deploy_id): "project_path": form.project_path.data.replace(" ",""), "variables": ast.literal_eval(variables), } - if not "deploy" in request.form.get("button"): - endpoint = f"plan/" + if "deploy" not in request.form.get("button"): + endpoint = "plan/" # Deploy response = request_url( verb="POST", @@ -779,7 +779,7 @@ def clone_deploy(deploy_id): json=data, ) if response.get("status_code") == 202: - flash(f"Updating deploy") + flash("Updating deploy") else: flash(response.get("json").get("detail"), "error") return redirect( @@ -836,13 +836,13 @@ def edit_schedule(deploy_id): # Deploy response = request_url( verb="PATCH", - uri=f"{endpoint}", + uri="{endpoint}", headers={"Authorization": f"Bearer {token}"}, json=data, ) if response.get("status_code") == 202: - flash(f"Updating schedule") + flash("Updating schedule") else: flash(response.get("json").get("detail"), "error") return redirect( @@ -876,13 +876,13 @@ def new_stack(): squad_acces_form_to_list = squad_acces_form.split(",") if request.method == "POST": new_stack: dict = { - "stack_name": form.name.data.replace(" ",""), - "git_repo": form.git.data.replace(" ",""), - "branch": form.branch.data.replace(" ",""), + "stack_name": form.name.data.replace(" ", ""), + "git_repo": form.git.data.replace(" ", ""), + "branch": form.branch.data.replace(" ", ""), "squad_access": squad_acces_form_to_list, "iac_type": form.iac_type.data, - "tf_version": form.tf_version.data.replace(" ",""), - "project_path": form.project_path.data.replace(" ",""), + "tf_version": form.tf_version.data.replace(" ", ""), + "project_path": form.project_path.data.replace(" ", ""), "description": form.description.data, "icon_path": request.form.get("icon_path"), } @@ -927,13 +927,13 @@ def edit_stack(stack_id): squad_acces_form = form.squad_access_edit.data squad_acces_form_to_list = squad_acces_form.split(",") update_stack = { - "stack_name": form.name.data.replace(" ",""), - "git_repo": form.git.data.replace(" ",""), - "branch": form.branch.data.replace(" ",""), + "stack_name": form.name.data.replace(" ", ""), + "git_repo": form.git.data.replace(" ", ""), + "branch": form.branch.data.replace(" ", ""), "squad_access": squad_acces_form_to_list, "iac_type": form.iac_type.data, - "tf_version": form.tf_version.data.replace(" ",""), - "project_path": form.project_path.data.replace(" ",""), + "tf_version": form.tf_version.data.replace(" ", ""), + "project_path": form.project_path.data.replace(" ", ""), "description": form.description.data, "icon_path": request.form.get("icon_path"), } @@ -969,11 +969,9 @@ def details_stack(stack_id): try: icons = list_icons() token = decrypt(r.get(current_user.id)) - # Check if token no expired check_unauthorized_token(token) form = StackForm(request.form) endpoint = f"stacks/{stack_id}" - # Get deploy data vars and set var for render response = request_url( verb="GET", uri=f"{endpoint}", headers={"Authorization": f"Bearer {token}"} ) @@ -982,23 +980,21 @@ def details_stack(stack_id): response = requests.get(readme_url) if response.status_code == 200: readme_content = response.text - readme_html = mistletoe.markdown(readme_content) # Convierte el contenido de Markdown a HTML + readme_html = mistletoe.markdown(readme_content) else: readme_html = "

    Error loading the README.md file, check that it exists in the repository and on the selected branch

    " - # When user push data with POST verb if request.method == "POST": - # Data dend to deploy squad_acces_form = form.squad_access_edit.data squad_acces_form_to_list = squad_acces_form.split(",") update_stack = { - "stack_name": form.name.data.replace(" ",""), - "git_repo": form.git.data.replace(" ",""), - "branch": form.branch.data.replace(" ",""), + "stack_name": form.name.data.replace(" ", ""), + "git_repo": form.git.data.replace(" ", ""), + "branch": form.branch.data.replace(" ", ""), "squad_access": squad_acces_form_to_list, "iac_type": form.iac_type.data, - "tf_version": form.tf_version.data.replace(" ",""), - "project_path": form.project_path.data.replace(" ",""), + "tf_version": form.tf_version.data.replace(" ", ""), + "project_path": form.project_path.data.replace(" ", ""), "description": form.description.data, "icon_path": request.form.get("icon_path"), } @@ -1042,7 +1038,6 @@ def delete_stack(view_mode, stack_name): else: flash(response["json"]["detail"], "error") - # Redirección basada en el parámetro 'view_mode' if view_mode == "table": return redirect(url_for("home_blueprint.route_template", template="stacks-list")) elif view_mode == "cards": @@ -1061,10 +1056,8 @@ def delete_stack(view_mode, stack_name): def resync_stack(view_mode, stack_id): try: token = decrypt(r.get(current_user.id)) - # Check if token not expired check_unauthorized_token(token) endpoint = f"stacks/{stack_id}" - # Get deploy data vars and set var for render response = request_url( verb="GET", uri=f"{endpoint}", headers={"Authorization": f"Bearer {token}"} ) @@ -1094,7 +1087,6 @@ def resync_stack(view_mode, stack_id): else: flash(response.get("json").get("detail"), "error") - # Redirección basada en el parámetro 'view_mode' if view_mode == "table": return redirect(url_for("home_blueprint.route_template", template="stacks-list")) elif view_mode == "cards": @@ -1113,54 +1105,45 @@ def resync_stack(view_mode, stack_id): def deploy_stack(stack_id): try: token = decrypt(r.get(current_user.id)) - # Check if token no expired check_unauthorized_token(token) form = DeployForm(request.form) - # Get data from aws accounts aws_response = request_url( verb="GET", - uri=f"accounts/aws/", + uri="accounts/aws/", headers={"Authorization": f"Bearer {token}"}, ) aws_content = aws_response.get("json") - # Get data from gcp accounts gcp_response = request_url( verb="GET", - uri=f"accounts/gcp/", + uri="accounts/gcp/", headers={"Authorization": f"Bearer {token}"}, ) gcp_content = gcp_response.get("json") - # Get data from azure accounts azure_response = request_url( verb="GET", - uri=f"accounts/azure/", + uri="accounts/azure/", headers={"Authorization": f"Bearer {token}"}, ) azure_content = azure_response.get("json") - # Get data from custom providers accounts custom_response = request_url( verb="GET", - uri=f"accounts/custom_providers/", + uri="accounts/custom_providers/", headers={"Authorization": f"Bearer {token}"}, ) custom_content = custom_response.get("json") - # Get data from stack stack = request_url( verb="GET", uri=f"stacks/{stack_id}", headers={"Authorization": f"Bearer {token}"}, ) stack_name = stack["json"]["stack_name"] - # Get vars from stack data_json = request_url( verb="GET", uri=f"variables/json?stack={stack_name}", headers={"Authorization": f"Bearer {token}"}, ) vars_json = data_json["json"] - # Push data vars to api for deploy if request.method == "POST": - # Define list for exclude vars in variables data form_vars = [ "csrf_token", "environment", @@ -1182,22 +1165,21 @@ def deploy_stack(stack_id): } variables = ast.literal_eval(json.dumps(convert_to_dict(data_raw))) data = { - "name": form.deploy_name.data.replace(" ",""), + "name": form.deploy_name.data.replace(" ", ""), "stack_name": stack["json"]["stack_name"], "start_time": form.start_time.data, "destroy_time": form.destroy_time.data, "squad": request.form.get("squad"), "environment": request.form.get("environment"), - "stack_branch": request.form.get("branch").replace(" ",""), - "tfvar_file": request.form.get("tfvar_file").replace(" ",""), - "project_path": request.form.get("project_path").replace(" ",""), + "stack_branch": request.form.get("branch").replace(" ", ""), + "tfvar_file": request.form.get("tfvar_file").replace(" ", ""), + "project_path": request.form.get("project_path").replace(" ", ""), "variables": variables, } - endpoint = f"plan" - if not "plan" in request.form.get("button"): - endpoint = f"deploy" + endpoint = "plan" + if "plan" not in request.form.get("button"): + endpoint = "deploy" - # Deploy response = request_url( verb="POST", uri=f"{endpoint}", @@ -1232,7 +1214,6 @@ def deploy_stack(stack_id): def get_task(task_id): try: token = decrypt(r.get(current_user.id)) - # Check if token no expired check_unauthorized_token(token) response = request_url( verb="GET", @@ -1261,11 +1242,10 @@ def get_task(task_id): def list_tasks(limit): try: token = decrypt(r.get(current_user.id)) - # Check if token no expired check_unauthorized_token(token) endpoint = f"tasks/all?limit={limit}" if limit == 0: - endpoint = f"tasks/all" + endpoint = "tasks/all" response = request_url( verb="GET", uri=f"{endpoint}", headers={"Authorization": f"Bearer {token}"} ) @@ -1286,7 +1266,6 @@ def list_tasks(limit): def list_task(task_id): try: token = decrypt(r.get(current_user.id)) - # Check if token no expired check_unauthorized_token(token) endpoint = f"tasks/id/{task_id}" response = request_url( @@ -1305,11 +1284,10 @@ def list_task(task_id): def list_activity(limit): try: token = decrypt(r.get(current_user.id)) - # Check if token no expired check_unauthorized_token(token) - endpoint = f"activity/all?limit={limit}" + endpoint = "activity/all?limit={limit}" if limit == 0: - endpoint = f"activity/all" + endpoint = "activity/all" response = request_url( verb="GET", uri=f"{endpoint}", headers={"Authorization": f"Bearer {token}"} ) @@ -1331,19 +1309,18 @@ def new_user(): try: form = UserForm(request.form) token = decrypt(r.get(current_user.id)) - # Check if token no expired check_unauthorized_token(token) if request.method == "POST": new_user: dict = { - "username": form.username.data.replace(" ",""), + "username": form.username.data.replace(" ", ""), "fullname": form.fullname.data, "password": form.password.data, - "email": form.email.data.replace(" ",""), + "email": form.email.data.replace(" ", ""), "squad": form.squad.data.split(","), "role": request.form.get("role").split(","), "is_active": form.is_active.data, } - endpoint = f"users/" + endpoint = "users/" response = request_url( verb="POST", uri=f"{endpoint}", @@ -1372,11 +1349,10 @@ def new_user(): def list_users(limit): try: token = decrypt(r.get(current_user.id)) - # Check if token no expired check_unauthorized_token(token) endpoint = f"users/?limit={limit}" if limit == 0: - endpoint = f"users/" + endpoint = "users/" response = request_url( verb="GET", uri=f"{endpoint}", headers={"Authorization": f"Bearer {token}"} ) @@ -1396,10 +1372,9 @@ def list_users(limit): def delete_user(user_name): try: token = decrypt(r.get(current_user.id)) - # Check if token no expired check_unauthorized_token(token) endpoint = f"users/{user_name}" - response = request_url( + request_url( verb="DELETE", uri=f"{endpoint}", headers={"Authorization": f"Bearer {token}"}, @@ -1416,9 +1391,7 @@ def edit_user(user_id): try: form = UserForm(request.form) token = decrypt(r.get(current_user.id)) - # Check if token no expired check_unauthorized_token(token) - # Get user info by id endpoint = f"users/{user_id}" response = request_url( verb="GET", uri=f"{endpoint}", headers={"Authorization": f"Bearer {token}"} @@ -1435,7 +1408,6 @@ def edit_user(user_id): "role": request.form.get("role").split(","), "is_active": request.form.get("is_active"), } - # Apply user change endpoint = f"users/{user_id}" response = request_url( verb="PATCH", @@ -1463,7 +1435,6 @@ def setting_user(): try: form = UserForm(request.form) token = decrypt(r.get(current_user.id)) - # Check if token no expired check_unauthorized_token(token) if request.method == "POST": user_data: dict = { @@ -1471,12 +1442,12 @@ def setting_user(): } response = request_url( verb="PATCH", - uri=f"users/reset/", + uri="users/reset/", headers={"Authorization": f"Bearer {token}"}, json=user_data, ) if response.get("status_code") == 200: - flash(f"Password Updated ") + flash("Password Updated ") else: flash(response.get("json").get("detail"), "error") return redirect( @@ -1503,18 +1474,17 @@ def new_aws_account(): try: form = AwsForm(request.form) token = decrypt(r.get(current_user.id)) - # Check if token no expired check_unauthorized_token(token) if request.method == "POST": key_list = request.values.getlist("sld_key") value_list = request.values.getlist("sld_value") aws_account_request: dict = { - "squad": form.squad.data.replace(" ",""), - "environment": form.environment.data.replace(" ",""), - "access_key_id": form.access_key_id.data.replace(" ",""), - "secret_access_key": form.secret_access_key.data.replace(" ",""), - "default_region": form.default_region.data.replace(" ",""), - "role_arn": form.role_arn.data.replace(" ",""), + "squad": form.squad.data.replace(" ", ""), + "environment": form.environment.data.replace(" ", ""), + "access_key_id": form.access_key_id.data.replace(" ", ""), + "secret_access_key": form.secret_access_key.data.replace(" ", ""), + "default_region": form.default_region.data.replace(" ", ""), + "role_arn": form.role_arn.data.replace(" ", ""), "extra_variables": dict(list(zip(key_list, value_list))) } @@ -1551,7 +1521,6 @@ def edit_aws_account(account_id): try: form = AwsForm(request.form) token = decrypt(r.get(current_user.id)) - # Check if token no expired check_unauthorized_token(token) endpoint = f"accounts/aws/?id={account_id}" response = request_url( @@ -1562,12 +1531,12 @@ def edit_aws_account(account_id): key_list = request.values.getlist("sld_key") value_list = request.values.getlist("sld_value") aws_account_request: dict = { - "squad": form.squad.data.replace(" ",""), - "environment": form.environment.data.replace(" ",""), - "access_key_id": form.access_key_id.data.replace(" ","") if "*" not in form.access_key_id.data else None, - "secret_access_key": form.secret_access_key.data.replace(" ","") if "*" not in form.secret_access_key.data else None, - "default_region": form.default_region.data.replace(" ",""), - "role_arn": form.role_arn.data.replace(" ",""), + "squad": form.squad.data.replace(" ", ""), + "environment": form.environment.data.replace(" ", ""), + "access_key_id": form.access_key_id.data.replace(" ", "") if "*" not in form.access_key_id.data else None, + "secret_access_key": form.secret_access_key.data.replace(" ", "") if "*" not in form.secret_access_key.data else None, + "default_region": form.default_region.data.replace(" ", ""), + "role_arn": form.role_arn.data.replace(" ", ""), "extra_variables": dict(list(zip(key_list, value_list))), } response = request_url( @@ -1607,7 +1576,7 @@ def list_aws_account(): check_unauthorized_token(token) response = request_url( verb="GET", - uri=f"accounts/aws/", + uri="accounts/aws/", headers={"Authorization": f"Bearer {token}"}, ) content = response.get("json") @@ -1655,16 +1624,19 @@ def new_gcp_account(): # Check if token no expired check_unauthorized_token(token) if request.method == "POST": - new_user: dict = { - "squad": form.squad.data.replace(" ",""), - "environment": form.environment.data.replace(" ",""), + key_list = request.values.getlist("sld_key") + value_list = request.values.getlist("sld_value") + new_account: dict = { + "squad": form.squad.data.replace(" ", ""), + "environment": form.environment.data.replace(" ", ""), "gcloud_keyfile_json": ast.literal_eval(form.gcloud_keyfile_json.data), + "extra_variables": dict(list(zip(key_list, value_list))), } response = request_url( verb="POST", uri="accounts/gcp/", headers={"Authorization": f"Bearer {token}"}, - json=new_user, + json=new_account, ) if response.get("status_code") == 200: flash( @@ -1686,6 +1658,57 @@ def new_gcp_account(): return redirect(url_for("base_blueprint.logout")) +@blueprint.route("/gcp-edit", methods=["GET", "POST"], defaults={"account_id": None}) +@blueprint.route("/gcp-edit/", methods=["GET", "POST"]) +@login_required +def edit_gcp_account(account_id): + try: + form = GcpFormUpdate(request.form) + token = decrypt(r.get(current_user.id)) + # Check if token no expired + check_unauthorized_token(token) + endpoint = f"accounts/gcp/?id={account_id}" + response = request_url( + verb="GET", uri=f"{endpoint}", headers={"Authorization": f"Bearer {token}"} + ) + vars_json = response["json"][0] + if request.method == "POST": + key_list = request.values.getlist("sld_key") + value_list = request.values.getlist("sld_value") + aws_account_request: dict = { + "squad": form.squad.data.replace(" ", ""), + "environment": form.environment.data.replace(" ", ""), + "gcloud_keyfile_json": ast.literal_eval(form.gcloud_keyfile_json.data) if form.gcloud_keyfile_json.data != "" else None, + "extra_variables": dict(list(zip(key_list, value_list))), + } + response = request_url( + verb="PATCH", + uri=f"accounts/gcp/{account_id}", + headers={"Authorization": f"Bearer {token}"}, + json=aws_account_request, + ) + if response.get("status_code") == 200: + flash( + f"Updated gcp account for environment {form.environment.data} in {form.squad.data} " + ) + return redirect(url_for("home_blueprint.route_template", template="gcp-list")) + + elif response.get("status_code") == 409: + flash(response["json"].get("detail"), "error") + else: + flash(response["json"], "error") + + return render_template( + "/gcp-edit.html", + title="Edit gcp account", + form=form, + active="edit_gcp_account", + data_json=vars_json, + external_api_dns=external_api_dns, + ) + except ValueError: + return redirect(url_for("base_blueprint.logout")) + @blueprint.route("/gcp-list") @login_required def list_gcp_account(): @@ -1695,7 +1718,7 @@ def list_gcp_account(): check_unauthorized_token(token) response = request_url( verb="GET", - uri=f"accounts/gcp/", + uri="accounts/gcp/", headers={"Authorization": f"Bearer {token}"}, ) content = response.get("json") @@ -1711,7 +1734,6 @@ def list_gcp_account(): def delete_gcp_account(gcp_account_id): try: token = decrypt(r.get(current_user.id)) - # Check if token no expired check_unauthorized_token(token) response = request_url( verb="DELETE", @@ -1721,7 +1743,7 @@ def delete_gcp_account(gcp_account_id): if response.get("status_code") == 200: flash( - f"Account Deleted" + "Account Deleted" ) elif response.get("status_code") == 409: flash(response["json"].get("detail"), "error") @@ -1740,16 +1762,18 @@ def new_azure_account(): try: form = AzureForm(request.form) if request.method == "POST": + key_list = request.values.getlist("sld_key") + value_list = request.values.getlist("sld_value") token = decrypt(r.get(current_user.id)) - # Check if token no expired check_unauthorized_token(token) new_user: dict = { - "squad": form.squad.data.replace(" ",""), - "environment": form.environment.data.replace(" ",""), - "subscription_id": form.subscription_id.data.replace(" ",""), - "client_id": form.client_id.data.replace(" ",""), - "client_secret": form.client_secret.data.replace(" ",""), - "tenant_id": form.tenant_id.data.replace(" ",""), + "squad": form.squad.data.replace(" ", ""), + "environment": form.environment.data.replace(" ", ""), + "subscription_id": form.subscription_id.data.replace(" ", ""), + "client_id": form.client_id.data.replace(" ", ""), + "client_secret": form.client_secret.data.replace(" ", ""), + "tenant_id": form.tenant_id.data.replace(" ", ""), + "extra_variables": dict(list(zip(key_list, value_list))) } response = request_url( verb="POST", @@ -1777,16 +1801,69 @@ def new_azure_account(): return redirect(url_for("base_blueprint.logout")) +@blueprint.route("/azure-edit", methods=["GET", "POST"], defaults={"account_id": None}) +@blueprint.route("/azure-edit/", methods=["GET", "POST"]) +@login_required +def edit_azure_account(account_id): + try: + form = AzureFormUpdate(request.form) + token = decrypt(r.get(current_user.id)) + check_unauthorized_token(token) + endpoint = f"accounts/azure/?id={account_id}" + response = request_url( + verb="GET", uri=f"{endpoint}", headers={"Authorization": f"Bearer {token}"} + ) + vars_json = response["json"][0] + if request.method == "POST": + key_list = request.values.getlist("sld_key") + value_list = request.values.getlist("sld_value") + aws_account_request: dict = { + "squad": form.squad.data.replace(" ", ""), + "environment": form.environment.data.replace(" ", ""), + "subscription_id": form.subscription_id.data.replace(" ", ""), + "client_id": form.client_id.data.replace(" ", ""), + "client_secret": form.client_secret.data.replace(" ", ""), + "tenant_id": form.tenant_id.data.replace(" ", ""), + "extra_variables": dict(list(zip(key_list, value_list))), + } + response = request_url( + verb="PATCH", + uri=f"accounts/azure/{account_id}", + headers={"Authorization": f"Bearer {token}"}, + json=aws_account_request, + ) + if response.get("status_code") == 200: + flash( + f"Updated azure account for environment {form.environment.data} in {form.squad.data} " + ) + return redirect(url_for("home_blueprint.route_template", template="azure-list")) + + elif response.get("status_code") == 409: + flash(response["json"].get("detail"), "error") + else: + flash(response["json"], "error") + + return render_template( + "/azure-edit.html", + title="Edit azure account", + form=form, + active="edit_azure_account", + data_json=vars_json, + external_api_dns=external_api_dns, + ) + except ValueError: + return redirect(url_for("base_blueprint.logout")) + + @blueprint.route("/azure-list") @login_required def list_azure_account(): try: token = decrypt(r.get(current_user.id)) - # Check if token no expired check_unauthorized_token(token) response = request_url( verb="GET", - uri=f"accounts/azure/", + uri="accounts/azure/", headers={"Authorization": f"Bearer {token}"}, ) content = response.get("json") @@ -1805,23 +1882,20 @@ def list_azure_account(): def delete_azure_account(azure_account_id): try: token = decrypt(r.get(current_user.id)) - # Check if token no expired check_unauthorized_token(token) response = request_url( verb="DELETE", uri=f"accounts/azure/{azure_account_id}", headers={"Authorization": f"Bearer {token}"}, ) - if response.get("status_code") == 200: flash( - f"Account Deleted" + "Account Deleted" ) elif response.get("status_code") == 409: flash(response["json"].get("detail"), "error") else: flash(response["json"], "error") - return redirect(url_for("home_blueprint.route_template", template="azure-list")) except ValueError: return redirect(url_for("base_blueprint.logout")) @@ -1834,13 +1908,9 @@ def route_template(template): try: if not template.endswith(".html"): template += ".html" - # Detect the current page segment = get_segment(request) - # Get Token token = decrypt(r.get(current_user.id)) - # Check if token no expired check_unauthorized_token(token) - # Get Api data deploy_response = request_url( verb="GET", uri="deploy", diff --git a/sld-dashboard/app/home/templates/azure-edit.html b/sld-dashboard/app/home/templates/azure-edit.html new file mode 100644 index 00000000..b73e4433 --- /dev/null +++ b/sld-dashboard/app/home/templates/azure-edit.html @@ -0,0 +1,163 @@ +{% extends "layouts/base.html" %} + +{% from "helpers/_forms.html" import render_field %} + +{% block title %} New AZURE Account {% endblock %} + + +{% block stylesheets %}{% endblock stylesheets %} +{% block content %} +
    + + {% include 'includes/navigation.html' %} +
    + Volt logo +
    + +
    + +
    +
    +

    Edit AZURE Account

    +

    Edit account by squad and environment

    +
    + +
    +
    + +
    +
    +
    +
    +
    +
    + +
    +
    +
    + {{ render_field(form.squad, class='form-control', value=data_json.squad) }} +
    + +
    + {{ render_field(form.environment, class='form-control', value=data_json.environment) }} +
    + +
    + {{ render_field(form.tenant_id, class='form-control', value=data_json.tenant_id) }} +
    + +
    + {{ render_field(form.subscription_id, class='form-control', value=data_json.subscription_id) }} +
    + +
    + {{ render_field(form.client_id, class='form-control', placeholder='client_id') + }} +
    + +
    + {{ render_field(form.client_secret, class='form-control', + placeholder='client_secret') }} +
    + + +
    +
    +
    +
    + + {% if data_json.extra_variables %} + {% for key, value in data_json.extra_variables.items() %} +
    +
    + +
    +
    + +
    +
    + +
    +
    + {% endfor %} + {% endif %} +
    + +
    + +
    +
    +
    + + + + +
    + + +
    +
    +
    +
    +
    + + {% include 'includes/footer.html' %} + +
    + +{% endblock content %} + + +{% block javascripts %} + + + + +{% endblock javascripts %} \ No newline at end of file diff --git a/sld-dashboard/app/home/templates/azure-list.html b/sld-dashboard/app/home/templates/azure-list.html index bb959cb5..6a3fbb9f 100644 --- a/sld-dashboard/app/home/templates/azure-list.html +++ b/sld-dashboard/app/home/templates/azure-list.html @@ -72,6 +72,7 @@

    All azure accounts

    Environment Subscription id Tenant id + Extra Variables @@ -86,6 +87,16 @@

    All azure accounts

    {{ azure_account.environment }} {{ azure_account.subscription_id }} {{ azure_account.tenant_id }} + + + {% if azure_account.extra_variables %} + {% set truncated_variables = azure_account.extra_variables.keys() | join(', ') | truncate(30, True, '...') %} + {{ truncated_variables }} + {% else %} + - + {% endif %} + + {% if "yoda" in current_user.role %}
    @@ -97,6 +108,9 @@

    All azure accounts

    Toggle Dropdown + +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + + @@ -93,4 +111,37 @@

    Add New AZURE Account

    {% endblock content %} -{% block javascripts %}{% endblock javascripts %} \ No newline at end of file +{% block javascripts %} + + +{% endblock javascripts %} \ No newline at end of file diff --git a/sld-dashboard/app/home/templates/gcp-edit.html b/sld-dashboard/app/home/templates/gcp-edit.html new file mode 100644 index 00000000..fcb07944 --- /dev/null +++ b/sld-dashboard/app/home/templates/gcp-edit.html @@ -0,0 +1,149 @@ +{% extends "layouts/base.html" %} + +{% from "helpers/_forms.html" import render_field %} + +{% block title %} New GCP Account {% endblock %} + + +{% block stylesheets %}{% endblock stylesheets %} +{% block content %} +
    + + {% include 'includes/navigation.html' %} +
    + Volt logo +
    + +
    + +
    +
    +

    Edit GCP Account

    +

    Edit account by squad and environment

    +
    + +
    +
    + +
    +
    +
    +
    +
    +
    + +
    +
    +
    + {{ render_field(form.squad, class='form-control', value=data_json.squad) }} +
    + +
    + {{ render_field(form.environment, class='form-control', value=data_json.environment) }} +
    + +
    + {{ render_field(form.gcloud_keyfile_json, class='form-control',value="{}", placeholder='Paste gcp json key') }} +
    + + +
    +
    +
    +
    + + {% if data_json.extra_variables %} + {% for key, value in data_json.extra_variables.items() %} +
    +
    + +
    +
    + +
    +
    + +
    +
    + {% endfor %} + {% endif %} +
    + +
    + +
    +
    +
    + + + + +
    + + +
    +
    +
    +
    +
    + + {% include 'includes/footer.html' %} + +
    + +{% endblock content %} + + +{% block javascripts %} + + + + +{% endblock javascripts %} \ No newline at end of file diff --git a/sld-dashboard/app/home/templates/gcp-list.html b/sld-dashboard/app/home/templates/gcp-list.html index 11a5345c..11972789 100644 --- a/sld-dashboard/app/home/templates/gcp-list.html +++ b/sld-dashboard/app/home/templates/gcp-list.html @@ -70,6 +70,7 @@

    All gcp accounts

    Account Name Squad Environment + Extra Variables @@ -82,6 +83,16 @@

    All gcp accounts

    {{ gcp_account.squad }}_{{ gcp_account.environment}} {{ gcp_account.squad }} {{ gcp_account.environment }} + + + {% if gcp_account.extra_variables %} + {% set truncated_variables = gcp_account.extra_variables.keys() | join(', ') | truncate(30, True, '...') %} + {{ truncated_variables }} + {% else %} + - + {% endif %} + + {% if "yoda" in current_user.role %}
    @@ -93,6 +104,9 @@

    All gcp accounts

    Toggle Dropdown
    -
    - - +
    +
    + diff --git a/sld-dashboard/app/home/templates/gcp-list.html b/sld-dashboard/app/home/templates/gcp-list.html index 11972789..3e9d8787 100644 --- a/sld-dashboard/app/home/templates/gcp-list.html +++ b/sld-dashboard/app/home/templates/gcp-list.html @@ -157,4 +157,5 @@

    All gcp accounts

    {% block javascripts %} + {% endblock javascripts %} \ No newline at end of file diff --git a/sld-dashboard/app/home/templates/stacks-cards.html b/sld-dashboard/app/home/templates/stacks-cards.html index 65f7360c..412b89cb 100644 --- a/sld-dashboard/app/home/templates/stacks-cards.html +++ b/sld-dashboard/app/home/templates/stacks-cards.html @@ -215,6 +215,40 @@
    Id Stack