Skip to content

Commit

Permalink
add default values to the user inputs of credentials
Browse files Browse the repository at this point in the history
  • Loading branch information
hsong-rh committed Jan 13, 2025
1 parent fe84a06 commit 35d6e68
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 29 deletions.
4 changes: 4 additions & 0 deletions src/aap_eda/core/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,7 @@ class DuplicateFileTemplateKeyError(Exception):

class DuplicateEnvKeyError(Exception):
pass


class InvalidEnvKeyError(Exception):
pass
1 change: 1 addition & 0 deletions src/aap_eda/core/utils/credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

if typing.TYPE_CHECKING:
from aap_eda.core import models

from aap_eda.core.utils.awx import validate_ssh_private_key

ENCRYPTED_STRING = "$encrypted$"
Expand Down
77 changes: 48 additions & 29 deletions src/aap_eda/wsapi/consumers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@
from django.conf import settings
from django.db import DatabaseError
from django.utils import timezone
from jinja2 import StrictUndefined

from aap_eda.api.vault import encrypt_string
from aap_eda.core import models
from aap_eda.core.enums import DefaultCredentialType
from aap_eda.core.exceptions import (
DuplicateEnvKeyError,
DuplicateFileTemplateKeyError,
InvalidEnvKeyError,
)
from aap_eda.core.models.activation import ActivationStatus
from aap_eda.core.utils.credentials import get_secret_fields
Expand Down Expand Up @@ -118,6 +120,8 @@ async def receive(self, text_data=None, bytes_data=None):
logger.warning(f"Unsupported message received: {data}")
except DatabaseError as err:
logger.error(f"Failed to parse {data} due to DB error: {err}")
except InvalidEnvKeyError as err:
logger.error(f"Failed to parse {data} due to Env error: {err}")

async def handle_workers(self, message: WorkerMessage):
logger.info(f"Start to handle workers: {message}")
Expand Down Expand Up @@ -531,39 +535,39 @@ def get_file_contents_from_credentials(
def get_env_vars_from_credentials(
self, activation: models.Activation
) -> tp.Optional[str]:
vault_password, vault_id = self.get_vault_password_and_id(activation)
env_vars = {}

for eda_credential in activation.eda_credentials.all():
schema_inputs = eda_credential.credential_type.inputs
secret_fields = get_secret_fields(schema_inputs)
injectors = eda_credential.credential_type.injectors
user_inputs = yaml.safe_load(
eda_credential.inputs.get_secret_value()
try:
vault_password, vault_id = self.get_vault_password_and_id(
activation
)
if "env" not in injectors:
continue

if secret_fields:
self.encrypt_user_inputs(
secret_fields=secret_fields,
user_inputs=user_inputs,
password=vault_password,
vault_id=vault_id,
)
env_vars = {}

for eda_credential in activation.eda_credentials.all():
injectors = eda_credential.credential_type.injectors
if "env" not in injectors:
continue

for key, value in injectors["env"].items():
if key in env_vars:
raise DuplicateEnvKeyError(f"env {key} already exists")
env_vars[key] = (
value
if not isinstance(value, str) or "eda.filename" in value
else substitute_variables(value, user_inputs)
schema_inputs = eda_credential.credential_type.inputs
secret_fields = get_secret_fields(schema_inputs)
user_inputs = yaml.safe_load(
eda_credential.inputs.get_secret_value()
)

if not env_vars:
return None
return yaml.dump(env_vars)
if secret_fields:
self.encrypt_user_inputs(
secret_fields=secret_fields,
user_inputs=user_inputs,
password=vault_password,
vault_id=vault_id,
)

self.substitute_envs(env_vars, injectors, user_inputs)

if not env_vars:
return None

return yaml.dump(env_vars)
except TypeError as err:
raise InvalidEnvKeyError(str(err)) from err

@staticmethod
def encrypt_user_inputs(
Expand All @@ -580,6 +584,21 @@ def encrypt_user_inputs(
vault_id=vault_id,
)

@staticmethod
def substitute_envs(
envs: dict, injectors: dict, user_inputs: dict
) -> None:
for key, value in injectors["env"].items():
if key in envs:
raise DuplicateEnvKeyError(f"env {key} already exists")

if not isinstance(value, str) or "eda.filename" in value:
envs[key] = value
else:
value = substitute_variables(value, user_inputs)
if not isinstance(value, StrictUndefined):
envs[key] = value

@staticmethod
def get_vault_password_and_id(
activation: models.Activation,
Expand Down
64 changes: 64 additions & 0 deletions tests/integration/wsapi/test_consumer.py
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,7 @@ def get_job_instance_event_count():
def _prepare_activation_instance_with_credentials(
default_organization: models.Organization,
credentials: list[models.EdaCredential],
system_vault_credential: models.EdaCredential = None,
):
project, _ = models.Project.objects.get_or_create(
name="test-project",
Expand Down Expand Up @@ -774,6 +775,7 @@ def _prepare_activation_instance_with_credentials(
project=project,
user=user,
decision_environment=decision_environment,
eda_system_vault_credential=system_vault_credential,
organization=default_organization,
)
activation.eda_credentials.add(*credentials)
Expand Down Expand Up @@ -1237,6 +1239,41 @@ async def test_handle_workers_with_file_contents(
assert response[key] == value


@pytest.mark.django_db(transaction=True)
async def test_handle_workers_with_env_vars(
ws_communicator: WebsocketCommunicator,
preseed_credential_types,
default_organization: models.Organization,
):
eda_credential = await _prepare_aap_credential_async(default_organization)
system_credential = await _prepare_system_vault_credential_async(
default_organization
)
rulebook_process_id = await _prepare_activation_instance_with_credentials(
default_organization,
[eda_credential],
system_credential,
)

payload = {
"type": "Worker",
"activation_id": rulebook_process_id,
}
await ws_communicator.send_json_to(payload)

for type in [
"Rulebook",
"ControllerInfo",
"VaultCollection",
"EnvVars",
"EndOfResponse",
]:
response = await ws_communicator.receive_json_from(timeout=TIMEOUT)
assert response["type"] == type
if type == "EnvVars":
assert response["data"].startswith("Q09OVFJPTExFUl9IT1NUOiBodHRwc")


@database_sync_to_async
def _prepare_credential(
credential_type_inputs: dict,
Expand All @@ -1259,6 +1296,33 @@ def _prepare_credential(
)


@database_sync_to_async
def _prepare_aap_credential_async(
organization: models.Organization,
):
return _prepare_aap_credential(organization)


def _prepare_aap_credential(
organization: models.Organization,
) -> models.EdaCredential:
aap_credential_type = models.CredentialType.objects.get(
name=enums.DefaultCredentialType.AAP
)

data = "secret"
return models.EdaCredential.objects.create(
name="eda_aap_credential",
inputs={
"host": "https://controller_url/",
"username": "adam",
"password": data,
},
credential_type=aap_credential_type,
organization=organization,
)


@database_sync_to_async
def _prepare_system_vault_credential_async(
organization: models.Organization,
Expand Down

0 comments on commit 35d6e68

Please sign in to comment.