diff --git a/docs/source/module/utils/types.rst b/docs/source/module/utils/types.rst new file mode 100644 index 0000000000000000000000000000000000000000..c70c8dd0c61a4fa29cc2f123ec4d0643ab7bdf5d --- /dev/null +++ b/docs/source/module/utils/types.rst @@ -0,0 +1,6 @@ +``gso.utils.types`` +=================== + +.. automodule:: gso.utils.types + :members: + :show-inheritance: diff --git a/gso/utils/types.py b/gso/utils/types.py new file mode 100644 index 0000000000000000000000000000000000000000..3e1b4091b127d9a572c12c7fad462dc4887de9f7 --- /dev/null +++ b/gso/utils/types.py @@ -0,0 +1,9 @@ +"""Define custom types for use across the application.""" + +from typing import Annotated + +from pydantic import AfterValidator + +from gso.utils.helpers import validate_tt_number + +TTNumber = Annotated[str, AfterValidator(validate_tt_number)] diff --git a/gso/workflows/iptrunk/create_iptrunk.py b/gso/workflows/iptrunk/create_iptrunk.py index 373dfc03b63e378b2889c62aca3496011121f909..90a966efacf6d71a4a0e849c329a40b4c8ad5ee2 100644 --- a/gso/workflows/iptrunk/create_iptrunk.py +++ b/gso/workflows/iptrunk/create_iptrunk.py @@ -40,9 +40,9 @@ from gso.utils.helpers import ( validate_interface_name_list, validate_iptrunk_unique_interface, validate_router_in_netbox, - validate_tt_number, ) from gso.utils.shared_enums import Vendor +from gso.utils.types import TTNumber from gso.utils.workflow_steps import prompt_sharepoint_checklist_url @@ -58,7 +58,7 @@ def initial_input_form_generator(product_name: str) -> FormGenerator: class CreateIptrunkForm(FormPage): model_config = ConfigDict(title=product_name) - tt_number: str + tt_number: TTNumber partner: ReadOnlyField("GEANT", default_type=str) # type: ignore[valid-type] geant_s_sid: str | None = None iptrunk_description: str | None = None @@ -66,10 +66,6 @@ def initial_input_form_generator(product_name: str) -> FormGenerator: iptrunk_speed: PhysicalPortCapacity iptrunk_number_of_members: int - @field_validator("tt_number") - def validate_tt_number(cls, tt_number: str) -> str: - return validate_tt_number(tt_number) - initial_user_input = yield CreateIptrunkForm recommended_minimum_links = calculate_recommended_minimum_links( initial_user_input.iptrunk_number_of_members, initial_user_input.iptrunk_speed diff --git a/gso/workflows/iptrunk/deploy_twamp.py b/gso/workflows/iptrunk/deploy_twamp.py index 92e37fd5777105be180674296194ea24ae70cf8f..a45b5eca61144577c5dbf58a251b0b1ca7c76f6d 100644 --- a/gso/workflows/iptrunk/deploy_twamp.py +++ b/gso/workflows/iptrunk/deploy_twamp.py @@ -10,11 +10,10 @@ from orchestrator.utils.json import json_dumps from orchestrator.workflow import StepList, begin, done, step, workflow from orchestrator.workflows.steps import resync, store_process_subscription, unsync from orchestrator.workflows.utils import wrap_modify_initial_input_form -from pydantic import field_validator from gso.products.product_types.iptrunk import Iptrunk from gso.services.lso_client import execute_playbook, lso_interaction -from gso.utils.helpers import validate_tt_number +from gso.utils.types import TTNumber def _initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: @@ -26,11 +25,7 @@ def _initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: f"{trunk.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn} to " f"{trunk.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}" ) - tt_number: str - - @field_validator("tt_number") - def validate_tt_number(cls, tt_number: str) -> str: - return validate_tt_number(tt_number) + tt_number: TTNumber user_input = yield DeployTWAMPForm diff --git a/gso/workflows/iptrunk/migrate_iptrunk.py b/gso/workflows/iptrunk/migrate_iptrunk.py index 908fcdbc1879b64e284914d6347c2ffa05640364..be15848ae73f933fa262211e765b0901313dac66 100644 --- a/gso/workflows/iptrunk/migrate_iptrunk.py +++ b/gso/workflows/iptrunk/migrate_iptrunk.py @@ -38,9 +38,9 @@ from gso.utils.helpers import ( available_lags_choices, get_router_vendor, validate_interface_name_list, - validate_tt_number, ) from gso.utils.shared_enums import Vendor +from gso.utils.types import TTNumber from gso.utils.workflow_steps import set_isis_to_max @@ -65,16 +65,12 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: class IPTrunkMigrateForm(FormPage): model_config = ConfigDict(title=form_title) - tt_number: str + tt_number: TTNumber replace_side: replaced_side_enum # type: ignore[valid-type] warning_label: Label = "Are we moving to a different Site?" migrate_to_different_site: bool = False restore_isis_metric: bool = True - @field_validator("tt_number", mode="before") - def validate_tt_number(cls, tt_number: str) -> str: - return validate_tt_number(tt_number) - migrate_form_input = yield IPTrunkMigrateForm current_routers = [ diff --git a/gso/workflows/iptrunk/modify_isis_metric.py b/gso/workflows/iptrunk/modify_isis_metric.py index 2e4a4586430cf3698d900924737c352463ed3343..285907b45508249794bb8c5fd486b62ed0b4dac6 100644 --- a/gso/workflows/iptrunk/modify_isis_metric.py +++ b/gso/workflows/iptrunk/modify_isis_metric.py @@ -12,6 +12,7 @@ from orchestrator.workflows.utils import wrap_modify_initial_input_form from gso.products.product_types.iptrunk import Iptrunk from gso.services.lso_client import execute_playbook, lso_interaction +from gso.utils.types import TTNumber def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: @@ -19,7 +20,7 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: subscription = Iptrunk.from_subscription(subscription_id) class ModifyIptrunkForm(FormPage): - tt_number: str + tt_number: TTNumber isis_metric: int = subscription.iptrunk.iptrunk_isis_metric user_input = yield ModifyIptrunkForm diff --git a/gso/workflows/iptrunk/modify_trunk_interface.py b/gso/workflows/iptrunk/modify_trunk_interface.py index f5d17d7752503d8fdb8979d0eefadd7818af4936..394e369a88d1f64481750080410ffcb8f4335064 100644 --- a/gso/workflows/iptrunk/modify_trunk_interface.py +++ b/gso/workflows/iptrunk/modify_trunk_interface.py @@ -32,9 +32,9 @@ from gso.utils.helpers import ( get_router_vendor, validate_interface_name_list, validate_iptrunk_unique_interface, - validate_tt_number, ) from gso.utils.shared_enums import IPv4AddressType, IPv6AddressType, Vendor +from gso.utils.types import TTNumber from gso.workflows.iptrunk.migrate_iptrunk import check_ip_trunk_optical_levels_pre from gso.workflows.iptrunk.validate_iptrunk import check_ip_trunk_isis @@ -85,7 +85,7 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: subscription = Iptrunk.from_subscription(subscription_id) class ModifyIptrunkForm(FormPage): - tt_number: str + tt_number: TTNumber geant_s_sid: str | None = subscription.iptrunk.geant_s_sid iptrunk_description: str | None = subscription.iptrunk.iptrunk_description iptrunk_type: IptrunkType = subscription.iptrunk.iptrunk_type @@ -103,10 +103,6 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: str(subscription.iptrunk.iptrunk_ipv6_network), default_type=IPv6AddressType ) - @field_validator("tt_number") - def validate_tt_number(cls, tt_number: str) -> str: - return validate_tt_number(tt_number) - initial_user_input = yield ModifyIptrunkForm recommended_minimum_links = calculate_recommended_minimum_links( diff --git a/gso/workflows/iptrunk/terminate_iptrunk.py b/gso/workflows/iptrunk/terminate_iptrunk.py index bee9739a9732ea3db1415e8d7955886b9dcaa2bb..bb1a6fd90b3d9a5e3b9aa9bb633db58cc2eb1cd4 100644 --- a/gso/workflows/iptrunk/terminate_iptrunk.py +++ b/gso/workflows/iptrunk/terminate_iptrunk.py @@ -16,15 +16,15 @@ from orchestrator.workflows.steps import ( unsync, ) from orchestrator.workflows.utils import wrap_modify_initial_input_form -from pydantic import field_validator from gso.products.product_blocks.iptrunk import IptrunkSideBlock from gso.products.product_types.iptrunk import Iptrunk from gso.services import infoblox from gso.services.lso_client import execute_playbook, lso_interaction from gso.services.netbox_client import NetboxClient -from gso.utils.helpers import get_router_vendor, validate_tt_number +from gso.utils.helpers import get_router_vendor from gso.utils.shared_enums import Vendor +from gso.utils.types import TTNumber from gso.utils.workflow_steps import set_isis_to_max @@ -40,16 +40,12 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: ) info_label_3: Label = "ONLY EXECUTE THIS WORKFLOW WHEN YOU ARE ABSOLUTELY SURE WHAT YOU ARE DOING." - tt_number: str + tt_number: TTNumber termination_label: Label = ( "Please confirm whether configuration should get removed from the A and B sides of the trunk." ) remove_configuration: bool = True - @field_validator("tt_number") - def validate_tt_number(cls, tt_number: str) -> str: - return validate_tt_number(tt_number) - user_input = yield TerminateForm return user_input.model_dump() diff --git a/gso/workflows/router/create_router.py b/gso/workflows/router/create_router.py index 8382e227181233c145371f00aeb835ba91797f58..59dd6210578d9556fdf79ecacf7df0b5518fdbd5 100644 --- a/gso/workflows/router/create_router.py +++ b/gso/workflows/router/create_router.py @@ -25,6 +25,7 @@ from gso.services.sharepoint import SharePointClient from gso.settings import load_oss_params from gso.utils.helpers import generate_fqdn, iso_from_ipv4 from gso.utils.shared_enums import PortNumber, Vendor +from gso.utils.types import TTNumber from gso.utils.workflow_steps import ( deploy_base_config_dry, deploy_base_config_real, @@ -48,7 +49,7 @@ def initial_input_form_generator(product_name: str) -> FormGenerator: class CreateRouterForm(FormPage): model_config = ConfigDict(title=product_name) - tt_number: str + tt_number: TTNumber partner: ReadOnlyField("GEANT", default_type=str) # type: ignore[valid-type] vendor: Vendor router_site: _site_selector() # type: ignore[valid-type] diff --git a/gso/workflows/router/promote_p_to_pe.py b/gso/workflows/router/promote_p_to_pe.py index 5b3a4bdbc2cfa0a8d7458298f1464a56993278d7..c8c4f016b185fc544e8b013011dbb88e66fdf1d9 100644 --- a/gso/workflows/router/promote_p_to_pe.py +++ b/gso/workflows/router/promote_p_to_pe.py @@ -11,15 +11,16 @@ from orchestrator.utils.errors import ProcessFailureError from orchestrator.workflow import StepList, begin, conditional, done, inputstep, step, workflow from orchestrator.workflows.steps import resync, store_process_subscription, unsync from orchestrator.workflows.utils import wrap_modify_initial_input_form -from pydantic import ConfigDict, field_validator +from pydantic import ConfigDict from gso.products.product_blocks.router import RouterRole from gso.products.product_types.router import Router from gso.services import lso_client from gso.services.kentik_client import KentikClient, NewKentikDevice from gso.services.lso_client import lso_interaction -from gso.utils.helpers import generate_inventory_for_active_routers, validate_tt_number +from gso.utils.helpers import generate_inventory_for_active_routers from gso.utils.shared_enums import Vendor +from gso.utils.types import TTNumber def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: @@ -29,23 +30,11 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: class PromotePToPEForm(FormPage): model_config = ConfigDict(title=f"Promote {subscription.router.router_fqdn} to PE router?") - tt_number: str - - @field_validator("tt_number") - def validate_tt_number(cls, tt_number: str) -> str: - return validate_tt_number(tt_number) + tt_number: TTNumber user_input = yield PromotePToPEForm - return user_input.model_dump() - - -@step("Prepare required keys in state") -def prepare_state(subscription_id: UUIDstr) -> State: - """Add required keys to the state for the workflow to run successfully.""" - router = Router.from_subscription(subscription_id) - - return {"subscription": router} + return user_input.model_dump() | {"subscription": subscription} @step("Evacuate the router by setting isis_overload") @@ -138,8 +127,8 @@ def create_kentik_device(subscription: Router) -> State: kentik_site = kentik_client.get_site_by_name(subscription.router.router_site.site_name) if not kentik_site: - msg = f"Site could not be found in Kentik: {subscription.router.router_site.site_name}" - raise ProcessFailureError(msg) + msg = "Site could not be found in Kentik." + raise ProcessFailureError(msg, details=subscription.router.router_site.site_name) site_tier = subscription.router.router_site.site_tier new_device = NewKentikDevice( @@ -573,7 +562,6 @@ def promote_p_to_pe() -> StepList: return ( begin >> store_process_subscription(Target.MODIFY) - >> prepare_state >> router_is_juniper(done) >> router_is_pe(done) >> unsync diff --git a/gso/workflows/router/redeploy_base_config.py b/gso/workflows/router/redeploy_base_config.py index c1a24c8340dc6c129fa99c1bb93be528bd85bd18..b30d02f16b6bb197a20257f9016eabb6dad0d8fa 100644 --- a/gso/workflows/router/redeploy_base_config.py +++ b/gso/workflows/router/redeploy_base_config.py @@ -10,6 +10,7 @@ from orchestrator.workflows.utils import wrap_modify_initial_input_form from gso.products.product_types.router import Router from gso.services.lso_client import lso_interaction +from gso.utils.types import TTNumber from gso.utils.workflow_steps import deploy_base_config_dry, deploy_base_config_real @@ -18,7 +19,7 @@ def _initial_input_form(subscription_id: UUIDstr) -> FormGenerator: class RedeployBaseConfigForm(FormPage): info_label: Label = f"Redeploy base config on {router.router.router_fqdn}?" - tt_number: str + tt_number: TTNumber user_input = yield RedeployBaseConfigForm diff --git a/gso/workflows/router/terminate_router.py b/gso/workflows/router/terminate_router.py index 688e3ecc21543ddc5ca678296b763fe7c6194bb5..07b0692589262b0b35e80c13a6d42dc51cdfec07 100644 --- a/gso/workflows/router/terminate_router.py +++ b/gso/workflows/router/terminate_router.py @@ -26,6 +26,7 @@ from gso.services.lso_client import execute_playbook, lso_interaction from gso.services.netbox_client import NetboxClient from gso.utils.helpers import generate_inventory_for_active_routers from gso.utils.shared_enums import Vendor +from gso.utils.types import TTNumber logger = logging.getLogger(__name__) @@ -42,7 +43,7 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: ) info_label_3: Label = "ONLY EXECUTE THIS WORKFLOW WHEN YOU ARE ABSOLUTELY SURE WHAT YOU ARE DOING." - tt_number: str + tt_number: TTNumber termination_label: Label = "Please confirm whether configuration should get removed from the router." remove_configuration: bool = True update_ibgp_mesh_label: Label = "Please confirm whether the iBGP mesh should get updated." diff --git a/gso/workflows/router/update_ibgp_mesh.py b/gso/workflows/router/update_ibgp_mesh.py index 58d207029c9431d7252ddf2b5c87e171282944ba..8fbb2813c94cff8443647a033c146e93f34b7ca4 100644 --- a/gso/workflows/router/update_ibgp_mesh.py +++ b/gso/workflows/router/update_ibgp_mesh.py @@ -18,6 +18,7 @@ from gso.services import librenms_client, lso_client from gso.services.lso_client import lso_interaction from gso.services.subscriptions import get_trunks_that_terminate_on_router from gso.utils.helpers import SNMPVersion, generate_inventory_for_active_routers +from gso.utils.types import TTNumber def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: @@ -31,7 +32,7 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: class AddBGPSessionForm(FormPage): model_config = ConfigDict(title=f"Add {subscription.router.router_fqdn} to the iBGP mesh?") - tt_number: str + tt_number: TTNumber @model_validator(mode="before") def router_has_a_trunk(cls, data: Any) -> Any: