diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index d982e6f9..71301933 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -106,7 +106,7 @@ }, { "category": "deployment", - "description": "Deploy applications to AWS with architecture recommendations, cost estimates, and IaC deployment.", + "description": "Deploy applications to AWS with architecture recommendations, cost estimates, and IaC deployment. Generate validated AWS architecture diagrams as draw.io XML.", "keywords": [ "aws", "aws agent skills", @@ -115,12 +115,15 @@ "cdk", "cloudformation", "infrastructure", - "pricing" + "pricing", + "diagrams", + "draw.io", + "architecture" ], "name": "deploy-on-aws", "source": "./plugins/deploy-on-aws", - "tags": ["aws", "deploy", "infrastructure", "cdk"], - "version": "1.1.0" + "tags": ["aws", "deploy", "infrastructure", "cdk", "diagrams"], + "version": "1.2.0" }, { "category": "migration", diff --git a/plugins/deploy-on-aws/.claude-plugin/plugin.json b/plugins/deploy-on-aws/.claude-plugin/plugin.json index 9c74d834..b65ede56 100644 --- a/plugins/deploy-on-aws/.claude-plugin/plugin.json +++ b/plugins/deploy-on-aws/.claude-plugin/plugin.json @@ -2,7 +2,7 @@ "author": { "name": "Amazon Web Services" }, - "description": "Deploy applications to AWS with architecture recommendations, cost estimates, and IaC deployment.", + "description": "Deploy applications to AWS with architecture recommendations, cost estimates, and IaC deployment. Generate validated AWS architecture diagrams as draw.io XML.", "homepage": "https://github.com/awslabs/agent-plugins", "keywords": [ "aws", @@ -10,10 +10,13 @@ "infrastructure", "cdk", "cloudformation", - "pricing" + "pricing", + "diagrams", + "draw.io", + "architecture" ], "license": "Apache-2.0", "name": "deploy-on-aws", "repository": "https://github.com/awslabs/agent-plugins", - "version": "1.1.0" + "version": "1.2.0" } diff --git a/plugins/deploy-on-aws/.codex-plugin/plugin.json b/plugins/deploy-on-aws/.codex-plugin/plugin.json index be54b7e6..1d391615 100644 --- a/plugins/deploy-on-aws/.codex-plugin/plugin.json +++ b/plugins/deploy-on-aws/.codex-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "deploy-on-aws", - "version": "1.1.0", - "description": "Deploy applications to AWS with architecture recommendations, cost estimates, and IaC deployment.", + "version": "1.2.0", + "description": "Deploy applications to AWS with architecture recommendations, cost estimates, and IaC deployment. Generate validated AWS architecture diagrams as draw.io XML.", "author": { "name": "Amazon Web Services", "email": "aws-agent-plugins@amazon.com", @@ -16,7 +16,10 @@ "infrastructure", "cdk", "cloudformation", - "pricing" + "pricing", + "diagrams", + "draw.io", + "architecture" ], "skills": "./skills/", "mcpServers": "./.mcp.json", diff --git a/plugins/deploy-on-aws/README.md b/plugins/deploy-on-aws/README.md new file mode 100644 index 00000000..b8ff2f2f --- /dev/null +++ b/plugins/deploy-on-aws/README.md @@ -0,0 +1,57 @@ +# Deploy on AWS Plugin + +Deploy applications to AWS with architecture recommendations, cost estimates, IaC deployment, and validated architecture diagrams. + +## Overview + +This plugin provides two skills: infrastructure deployment with cost estimation, and AWS architecture diagram generation as draw.io XML with official AWS4 icons. + +## Skills + +| Skill | Description | +| -------------------------- | ----------------------------------------------------------------------------------------------------- | +| `deploy` | Analyze codebases, recommend AWS services, estimate costs, and generate CDK/CloudFormation IaC | +| `aws-architecture-diagram` | Generate validated draw.io architecture diagrams with AWS4 icons, step legends, and dark mode support | + +## MCP Servers + +| Server | Description | +| -------------- | ------------------------------------------------- | +| `awsiac` | AWS IaC best practices and patterns | +| `awsknowledge` | Architecture guidance and service recommendations | +| `awspricing` | Real-time AWS pricing data for cost estimation | + +## Installation + +```bash +/plugin marketplace add awslabs/agent-plugins +/plugin install deploy-on-aws@agent-plugins-for-aws +``` + +## Prerequisites + +- Python 3.9+ +- `defusedxml` — required for diagram XML validation: `pip3 install defusedxml>=0.7.1` +- AWS CLI with configured credentials (for deployment skills) +- [draw.io desktop](https://www.drawio.com/) (optional, for PNG/SVG/PDF export) + +## Examples + +- "Deploy my app to AWS" +- "Estimate AWS costs for this project" +- "Generate an architecture diagram for a serverless REST API" +- "Create a sketch-mode diagram of my IoT pipeline" +- "Analyze my codebase and generate an architecture diagram" + +## Files + +- `skills/deploy/SKILL.md` — Infrastructure deployment workflow +- `skills/aws-architecture-diagram/SKILL.md` — Diagram generation skill +- `skills/aws-architecture-diagram/references/` — Style guides, templates, and example diagrams +- `scripts/validate-drawio.sh` — PostToolUse hook for diagram validation +- `scripts/lib/` — Post-processing pipeline (icon colors, badge placement, legend sizing) +- `scripts/requirements.txt` — Python dependencies + +## License + +Apache-2.0 diff --git a/plugins/deploy-on-aws/hooks/hooks.json b/plugins/deploy-on-aws/hooks/hooks.json new file mode 100644 index 00000000..5e51d62c --- /dev/null +++ b/plugins/deploy-on-aws/hooks/hooks.json @@ -0,0 +1,16 @@ +{ + "hooks": { + "PostToolUse": [ + { + "matcher": "Edit|Write", + "hooks": [ + { + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/scripts/validate-drawio.sh", + "timeout": 30 + } + ] + } + ] + } +} diff --git a/plugins/deploy-on-aws/scripts/lib/aws4-shapes.json b/plugins/deploy-on-aws/scripts/lib/aws4-shapes.json new file mode 100644 index 00000000..10fe63f6 --- /dev/null +++ b/plugins/deploy-on-aws/scripts/lib/aws4-shapes.json @@ -0,0 +1,1306 @@ +{ + "description": "Complete AWS4 shape registry for draw.io mxgraph.aws4.* namespace. Auto-generated from Sidebar-AWS4.js (service icons) + aws4.xml stencils (sub-resources).", + "source": "https://github.com/jgraph/drawio/blob/dev/src/main/webapp/js/diagramly/sidebar/Sidebar-AWS4.js", + "stencil_source": "https://github.com/jgraph/drawio/blob/dev/src/main/webapp/stencils/aws4.xml", + "version": "2026.03", + "generated_from": "draw.io v29.6.1 (2026-03-06)", + "categories": { + "customer_enablement": { + "fillColor": "#3F8624", + "shapes": [ + "activate", + "customer_enablement", + "iq", + "managed_services", + "professional_services", + "repost", + "repost_private", + "support", + "training_certification" + ] + }, + "business_applications": { + "fillColor": "#E7157B", + "shapes": [ + "alexa_for_business", + "appfabric", + "business_application", + "chime", + "chime_sdk", + "connect", + "end_user_messaging", + "honeycode", + "pinpoint", + "quick_suite", + "simple_email_service", + "supply_chain", + "wickr", + "workdocs", + "workmail" + ] + }, + "general": { + "fillColor": "#232F3D", + "shapes": [ + "all_products", + "general", + "marketplace", + "productIcon", + "resourceIcon" + ] + }, + "front_end_web_mobile": { + "fillColor": "#E7157B", + "shapes": [ + "amplify", + "device_farm", + "location_service", + "mobile" + ] + }, + "analytics": { + "fillColor": "#8C4FFF", + "shapes": [ + "analytics", + "athena", + "clean_rooms", + "cloudsearch2", + "data_exchange", + "data_pipeline", + "datazone", + "elasticsearch_service", + "emr", + "entity_resolution", + "finspace", + "glue", + "glue_databrew", + "glue_elastic_views", + "kinesis", + "kinesis_data_analytics", + "kinesis_data_firehose", + "kinesis_data_streams", + "kinesis_video_streams", + "lake_formation", + "managed_service_for_apache_flink", + "managed_streaming_for_kafka", + "quicksight", + "redshift", + "sagemaker_2", + "sql_workbench" + ] + }, + "ai_ml": { + "fillColor": "#01A88D", + "shapes": [ + "apache_mxnet_on_aws", + "app_studio", + "augmented_ai", + "bedrock", + "bedrock_agentcore", + "codeguru_2", + "codewhisperer", + "comprehend", + "comprehend_medical", + "deep_learning_amis", + "deep_learning_containers", + "deepcomposer", + "deeplens", + "deepracer", + "devops_guru", + "elastic_inference_2", + "forecast", + "fraud_detector", + "healthimaging", + "healthlake", + "healthscribe", + "kendra", + "lex", + "lookout_for_equipment", + "lookout_for_metrics", + "lookout_for_vision", + "machine_learning", + "monitron", + "neuron_ml_sdk", + "nova2", + "omics", + "panorama", + "personalize", + "polly", + "q", + "rekognition_2", + "sagemaker", + "sagemaker_ground_truth", + "sagemaker_studio_lab", + "tensorflow_on_aws", + "textract", + "torchserve", + "transcribe", + "translate" + ] + }, + "application_integration": { + "fillColor": "#E7157B", + "shapes": [ + "api_gateway", + "appflow", + "application_integration", + "appsync", + "b2b_data_interchange", + "eventbridge", + "express_workflow", + "managed_workflows_for_apache_airflow", + "mobile_application", + "mq", + "sns", + "sqs", + "step_functions" + ] + }, + "management_governance": { + "fillColor": "#E7157B", + "shapes": [ + "app_config", + "app_wizard", + "application_auto_scaling", + "autoscaling", + "backint_agent", + "chatbot", + "cloudformation", + "cloudtrail", + "cloudwatch_2", + "codeguru", + "config", + "control_tower", + "devops_agent", + "distro_for_opentelemetry", + "license_manager", + "managed_service_for_grafana", + "managed_service_for_prometheus", + "management_and_governance", + "management_console", + "opsworks", + "organizations", + "partner_central", + "personal_health_dashboard", + "proton", + "resilience_hub", + "resource_explorer", + "service_catalog", + "service_management_connector", + "systems_manager", + "systems_manager_incident_manager", + "telco_network_builder", + "trusted_advisor", + "user_notifications", + "well_architect_tool" + ] + }, + "networking": { + "fillColor": "#8C4FFF", + "shapes": [ + "app_mesh", + "application_recovery_controller", + "client_vpn", + "cloud_directory", + "cloud_map", + "cloud_wan", + "cloudfront", + "direct_connect", + "global_accelerator", + "networking_and_content_delivery", + "private_5g", + "route_53", + "rtb_fabric", + "site_to_site_vpn", + "transit_gateway", + "verified_access", + "vpc", + "vpc_lattice", + "vpc_privatelink" + ] + }, + "compute": { + "fillColor": "#ED7100", + "shapes": [ + "app_runner", + "auto_scaling2", + "auto_scaling3", + "batch", + "bottlerocket", + "compute", + "compute_optimizer", + "ec2", + "ec2_image_builder", + "elastic_beanstalk", + "elastic_fabric_adapter", + "elastic_load_balancing", + "elastic_vmware_service", + "fargate", + "genomics_cli", + "lambda", + "lightsail", + "lightsail_for_research", + "local_zones", + "nice_dcv", + "nice_enginframe", + "nitro_enclaves", + "outposts", + "outposts_1u_and_2u_servers", + "outposts_family", + "parallel_cluster", + "parallel_computing_service", + "serverless_application_repository", + "simspace_weaver", + "vmware_cloud_on_aws", + "wavelength" + ] + }, + "developer_tools": { + "fillColor": "#C925D1", + "shapes": [ + "application_composer", + "cloud9", + "cloud_control_api", + "cloud_development_kit", + "cloudshell", + "codeartifact", + "codebuild", + "codecatalyst", + "codecommit", + "codedeploy", + "codepipeline", + "codestar", + "command_line_interface", + "corretto", + "developer_tools", + "fault_injection_simulator", + "tools_and_sdks", + "xray" + ] + }, + "cloud_financial_management": { + "fillColor": "#3F8624", + "shapes": [ + "application_cost_profiler", + "budgets_2", + "cost_and_usage_report", + "cost_explorer", + "cost_management", + "custom_billing_manager", + "reserved_instance_reporting", + "savings_plans" + ] + }, + "migration_modernization": { + "fillColor": "#3F8624", + "shapes": [ + "application_discovery_service", + "cloudendure_migration", + "data_transfer_terminal", + "datasync", + "mainframe_modernization", + "migration_and_transfer", + "migration_evaluator", + "migration_hub", + "server_migration_service", + "snowball", + "snowball_edge", + "snowmobile", + "transfer_family", + "transfer_for_sftp", + "transform" + ] + }, + "end_user_computing": { + "fillColor": "#ED7100", + "shapes": [ + "appstream_20", + "desktop_and_app_streaming", + "worklink", + "workspaces", + "workspaces_family", + "workspaces_thin_client" + ] + }, + "ar_vr": { + "fillColor": "#E7157B", + "shapes": [ + "ar_vr", + "sumerian" + ] + }, + "security": { + "fillColor": "#DD344C", + "shapes": [ + "access_analyzer", + "cognito", + "guardduty", + "inspector", + "inspector_agent", + "macie", + "secrets_manager", + "security_hub", + "waf", + "waf_filtering_rule", + "shield", + "shield_shield_advanced", + "identity_and_access_management", + "iam", + "iam_access_analyzer", + "iam_add_on", + "iam_aws_sts", + "iam_aws_sts_alternate", + "iam_data_encryption_key", + "iam_encrypted_data", + "iam_long_term_security_credential", + "iam_mfa_token", + "iam_permissions", + "iam_role", + "iam_temporary_security_credential", + "kms", + "certificate_manager", + "cloudhsm", + "detective", + "firewall_manager", + "artifact", + "audit_manager", + "directory_service", + "resource_access_manager", + "single_sign_on", + "sso", + "artifact", + "audit_manager", + "certificate_manager_3", + "cloudhsm", + "cognito", + "detective", + "directory_service", + "firewall_manager", + "guardduty", + "identity_and_access_management", + "inspector", + "key_management_service", + "macie", + "network_firewall", + "payment_cryptography", + "private_certificate_authority", + "resource_access_manager", + "secrets_manager", + "security_agent", + "security_hub", + "security_identity_and_compliance", + "security_incident_response", + "security_lake", + "shield", + "signer", + "single_sign_on", + "verified_permissions", + "waf" + ] + }, + "database": { + "fillColor": "#C925D1", + "shapes": [ + "aurora", + "database", + "database_migration_service", + "documentdb_with_mongodb_compatibility", + "dynamodb", + "elasticache", + "keyspaces", + "managed_apache_cassandra_service", + "memorydb_for_redis", + "neptune", + "oracle_database_at_aws", + "rds", + "rds_on_vmware", + "timestream" + ] + }, + "storage": { + "fillColor": "#3F8624", + "shapes": [ + "backup", + "cloudendure_disaster_recovery", + "efs_infrequentaccess", + "efs_standard", + "elastic_block_store", + "elastic_file_system", + "file_cache", + "fsx", + "fsx_for_lustre", + "fsx_for_netapp_ontap", + "fsx_for_openzfs", + "fsx_for_windows_file_server", + "glacier", + "infrequent_access_storage_class", + "s3", + "s3_on_outposts_storage", + "snowcone", + "storage", + "storage_gateway" + ] + }, + "blockchain": { + "fillColor": "#ED7100", + "shapes": [ + "blockchain", + "managed_blockchain", + "quantum_ledger_database" + ] + }, + "quantum_technologies": { + "fillColor": "#ED7100", + "shapes": [ + "braket", + "quantum_technologies" + ] + }, + "contact_center": { + "fillColor": "#E7157B", + "shapes": [ + "contact_center" + ] + }, + "containers": { + "fillColor": "#ED7100", + "shapes": [ + "containers", + "ecr", + "ecs", + "ecs_anywhere", + "eks", + "eks_anywhere", + "eks_cloud", + "eks_distro", + "red_hat_openshift" + ] + }, + "customer_engagement": { + "fillColor": "#E7157B", + "shapes": [ + "customer_engagement" + ] + }, + "iot": { + "fillColor": "#1A9C37", + "shapes": [ + "iot", + "iot_core", + "iot_greengrass", + "iot_analytics", + "iot_events", + "iot_sitewise", + "iot_device_defender", + "iot_device_management", + "iot_things_graph", + "iot_1click", + "iot_button", + "iot_certificate", + "iot_action", + "iot_actuator", + "iot_alexa_enabled_device", + "iot_alexa_skill", + "iot_alexa_voice_service", + "iot_bank", + "iot_bicycle", + "iot_camera", + "iot_car", + "iot_cart", + "iot_coffee_pot", + "iot_desired_state", + "iot_device_gateway", + "iot_dog", + "iot_door_lock", + "iot_factory", + "iot_fire_tv", + "iot_fire_tv_stick", + "iot_generic", + "iot_house", + "iot_http", + "iot_http2", + "iot_lambda", + "iot_lightbulb", + "iot_medical_emergency", + "iot_mqtt", + "iot_over_the_air_update", + "iot_police_emergency", + "iot_policy", + "iot_reported_state", + "iot_rule", + "iot_sensor", + "iot_servo", + "iot_shadow", + "iot_simulator", + "iot_thermostat", + "iot_topic", + "iot_travel", + "iot_utility", + "iot_windfarm", + "freertos", + "iot_device_defender2", + "iot_fleet_hub", + "iot_expresslink", + "iot_fleetwise", + "iot_roborunner", + "iot_twinmaker" + ] + }, + "media_services": { + "fillColor": "#ED7100", + "shapes": [ + "deadline_cloud", + "elastic_transcoder", + "elemental", + "elemental_link", + "elemental_mediaconnect", + "elemental_mediaconvert", + "elemental_medialive", + "elemental_mediapackage", + "elemental_mediastore", + "elemental_mediatailor", + "interactive_video", + "media_services", + "nimble_studio", + "thinkbox_deadline", + "thinkbox_draft", + "thinkbox_frost", + "thinkbox_krakatoa", + "thinkbox_sequoia", + "thinkbox_stoke", + "thinkbox_xmesh" + ] + }, + "iot": { + "fillColor": "#3F8624", + "shapes": [ + "freertos", + "greengrass", + "internet_of_things", + "iot_1click", + "iot_analytics", + "iot_button", + "iot_core", + "iot_device_defender", + "iot_device_management", + "iot_edukit", + "iot_events", + "iot_expresslink", + "iot_fleetwise", + "iot_roborunner", + "iot_sitewise", + "iot_things_graph", + "iot_twinmaker" + ] + }, + "games": { + "fillColor": "#ED7100", + "shapes": [ + "gamekit", + "gamelift_2", + "gamelift_streams", + "games", + "gamesparks", + "lumberyard", + "open_3d_engine_2" + ] + }, + "satellite": { + "fillColor": "#8C4FFF", + "shapes": [ + "ground_station", + "satellite" + ] + }, + "robotics": { + "fillColor": "#ED7100", + "shapes": [ + "robomaker", + "robotics" + ] + }, + "serverless": { + "fillColor": "#ED7100", + "shapes": [ + "serverless" + ] + }, + "groups": { + "fillColor": "none", + "shapes": [ + "group", + "group_account", + "group_auto_scaling_group", + "group_aws_cloud", + "group_aws_cloud_alt", + "group_aws_step_functions_workflow", + "group_corporate_data_center", + "group_ec2_instance_contents", + "group_elastic_beanstalk", + "group_iot_greengrass", + "group_iot_greengrass_deployment", + "group_on_premise", + "group_region", + "group_security_group", + "group_spot_fleet", + "group_vpc2" + ] + }, + "sub_resources": { + "fillColor": "varies_by_parent_service", + "note": "Sub-resource icons from stencil XML. Use shape=mxgraph.aws4.{name} style (not resourceIcon).", + "shapes": [ + "a1_instance", + "access_analyzer", + "action", + "actuator", + "ad_connector", + "addon", + "agent", + "agent2", + "alarm", + "alert", + "alexa_enabled_device", + "alexa_skill", + "alexa_smart_home_skill", + "alexa_voice_service", + "ami", + "amplify_aws_amplify_studio", + "application", + "application_discovery_service_aws_agentless_collector", + "application_discovery_service_aws_discovery_agent", + "application_discovery_service_migration_evaluator_collector", + "application_load_balancer", + "apps", + "archive", + "athena_data_source_connectors", + "attribute", + "attributes", + "aurora_instance", + "aurora_instance_alt", + "authenticated_user", + "auto_scaling", + "automation", + "aws_backup_for_aws_cloudformation", + "aws_backup_legal_hold", + "aws_backup_support_for_amazon_fsx_for_netapp_ontap", + "aws_backup_vault_lock", + "aws_backup_virtual_machine_monitor", + "aws_cloud", + "aws_glue_data_quality", + "aws_glue_for_ray", + "aws_user_notifications", + "backup_audit_manager", + "backup_aws_backup_support_for_amazon_s3", + "backup_aws_backup_support_for_vmware_workloads", + "backup_backup_plan", + "backup_backup_restore", + "backup_compliance_reporting", + "backup_compute", + "backup_database", + "backup_gateway", + "backup_plan", + "backup_recovery_point_objective", + "backup_recovery_time_objective", + "backup_restore", + "backup_storage", + "backup_vault", + "backup_virtual_machine", + "backup_virtual_machine_monitor", + "bank", + "blockchain_resource", + "braket_chandelier", + "braket_chip", + "braket_embedded_simulator", + "braket_managed_simulator", + "braket_noise_simulator", + "braket_qpu", + "braket_simulator", + "braket_simulator_1", + "braket_simulator_2", + "braket_simulator_3", + "braket_simulator_4", + "braket_state_vector", + "braket_tensor_network", + "bucket", + "bucket_with_objects", + "budgets", + "bycicle", + "c4_instance", + "c5_instance", + "c5a", + "c5ad", + "c5d", + "c5n_instance", + "c6g_instance", + "c6gd", + "cache_node", + "cached_volume", + "camera", + "camera2", + "car", + "cart", + "certificate_manager", + "certificate_manager_2", + "change_set", + "chat", + "checklist", + "checklist_cost", + "checklist_fault_tolerant", + "checklist_performance", + "checklist_security", + "classic_load_balancer", + "client", + "cloud_digital_interface", + "cloud_extension_ros", + "cloud_map_resource", + "cloud_wan_segment_network", + "cloud_wan_transit_gateway_route_table_attachment", + "cloud_wan_virtual_pop", + "cloudfront_functions", + "cloudsearch", + "cloudtrail_cloudtrail_lake", + "cloudwatch", + "cloudwatch_cross_account_observability", + "cloudwatch_data_protection", + "cloudwatch_evidently", + "cloudwatch_logs", + "cloudwatch_metrics_insights", + "cloudwatch_rum", + "cloudwatch_synthetics", + "cluster", + "coffee_pot", + "cold_storage", + "connector", + "container_1", + "container_2", + "container_3", + "container_registry_image", + "corporate_data_center", + "corporate_data_center2", + "credentials", + "custom_event_bus_resource", + "customer_gateway", + "d2_instance", + "d3_instance", + "d3en_instance", + "data_encryption_key", + "data_exchange_for_apis", + "data_lake_resource_icon", + "data_set", + "data_stream", + "data_table", + "database_migration_workflow_job", + "datasync_discovery", + "datazone_business_data_catalog", + "datazone_data_portal", + "datazone_data_projects", + "db_instance", + "db_instance_read_replica", + "db_instance_standby", + "db_on_instance", + "db_on_instance2", + "default_event_bus_resource", + "dense_compute_node", + "dense_storage_node", + "deployment", + "deployments", + "desired_state", + "development_environment", + "devops_guru_insights", + "disk", + "document", + "documentdb_elastic_clusters", + "documents", + "documents2", + "documents3", + "door_lock", + "download_distribution", + "dynamodb_dax", + "dynamodb_standard_access_table_class", + "dynamodb_standard_infrequent_access_table_class", + "dynamodb_stream", + "ec2_aws_microservice_extractor_for_net", + "ec2_c6a_instance", + "ec2_c6gn_instance", + "ec2_c6i_instance", + "ec2_c6in_instance", + "ec2_c7g_instance", + "ec2_c7gn_instance", + "ec2_dl1_instance", + "ec2_g5_instance", + "ec2_g5g_instance", + "ec2_hpc6a_instance", + "ec2_hpc6id_instance", + "ec2_i4i_instance", + "ec2_im4gn_instance", + "ec2_inf2_instance", + "ec2_instance_contents", + "ec2_is4gen_instance", + "ec2_m1_mac_instance", + "ec2_m6a_instance", + "ec2_m6i_instance", + "ec2_m6idn_instance", + "ec2_m6in_instance", + "ec2_p4de_instance", + "ec2_r6a_instance", + "ec2_r6i_instance", + "ec2_r6idn_instance", + "ec2_r6in_instance", + "ec2_r7iz_instance", + "ec2_trn1_instance", + "ec2_vt1_instance", + "ec2_x2gd_instance", + "ec2_x2idn_instance", + "ec2_x2iedn_instance", + "ec2_x2iezn_instance", + "echo", + "ecs_copilot_cli", + "ecs_service", + "ecs_service_connect", + "ecs_task", + "edge_location", + "eks_on_outposts", + "elastic_block_store_amazon_data_lifecycle_manager", + "elastic_block_store_volume_gp3", + "elastic_file_system_elastic_throughput", + "elastic_file_system_infrequent_access", + "elastic_file_system_intelligent_tiering", + "elastic_file_system_one_zone", + "elastic_file_system_one_zone_infrequent_access", + "elastic_file_system_one_zone_standard", + "elastic_file_system_standard", + "elastic_file_system_standard_infrequent_access", + "elastic_inference", + "elastic_ip_address", + "elastic_network_adapter", + "elastic_network_interface", + "elasticache_for_memcached", + "elasticache_for_redis", + "elasticache_for_valkey", + "email", + "email_2", + "email_notification", + "emr_engine", + "emr_engine_mapr_m3", + "emr_engine_mapr_m5", + "emr_engine_mapr_m7", + "encrypted_data", + "endpoint", + "endpoints", + "event", + "event_event_based", + "event_resource", + "event_time_based", + "eventbridge_custom_event_bus_resource", + "eventbridge_default_event_bus_resource", + "eventbridge_pipes", + "eventbridge_saas_partner_event_bus_resource", + "eventbridge_scheduler", + "eventbridge_schema", + "eventbridge_schema_registry", + "external_sdk", + "external_toolkit", + "f1_instance", + "factory", + "file_cache_hybrid_nfs_linked_datasets", + "file_cache_on_premises_nfs_linked_datasets", + "file_cache_s3_linked_datasets", + "file_gateway", + "file_system", + "filtering_rule", + "finding", + "firetv", + "firetv_stick", + "fleet_management", + "flow_logs", + "folder", + "folders", + "forums", + "fsx_file_gateway", + "g3_instance", + "g4ad_instance", + "g4dn", + "game_tech", + "game_tech2", + "gamelift", + "gateway", + "gateway_load_balancer", + "gear", + "general_access_points", + "generic", + "generic_application", + "generic_database", + "generic_firewall", + "git_repository", + "glacier_deep_archive", + "global_secondary_index", + "globe", + "glue_crawlers", + "glue_data_catalog", + "group_availability_zone", + "group_elastic_load_balancing", + "group_subnet", + "group_vpc", + "h1_instance", + "habana_gaudi", + "hardware_board", + "hdfs_cluster", + "high_memory_instance", + "hosted_zone", + "house", + "http2_protocol", + "http_notification", + "http_protocol", + "i2", + "i3_instance", + "i3en", + "identity_access_management_iam_roles_anywhere", + "illustration_desktop", + "illustration_devices", + "illustration_notification", + "illustration_office_building", + "illustration_users", + "import_export", + "inf1", + "inferentia", + "instance", + "instance2", + "instance_with_cloudwatch", + "instance_with_cloudwatch2", + "instances", + "instances_2", + "intelligent_tiering", + "internet", + "internet_alt1", + "internet_alt2", + "internet_alt22", + "internet_gateway", + "inventory", + "iot_analytics_channel", + "iot_analytics_data_store", + "iot_analytics_dataset", + "iot_analytics_pipeline", + "iot_core_device_advisor", + "iot_core_device_location", + "iot_device_defender_iot_device_jobs", + "iot_device_gateway", + "iot_device_jobs_resource", + "iot_device_management_fleet", + "iot_device_tester", + "iot_greengrass_artifact", + "iot_greengrass_component", + "iot_greengrass_component_machine_learning", + "iot_greengrass_component_nucleus", + "iot_greengrass_component_private", + "iot_greengrass_component_public", + "iot_greengrass_interprocess_communication", + "iot_greengrass_protocol", + "iot_greengrass_recipe", + "iot_greengrass_stream_manager", + "iot_lorawan_protocol", + "iot_over_the_air_update", + "iot_sailboat", + "iot_sitewise_asset", + "iot_sitewise_asset_hierarchy", + "iot_sitewise_asset_model", + "iot_sitewise_asset_properties", + "iot_sitewise_data_streams", + "iot_thing_freertos_device", + "iot_thing_humidity_sensor", + "iot_thing_industrial_pc", + "iot_thing_plc", + "iot_thing_relay", + "iot_thing_stacklight", + "iot_thing_temperature_humidity_sensor", + "iot_thing_temperature_sensor", + "iot_thing_temperature_vibration_sensor", + "iot_thing_vibration_sensor", + "item", + "items", + "json_script", + "key_management_service_external_key_store", + "lambda_function", + "layers", + "license_manager_application_discovery", + "license_manager_license_blending", + "lightbulb", + "location_service_geofence", + "location_service_map", + "location_service_place", + "location_service_routes", + "location_service_track", + "logs", + "long_term_security_credential", + "m4_instance", + "m5_instance", + "m5a_instance", + "m5d_instance", + "m5dn_instance", + "m5n", + "m5n_instance", + "m5zn_instance", + "m6g_instance", + "m6gd_instance", + "mac_instance", + "magnifying_glass", + "magnifying_glass_2", + "mainframe_modernization_analyzer", + "mainframe_modernization_compiler", + "mainframe_modernization_converter", + "mainframe_modernization_developer", + "mainframe_modernization_runtime", + "maintenance_windows", + "managed_ms_ad", + "management_console2", + "mediaconnect_gateway", + "medical_emergency", + "mesh", + "message", + "metrics", + "mfa_token", + "migration_hub_refactor_spaces_applications", + "migration_hub_refactor_spaces_environments", + "migration_hub_refactor_spaces_services", + "mobile_client", + "mobile_hub", + "monitoring", + "mq_broker", + "mqtt_protocol", + "ms_sql_instance", + "ms_sql_instance_alternate", + "msk_amazon_msk_connect", + "multimedia", + "multiple_volumes_resource", + "mxgraph.aws4", + "mysql_db_instance", + "mysql_db_instance_alternate", + "namespace", + "nat_gateway", + "network_access_control_list", + "network_firewall_endpoints", + "network_load_balancer", + "non_cached_volume", + "notebook", + "nova", + "object", + "office_building", + "one_zone_ia", + "open_3d_engine", + "opensearch_dashboards", + "opensearch_ingestion", + "opensearch_observability", + "opensearch_service_cluster_administrator_node", + "opensearch_service_data_node", + "opensearch_service_index", + "opensearch_service_traces", + "opensearch_service_ultrawarm_node", + "opsworks_apps", + "opsworks_permissions", + "optimized_instance", + "oracle_db_instance", + "oracle_db_instance_alternate", + "organizations_account", + "organizations_account2", + "organizations_management_account", + "organizations_management_account2", + "organizations_organizational_unit", + "organizations_organizational_unit2", + "p2_instance", + "p3_instance", + "p3dn_instance", + "p4_instance", + "p4d_instance", + "parameter_store", + "patch_manager", + "peering", + "permissions", + "permissions_2", + "pinpoint_journey", + "police_emergency", + "policy", + "postgresql_instance", + "privatelink", + "programming_language", + "question", + "queue", + "quicksight_paginated_reports", + "r4_instance", + "r5_instance", + "r5a_instance", + "r5ad_instance", + "r5b_instance", + "r5d_instance", + "r5gd_instance", + "r5n", + "r5n_instance", + "r6g_instance", + "rdn_instance", + "rds_blue_green_deployments", + "rds_instance", + "rds_instance_alt", + "rds_mariadb_instance", + "rds_mariadb_instance_alt", + "rds_multi_az", + "rds_multi_az_db_cluster", + "rds_mysql_instance", + "rds_mysql_instance_alt", + "rds_optimized_writes", + "rds_oracle_instance", + "rds_oracle_instance_alt", + "rds_piop", + "rds_piops", + "rds_postgresql_instance", + "rds_postgresql_instance_alt", + "rds_proxy", + "rds_proxy_alt", + "rds_sql_server_instance", + "rds_sql_server_instance_alt", + "rds_trusted_language_extensions_for_postgresql", + "recover", + "redshift_auto_copy", + "redshift_data_sharing_governance", + "redshift_ml", + "redshift_query_editor_v20_light", + "redshift_ra3", + "redshift_streaming_ingestion", + "registry", + "rekognition", + "rekognition_image", + "rekognition_video", + "replication", + "replication_time_control", + "reported_state", + "rescue", + "resource", + "resources", + "role", + "route_53_application_recovery_controller", + "route_53_readiness_checks", + "route_53_resolver", + "route_53_resolver_dns_firewall", + "route_53_resolver_query_logging", + "route_53_routing_controls", + "route_table", + "router", + "rule", + "rule_2", + "rule_3", + "run_command", + "s3_batch_operations", + "s3_express_one_zone", + "s3_file_gateway", + "s3_multi_region_access_points", + "s3_object_lambda", + "s3_object_lambda_access_points", + "s3_object_lock", + "s3_on_outposts", + "s3_replication_time_control", + "s3_select", + "s3_storage_lens", + "s3_tables", + "s3_vectors", + "saas_event_bus_resource", + "sagemaker_canvas", + "sagemaker_geospatial_ml", + "sagemaker_model", + "sagemaker_notebook", + "sagemaker_shadow_testing", + "sagemaker_train", + "saml_token", + "search_documents", + "security_group", + "security_hub_finding", + "sensor", + "servers", + "service", + "servo", + "shadow", + "shield2", + "shield_shield_advanced", + "simple_ad", + "simple_storage_service_directory_bucket", + "simple_storage_service_s3_glacier_instant_retrieval", + "simulation", + "simulator", + "snapshot", + "source_code", + "spot_instance", + "sql_primary", + "sql_replica", + "ssl_padlock", + "stack", + "stack2", + "standard_ia", + "state_manager", + "streaming_distribution", + "sts", + "sts_alternate", + "systems_manager_application_manager", + "systems_manager_change_calendar", + "systems_manager_change_manager", + "systems_manager_compliance", + "systems_manager_distributor", + "systems_manager_opscenter", + "systems_manager_session_manager", + "t2_instance", + "t3_instance", + "t3a_instance", + "t4g_instance", + "table", + "tape_gateway", + "tape_storage", + "template", + "temporary_security_credential", + "textract_analyze_lending", + "thermostat", + "topic", + "topic_2", + "traditional_server", + "trainium_instance", + "transfer_family_aws_as2", + "transfer_for_ftp_resource", + "transfer_for_ftps_resource", + "transfer_for_sftp_resource", + "transit_gateway_attachment", + "travel", + "user", + "users", + "utility", + "vault", + "virtual_gateway", + "virtual_node", + "virtual_private_cloud", + "virtual_router", + "virtual_service", + "virtual_tape_library", + "volume", + "volume_gateway", + "vpc_access_points", + "vpc_carrier_gateway", + "vpc_network_access_analyzer", + "vpc_reachability_analyzer", + "vpc_traffic_mirroring", + "vpc_virtual_private_cloud_vpc", + "vpn_connection", + "vpn_gateway", + "waf_bad_bot", + "waf_bot", + "waf_bot_control", + "waf_labels", + "waf_managed_rule", + "waf_rule", + "well_architected_tool", + "windfarm", + "work_package", + "workspaces_family_amazon_workspaces", + "workspaces_family_amazon_workspaces_core", + "workspaces_workspaces_web", + "x1_instance", + "x1_instance2", + "x1e_instance", + "z1d_instance" + ] + } + } +} diff --git a/plugins/deploy-on-aws/scripts/lib/drawio_url.py b/plugins/deploy-on-aws/scripts/lib/drawio_url.py new file mode 100755 index 00000000..4bec7628 --- /dev/null +++ b/plugins/deploy-on-aws/scripts/lib/drawio_url.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +""" +drawio_url.py - Generate a draw.io URL from a .drawio file + +Compresses the XML and encodes it into a URL fragment that +opens directly in app.diagrams.net with the diagram loaded. + +Usage: python3 drawio_url.py [--open] + --open Also opens the URL in the default browser +""" + +import json +import subprocess # nosec B404 # nosemgrep: gitlab.bandit.B404 - opens URLs in browser via platform launcher +import sys +import zlib +from base64 import b64encode +from pathlib import Path +from platform import system +from urllib.parse import quote + + +MAX_FILE_SIZE = 2 * 1024 * 1024 # 2 MB + + +def generate_url(file_path): + path = Path(file_path) + file_size = path.stat().st_size + if file_size > MAX_FILE_SIZE: + print( + f"Error: File too large ({file_size // 1024}KB > {MAX_FILE_SIZE // 1024 // 1024}MB limit)", + file=sys.stderr, + ) + sys.exit(1) + + xml = path.read_text(encoding="utf-8").strip() + + if not xml: + print("Error: File is empty", file=sys.stderr) + sys.exit(1) + + # 1. URL-encode the XML + encoded = quote(xml, safe="") + + # 2. Deflate compress (raw deflate, no zlib header) + # wbits=-15 gives raw deflate like Node's deflateRaw + compressed = zlib.compress(encoded.encode("utf-8"), level=9) + # Strip zlib header (first 2 bytes) and checksum (last 4 bytes) for raw deflate + raw_deflated = compressed[2:-4] + + # 3. Base64 encode + base64_str = b64encode(raw_deflated).decode("ascii") + + # 4. Build the JSON payload + payload = json.dumps({"type": "xml", "compressed": True, "data": base64_str}) + + # 5. URL-encode the payload and build the full URL + url = f"https://app.diagrams.net/#create={quote(payload, safe='')}" + + return url + + +def main(): + args = sys.argv[1:] + file_path = next((a for a in args if not a.startswith("--")), None) + should_open = "--open" in args + + if not file_path: + print("Usage: drawio_url.py [--open]", file=sys.stderr) + sys.exit(1) + + url = generate_url(file_path) + print(url) + + if should_open: + os_name = system() + if os_name == "Darwin": + cmd = "open" + elif os_name == "Windows": + cmd = "start" + else: + cmd = "xdg-open" + subprocess.run([cmd, url], check=False) # nosec B603 - cmd is a hardcoded platform launcher + + +if __name__ == "__main__": + main() diff --git a/plugins/deploy-on-aws/scripts/lib/fix_icon_colors.py b/plugins/deploy-on-aws/scripts/lib/fix_icon_colors.py new file mode 100644 index 00000000..913776b3 --- /dev/null +++ b/plugins/deploy-on-aws/scripts/lib/fix_icon_colors.py @@ -0,0 +1,354 @@ +#!/usr/bin/env python3 +"""Fix incorrect icon fillColor, container colors, and shape names. + +Fixes three types of issues: +1. Icon fillColor: resIcon cells with wrong category fill color +2. Container tint/stroke: parent container with wrong category colors +3. Shape renames: broken shape names (e.g., iam → identity_and_access_management) + +""" + +import argparse +import defusedxml.ElementTree as ET + +# Broken shape names → correct shape names +SHAPE_RENAMES: dict[str, str] = { + "mxgraph.aws4.iam": "mxgraph.aws4.identity_and_access_management", + "mxgraph.aws4.iot_sensor": "mxgraph.aws4.sensor", +} + +# Map resIcon → (container_fillColor, container_strokeColor) +# Container = the 120x120 rounded rect that holds the icon +SHAPE_TO_CONTAINER: dict[str, tuple[str, str]] = { + # Networking → #EDE7F6 / #8C4FFF + "mxgraph.aws4.api_gateway": ("#EDE7F6", "#8C4FFF"), + "mxgraph.aws4.cloudfront": ("#EDE7F6", "#8C4FFF"), + "mxgraph.aws4.route_53": ("#EDE7F6", "#8C4FFF"), + "mxgraph.aws4.elastic_load_balancing": ("#EDE7F6", "#8C4FFF"), + "mxgraph.aws4.vpc": ("#EDE7F6", "#8C4FFF"), + "mxgraph.aws4.transit_gateway": ("#EDE7F6", "#8C4FFF"), + "mxgraph.aws4.direct_connect": ("#EDE7F6", "#8C4FFF"), + "mxgraph.aws4.cloud_map": ("#EDE7F6", "#8C4FFF"), + "mxgraph.aws4.nat_gateway": ("#EDE7F6", "#8C4FFF"), + "mxgraph.aws4.internet_gateway": ("#EDE7F6", "#8C4FFF"), + # Analytics → #EDE7F6 / #8C4FFF + "mxgraph.aws4.kinesis_data_streams": ("#EDE7F6", "#8C4FFF"), + "mxgraph.aws4.kinesis_data_firehose": ("#EDE7F6", "#8C4FFF"), + "mxgraph.aws4.athena": ("#EDE7F6", "#8C4FFF"), + "mxgraph.aws4.glue": ("#EDE7F6", "#8C4FFF"), + "mxgraph.aws4.quicksight": ("#EDE7F6", "#8C4FFF"), + # Developer Tools → #EDE7F6 / #8C4FFF + "mxgraph.aws4.codecommit": ("#EDE7F6", "#8C4FFF"), + "mxgraph.aws4.codepipeline": ("#EDE7F6", "#8C4FFF"), + "mxgraph.aws4.codebuild": ("#EDE7F6", "#8C4FFF"), + "mxgraph.aws4.codedeploy": ("#EDE7F6", "#8C4FFF"), + "mxgraph.aws4.xray": ("#EDE7F6", "#8C4FFF"), + "mxgraph.aws4.x_ray": ("#EDE7F6", "#8C4FFF"), + # Compute → #FFF2E8 / #ED7100 + "mxgraph.aws4.lambda": ("#FFF2E8", "#ED7100"), + "mxgraph.aws4.ec2": ("#FFF2E8", "#ED7100"), + "mxgraph.aws4.fargate": ("#FFF2E8", "#ED7100"), + "mxgraph.aws4.app_runner": ("#FFF2E8", "#ED7100"), + "mxgraph.aws4.ecs": ("#FFF2E8", "#ED7100"), + # Database → #F5E6F7 / #C925D1 + "mxgraph.aws4.dynamodb": ("#F5E6F7", "#C925D1"), + "mxgraph.aws4.rds": ("#F5E6F7", "#C925D1"), + "mxgraph.aws4.aurora": ("#F5E6F7", "#C925D1"), + "mxgraph.aws4.elasticache": ("#F5E6F7", "#C925D1"), + "mxgraph.aws4.dynamodb_streams": ("#F5E6F7", "#C925D1"), + "mxgraph.aws4.dynamodb_stream": ("#F5E6F7", "#C925D1"), + # Storage → #E8F5E9 / #3F8624 + "mxgraph.aws4.s3": ("#E8F5E9", "#3F8624"), + "mxgraph.aws4.ebs": ("#E8F5E9", "#3F8624"), + "mxgraph.aws4.efs": ("#E8F5E9", "#3F8624"), + # App Integration → #FCE4EC / #E7157B + "mxgraph.aws4.eventbridge": ("#FCE4EC", "#E7157B"), + "mxgraph.aws4.sqs": ("#FCE4EC", "#E7157B"), + "mxgraph.aws4.sns": ("#FCE4EC", "#E7157B"), + "mxgraph.aws4.step_functions": ("#FCE4EC", "#E7157B"), + # Security → #FFEBEE / #DD344C + "mxgraph.aws4.identity_and_access_management": ("#FFEBEE", "#DD344C"), + "mxgraph.aws4.cognito": ("#FFEBEE", "#DD344C"), + "mxgraph.aws4.waf": ("#FFEBEE", "#DD344C"), + "mxgraph.aws4.kms": ("#FFEBEE", "#DD344C"), + "mxgraph.aws4.secrets_manager": ("#FFEBEE", "#DD344C"), + "mxgraph.aws4.certificate_manager": ("#FFEBEE", "#DD344C"), + "mxgraph.aws4.directory_service": ("#FFEBEE", "#DD344C"), + # AI/ML → #E0F2F1 / #01A88D + "mxgraph.aws4.sagemaker": ("#E0F2F1", "#01A88D"), + "mxgraph.aws4.bedrock": ("#E0F2F1", "#01A88D"), + "mxgraph.aws4.bedrock_agentcore": ("#E0F2F1", "#01A88D"), + # IoT → #E8F5E9 / #1A9C37 + "mxgraph.aws4.iot_core": ("#E8F5E9", "#1A9C37"), + "mxgraph.aws4.iot_greengrass": ("#E8F5E9", "#1A9C37"), + "mxgraph.aws4.sensor": ("#E8F5E9", "#1A9C37"), + "mxgraph.aws4.iot_sensor": ("#E8F5E9", "#1A9C37"), + # Management → #FCE4EC / #E7157B + "mxgraph.aws4.cloudwatch": ("#FCE4EC", "#E7157B"), + "mxgraph.aws4.cloudtrail": ("#FCE4EC", "#E7157B"), + "mxgraph.aws4.systems_manager": ("#FCE4EC", "#E7157B"), +} + +# Map resIcon shape names to correct icon fillColor +SHAPE_TO_FILL: dict[str, str] = { + # Developer Tools → #8C4FFF (often confused with Database #C925D1) + "mxgraph.aws4.codecommit": "#8C4FFF", + "mxgraph.aws4.codepipeline": "#8C4FFF", + "mxgraph.aws4.codebuild": "#8C4FFF", + "mxgraph.aws4.codedeploy": "#8C4FFF", + "mxgraph.aws4.codestar": "#8C4FFF", + "mxgraph.aws4.codeartifact": "#8C4FFF", + "mxgraph.aws4.cloud9": "#8C4FFF", + "mxgraph.aws4.xray": "#8C4FFF", + "mxgraph.aws4.x_ray": "#8C4FFF", + # Networking/Analytics → #8C4FFF + "mxgraph.aws4.kinesis_data_streams": "#8C4FFF", + "mxgraph.aws4.kinesis_data_firehose": "#8C4FFF", + "mxgraph.aws4.athena": "#8C4FFF", + "mxgraph.aws4.glue": "#8C4FFF", + "mxgraph.aws4.quicksight": "#8C4FFF", + "mxgraph.aws4.api_gateway": "#8C4FFF", + "mxgraph.aws4.cloudfront": "#8C4FFF", + "mxgraph.aws4.route_53": "#8C4FFF", + "mxgraph.aws4.vpc": "#8C4FFF", + "mxgraph.aws4.elastic_load_balancing": "#8C4FFF", + "mxgraph.aws4.transit_gateway": "#8C4FFF", + "mxgraph.aws4.direct_connect": "#8C4FFF", + "mxgraph.aws4.cloud_map": "#8C4FFF", + # Compute → #ED7100 + "mxgraph.aws4.lambda": "#ED7100", + "mxgraph.aws4.ec2": "#ED7100", + "mxgraph.aws4.fargate": "#ED7100", + "mxgraph.aws4.app_runner": "#ED7100", + "mxgraph.aws4.ecs": "#ED7100", + # Database → #C925D1 + "mxgraph.aws4.dynamodb": "#C925D1", + "mxgraph.aws4.rds": "#C925D1", + "mxgraph.aws4.aurora": "#C925D1", + "mxgraph.aws4.elasticache": "#C925D1", + "mxgraph.aws4.dynamodb_streams": "#C925D1", + # Storage → #3F8624 + "mxgraph.aws4.s3": "#3F8624", + "mxgraph.aws4.ebs": "#3F8624", + "mxgraph.aws4.efs": "#3F8624", + # App Integration → #E7157B + "mxgraph.aws4.eventbridge": "#E7157B", + "mxgraph.aws4.sqs": "#E7157B", + "mxgraph.aws4.sns": "#E7157B", + "mxgraph.aws4.step_functions": "#E7157B", + # Security → #DD344C + "mxgraph.aws4.identity_and_access_management": "#DD344C", + "mxgraph.aws4.cognito": "#DD344C", + "mxgraph.aws4.waf": "#DD344C", + "mxgraph.aws4.kms": "#DD344C", + "mxgraph.aws4.secrets_manager": "#DD344C", + "mxgraph.aws4.certificate_manager": "#DD344C", + # AI/ML → #01A88D + "mxgraph.aws4.sagemaker": "#01A88D", + "mxgraph.aws4.bedrock": "#01A88D", + "mxgraph.aws4.bedrock_agentcore": "#01A88D", + # IoT → #1A9C37 + "mxgraph.aws4.iot_core": "#1A9C37", + "mxgraph.aws4.iot_greengrass": "#1A9C37", + "mxgraph.aws4.iot_analytics": "#1A9C37", + "mxgraph.aws4.sensor": "#1A9C37", + "mxgraph.aws4.iot_sensor": "#1A9C37", + # Management → #E7157B + "mxgraph.aws4.cloudwatch": "#E7157B", + "mxgraph.aws4.cloudtrail": "#E7157B", + "mxgraph.aws4.systems_manager": "#E7157B", +} + + +def _replace_style_value(style: str, key: str, new_val: str) -> str: + """Replace a key=value in a semicolon-delimited style string.""" + parts = [] + found = False + for part in style.split(";"): + if part.startswith(f"{key}="): + parts.append(f"{key}={new_val}") + found = True + else: + parts.append(part) + if not found: + parts.append(f"{key}={new_val}") + return ";".join(parts) + + +def _extract_color(value: str) -> str | None: + """Extract a hex color from a style value, unwrapping light-dark() if present. + + Examples: + '#E7157B' → '#E7157B' + 'light-dark(#DE227B,#DE227B)' → '#DE227B' + 'light-dark(#FCE4EC,#FCE4EC)' → '#FCE4EC' + """ + if not value: + return None + if "light-dark(" in value: + # Extract first color from light-dark(COLOR1,COLOR2) + inner = value.split("light-dark(", 1)[1].rstrip(")") + parts = inner.split(",") + return parts[0].strip() if parts else None + if value.startswith("#"): + return value + return None + + +def fix_icon_colors(tree: ET.ElementTree, verbose: bool = False) -> int: + """Fix icon fillColor, container tint/stroke, and broken shape names. + + 1. Rename broken resIcon shapes (e.g., iam → identity_and_access_management) + 2. Fix icon fillColor to match category + 3. Fix parent container fillColor/strokeColor to match category + + Returns total number of cells fixed. + """ + root_elem = tree.getroot() + fixed = 0 + + # Build a map of cell ID → cell element + cells: dict[str, ET.Element] = {} + for cell in root_elem.iter("mxCell"): + cid = cell.get("id") + if cid: + cells[cid] = cell + + for cell in root_elem.iter("mxCell"): + style = cell.get("style", "") + if "resIcon=" not in style: + continue + + # Extract resIcon value + res_icon = None + for part in style.split(";"): + if part.startswith("resIcon="): + res_icon = part.split("=", 1)[1] + break + + if res_icon is None: + continue + + # Step 1: Fix broken shape names + if res_icon in SHAPE_RENAMES: + new_icon = SHAPE_RENAMES[res_icon] + style = style.replace(f"resIcon={res_icon}", f"resIcon={new_icon}") + cell.set("style", style) + fixed += 1 + if verbose: + cid = cell.get("id", "?") + print(f" Renamed {cid}: {res_icon} -> {new_icon}") + res_icon = new_icon + + # Step 2: Fix icon fillColor + if res_icon in SHAPE_TO_FILL: + expected_fill = SHAPE_TO_FILL[res_icon] + current_fill = None + for part in style.split(";"): + if part.startswith("fillColor="): + current_fill = part.split("=", 1)[1] + break + + if current_fill is not None and current_fill.upper() != expected_fill.upper(): + style = _replace_style_value(style, "fillColor", expected_fill) + cell.set("style", style) + fixed += 1 + if verbose: + cid = cell.get("id", "?") + print(f" Fixed icon {cid}: fillColor {current_fill} -> {expected_fill}") + + # Step 3: Fix parent container tint/stroke/fontColor + if res_icon in SHAPE_TO_CONTAINER: + expected_tint, expected_stroke = SHAPE_TO_CONTAINER[res_icon] + parent_id = cell.get("parent", "") + parent_cell = cells.get(parent_id) + + if parent_cell is not None and parent_cell.get("vertex") == "1": + parent_style = parent_cell.get("style", "") + + # Only fix containers (rounded rect with strokeColor) + if "strokeColor=" not in parent_style: + continue + + # Check current container colors + cur_stroke_raw = None + cur_fill_raw = None + cur_font_raw = None + for part in parent_style.split(";"): + if part.startswith("strokeColor="): + cur_stroke_raw = part.split("=", 1)[1] + elif part.startswith("fillColor="): + cur_fill_raw = part.split("=", 1)[1] + elif part.startswith("fontColor="): + cur_font_raw = part.split("=", 1)[1] + + container_changed = False + + # Fix strokeColor — handle both plain and light-dark() wrapped + if cur_stroke_raw: + cur_stroke = _extract_color(cur_stroke_raw) + if cur_stroke and cur_stroke.upper() != expected_stroke.upper(): + if "light-dark" in cur_stroke_raw: + # Replace with light-dark using correct color + new_val = f"light-dark({expected_stroke},{expected_stroke})" + else: + new_val = expected_stroke + parent_style = _replace_style_value(parent_style, "strokeColor", new_val) + container_changed = True + + # Fix fillColor — handle both plain and light-dark() wrapped + if cur_fill_raw: + cur_fill = _extract_color(cur_fill_raw) + if cur_fill and cur_fill.upper() != expected_tint.upper(): + if "light-dark" in cur_fill_raw: + new_val = f"light-dark({expected_tint},{expected_tint})" + else: + new_val = expected_tint + parent_style = _replace_style_value(parent_style, "fillColor", new_val) + container_changed = True + + # Fix fontColor to match category stroke color + if cur_font_raw: + cur_font = _extract_color(cur_font_raw) + if cur_font and cur_font.upper() != expected_stroke.upper(): + # Don't touch light-dark font colors (those are for dark mode text) + if "light-dark" not in cur_font_raw: + parent_style = _replace_style_value(parent_style, "fontColor", expected_stroke) + container_changed = True + + if container_changed: + parent_cell.set("style", parent_style) + fixed += 1 + if verbose: + pid = parent_cell.get("id", "?") + print(f" Fixed container {pid}: fill={expected_tint} stroke={expected_stroke} font={expected_stroke}") + + return fixed + + +def main() -> None: + parser = argparse.ArgumentParser( + description="Fix AWS service icon fill colors in draw.io files" + ) + parser.add_argument("file", help="Path to .drawio file") + parser.add_argument("--dry-run", action="store_true") + parser.add_argument("--verbose", "-v", action="store_true") + args = parser.parse_args() + + tree = ET.parse(args.file) + fixed = fix_icon_colors(tree, args.verbose) + + if fixed > 0: + print(f"Icons fixed: {fixed}") + if not args.dry_run: + ET.indent(tree, space=" ") + tree.write(args.file, encoding="unicode", xml_declaration=False) + print(f"Written: {args.file}") + else: + print("(dry run, no changes written)") + else: + print("No icon color issues found") + + +if __name__ == "__main__": + main() diff --git a/plugins/deploy-on-aws/scripts/lib/fix_nesting.py b/plugins/deploy-on-aws/scripts/lib/fix_nesting.py new file mode 100644 index 00000000..9080e68e --- /dev/null +++ b/plugins/deploy-on-aws/scripts/lib/fix_nesting.py @@ -0,0 +1,170 @@ +#!/usr/bin/env python3 +"""Fix container nesting issues in draw.io AWS architecture diagrams. + +Region groups should use container=0 (decoration-only). If they use +container=1, services nested inside them cause edge routing failures +because draw.io's orthogonal auto-router can't resolve paths across +multiple container boundaries. + +This script: +1. Finds Region cells with container=1 in their style +2. Changes them to container=0 +3. Re-parents all children from the region to the region's parent +4. Converts children's relative coordinates to absolute by adding + the region's offset + +""" + +import argparse +import defusedxml.ElementTree as ET + + +def get_style_dict(style_str: str) -> dict[str, str]: + result: dict[str, str] = {} + if not style_str: + return result + for part in style_str.split(";"): + part = part.strip() + if "=" in part: + k, v = part.split("=", 1) + result[k] = v + elif part: + result[part] = "" + return result + + +def set_style_value(style_str: str, key: str, value: str) -> str: + parts = [] + found = False + for part in style_str.split(";"): + part = part.strip() + if not part: + continue + if "=" in part: + k, v = part.split("=", 1) + if k == key: + parts.append(f"{key}={value}") + found = True + else: + parts.append(part) + else: + parts.append(part) + if not found: + parts.append(f"{key}={value}") + return ";".join(parts) + ";" + + +def get_geometry(cell: ET.Element) -> tuple[float, float, float, float] | None: + for geom in cell: + if geom.tag == "mxGeometry" and geom.get("as") == "geometry": + x = float(geom.get("x", "0")) + y = float(geom.get("y", "0")) + w = float(geom.get("width", "0")) + h = float(geom.get("height", "0")) + return (x, y, w, h) + return None + + +def offset_geometry(cell: ET.Element, dx: float, dy: float) -> None: + for geom in cell: + if geom.tag == "mxGeometry" and geom.get("as") == "geometry": + if geom.get("relative") == "1": + return # skip relative geometries (edge labels) + old_x = float(geom.get("x", "0")) + old_y = float(geom.get("y", "0")) + geom.set("x", str(round(old_x + dx, 1))) + geom.set("y", str(round(old_y + dy, 1))) + return + + +def is_region_container(cell: ET.Element) -> bool: + style = cell.get("style", "") + style_dict = get_style_dict(style) + return ( + "group_region" in style + and style_dict.get("container") == "1" + ) + + +def fix_nesting(tree: ET.ElementTree, verbose: bool = False) -> int: + root_elem = tree.getroot() + + cells: dict[str, ET.Element] = {} + for cell in root_elem.iter("mxCell"): + cid = cell.get("id") + if cid: + cells[cid] = cell + + fixed = 0 + + for cid, cell in list(cells.items()): + if not is_region_container(cell): + continue + + region_parent = cell.get("parent", "1") + region_geom = get_geometry(cell) + if region_geom is None: + continue + + rx, ry, rw, rh = region_geom + + if verbose: + print(f" Region {cid}: container=1 at ({rx},{ry}), parent={region_parent}") + + # Change region to container=0 + old_style = cell.get("style", "") + new_style = set_style_value(old_style, "container", "0") + cell.set("style", new_style) + + # Find all children of this region + children_moved = 0 + for child_id, child_cell in cells.items(): + if child_cell.get("parent") != cid: + continue + + # Re-parent to region's parent + child_cell.set("parent", region_parent) + + # Convert relative coordinates to absolute + if child_cell.get("edge") == "1": + # Edges don't need coordinate conversion + pass + else: + offset_geometry(child_cell, rx, ry) + + children_moved += 1 + + if verbose: + print(f" Changed to container=0, re-parented {children_moved} children (offset +{rx},+{ry})") + + fixed += 1 + + return fixed + + +def main() -> None: + parser = argparse.ArgumentParser( + description="Fix Region container nesting in draw.io files" + ) + parser.add_argument("file", help="Path to .drawio file") + parser.add_argument("--dry-run", action="store_true") + parser.add_argument("--verbose", "-v", action="store_true") + args = parser.parse_args() + + tree = ET.parse(args.file) + fixed = fix_nesting(tree, args.verbose) + + if fixed > 0: + print(f"Regions fixed: {fixed}") + if not args.dry_run: + ET.indent(tree, space=" ") + tree.write(args.file, encoding="unicode", xml_declaration=False) + print(f"Written: {args.file}") + else: + print("(dry run, no changes written)") + else: + print("No region nesting issues found") + + +if __name__ == "__main__": + main() diff --git a/plugins/deploy-on-aws/scripts/lib/fix_step_badges.py b/plugins/deploy-on-aws/scripts/lib/fix_step_badges.py new file mode 100644 index 00000000..0599a74d --- /dev/null +++ b/plugins/deploy-on-aws/scripts/lib/fix_step_badges.py @@ -0,0 +1,494 @@ +#!/usr/bin/env python3 +"""Post-processing script to fix step badge overlap in draw.io XML files. + +Improved algorithm (v2): +1. Parse .drawio XML +2. Identify on-diagram step badges and all obstacles (icons, containers, + edge labels, text cells, other badges) +3. Resolve absolute coordinates by walking parent chains +4. Multi-pass iterative solver: + a. For each badge with overlap, try a grid of candidate positions + at multiple distances (30, 60, 90, 120, 150px) in 16 directions + b. Score each candidate by minimum clearance to ALL obstacles + (including already-placed badges) + c. Pick the candidate with the best (highest) minimum clearance + d. After moving all badges, re-check — repeat up to 3 passes +5. Update badge mxGeometry (convert back to parent-relative coords) + +""" + +import argparse +import math +import re +import defusedxml.ElementTree as ET +from dataclasses import dataclass + + +@dataclass +class Rect: + x: float + y: float + w: float + h: float + + @property + def x2(self) -> float: + return self.x + self.w + + @property + def y2(self) -> float: + return self.y + self.h + + @property + def cx(self) -> float: + return self.x + self.w / 2 + + @property + def cy(self) -> float: + return self.y + self.h / 2 + + def overlaps(self, other: "Rect") -> bool: + return ( + self.x < other.x2 + and self.x2 > other.x + and self.y < other.y2 + and self.y2 > other.y + ) + + def min_clearance(self, other: "Rect") -> float: + """Minimum distance between edges. Negative if overlapping.""" + dx = max(other.x - self.x2, self.x - other.x2, 0) + dy = max(other.y - self.y2, self.y - other.y2, 0) + if dx == 0 and dy == 0: + ox = min(self.x2, other.x2) - max(self.x, other.x) + oy = min(self.y2, other.y2) - max(self.y, other.y) + return -min(ox, oy) + return (dx**2 + dy**2) ** 0.5 + + def expanded(self, margin: float) -> "Rect": + return Rect( + self.x - margin, + self.y - margin, + self.w + 2 * margin, + self.h + 2 * margin, + ) + + +def get_style_dict(style_str: str) -> dict[str, str]: + result: dict[str, str] = {} + if not style_str: + return result + for part in style_str.split(";"): + part = part.strip() + if "=" in part: + k, v = part.split("=", 1) + result[k] = v + elif part: + result[part] = "" + return result + + +def get_geometry(cell: ET.Element) -> Rect | None: + for geom in cell: + if geom.tag == "mxGeometry" and geom.get("as") == "geometry": + if geom.get("relative") == "1": + return None + x = float(geom.get("x", "0")) + y = float(geom.get("y", "0")) + w = float(geom.get("width", "0")) + h = float(geom.get("height", "0")) + return Rect(x, y, w, h) + return None + + +def resolve_edge_label_position( + cell: ET.Element, + cells: dict[str, ET.Element], + geom_cache: dict[str, Rect], +) -> Rect | None: + """Resolve an edge label's absolute position by finding the midpoint + of its parent edge's source and target, then applying offsets. + + Edge labels use relative geometry (position along the edge), which + can't be resolved by the normal parent-chain walk. Instead we: + 1. Find the parent edge's source and target cells + 2. Resolve source and target center points + 3. Interpolate along the edge based on the label's relative x + 4. Apply the perpendicular y offset + 5. Estimate label text width (~7px per char) for the bounding box + """ + parent_id = cell.get("parent", "1") + parent_cell = cells.get(parent_id) + if parent_cell is None or parent_cell.get("edge") != "1": + return None + + source_id = parent_cell.get("source") + target_id = parent_cell.get("target") + if not source_id or not target_id: + return None + + source_rect = resolve_absolute(source_id, cells, geom_cache) + target_rect = resolve_absolute(target_id, cells, geom_cache) + if source_rect is None or target_rect is None: + return None + + # Get label's relative position along edge + rel_x = 0.0 # default: midpoint + offset_y = 0.0 + for geom in cell: + if geom.tag == "mxGeometry": + rel_x = float(geom.get("x", "0")) + offset_y = float(geom.get("y", "0")) + break + + # Interpolate: rel_x of 0 = midpoint, -1 = source, +1 = target + t = 0.5 + rel_x * 0.5 # map [-1,1] -> [0,1] + cx = source_rect.cx + t * (target_rect.cx - source_rect.cx) + cy = source_rect.cy + t * (target_rect.cy - source_rect.cy) + offset_y + + # Estimate label dimensions from text content + value = cell.get("value", "") + text = re.sub(r"<[^>]+>", "", value).strip() + est_w = max(len(text) * 7, 40) # ~7px per char, min 40px + est_h = 16 # single line height + + label_rect = Rect(cx - est_w / 2, cy - est_h / 2, est_w, est_h) + return label_rect + + +def resolve_absolute( + cell_id: str, + cells: dict[str, ET.Element], + geom_cache: dict[str, Rect], +) -> Rect | None: + if cell_id in geom_cache: + return geom_cache[cell_id] + + cell = cells.get(cell_id) + if cell is None: + return None + + local = get_geometry(cell) + if local is None: + # Check if this is an edge label with relative geometry + style = cell.get("style", "") + if "edgeLabel" in style: + label_rect = resolve_edge_label_position(cell, cells, geom_cache) + if label_rect: + geom_cache[cell_id] = label_rect + return label_rect + return None + + parent_id = cell.get("parent", "1") + if parent_id in ("0", "1"): + geom_cache[cell_id] = local + return local + + parent_abs = resolve_absolute(parent_id, cells, geom_cache) + if parent_abs is None: + geom_cache[cell_id] = local + return local + + abs_rect = Rect( + parent_abs.x + local.x, + parent_abs.y + local.y, + local.w, + local.h, + ) + geom_cache[cell_id] = abs_rect + return abs_rect + + +def is_on_diagram_badge(cell: ET.Element) -> bool: + """On-diagram step badge: fillColor=#007CBD, numeric value, not in legend.""" + style = get_style_dict(cell.get("style", "")) + fill = style.get("fillColor", "").upper() + value = (cell.get("value") or "").strip() + cell_id = (cell.get("id") or "").lower() + parent_id = (cell.get("parent") or "").lower() + + if "legend" in cell_id or "legend" in parent_id: + return False + + if fill != "#007CBD": + return False + # Value may be plain "1" or HTML-wrapped like '1' + if re.match(r"^\d{1,2}$", value): + return True + stripped = re.sub(r"<[^>]+>", "", value).strip() + return bool(re.match(r"^\d{1,2}$", stripped)) + + +def classify_cell(cell: ET.Element) -> str: + """Classify a cell as 'badge', 'obstacle', or 'skip'.""" + cell_id = cell.get("id", "") + if cell_id in ("0", "1"): + return "skip" + + if is_on_diagram_badge(cell): + return "badge" + + style_str = cell.get("style", "") + style = get_style_dict(style_str) + + # Edges (not labels) — skip + if cell.get("edge") == "1": + return "skip" + + # Edge labels — skip (handled by model placement, not script). + # Badges and edge labels both annotate the same arrow, so they're + # inherently near each other. The model places them on opposite sides + # of the arrow. Script focuses on preventing badge-icon overlaps. + if "edgeLabel" in style_str: + return "skip" + + # Must be a vertex + if cell.get("vertex") != "1": + return "skip" + + geom = get_geometry(cell) + if geom is None or (geom.w == 0 and geom.h == 0): + return "skip" + + # Large architectural groups — skip (badges sit on these) + if "mxgraph.aws4.group" in style_str: + return "skip" + + # Legend panel backgrounds — skip + if "mxgraph.basic.rect" in style_str: + return "skip" + if "legend" in cell_id.lower(): + return "skip" + + # Invisible groups — skip + if style_str.startswith("group") or style.get("group") == "": + return "skip" + + # Line elements (title separator) — skip + if "line" in style_str.split(";") or style.get("line") == "": + return "skip" + + # Text cells — obstacle (badges shouldn't cover text) + if style_str.startswith("text;") or style.get("text") == "": + return "obstacle" + + # Everything else with geometry (icons, containers, etc.) — obstacle + return "obstacle" + + +def generate_candidates( + origin: Rect, + distances: list[float], + n_angles: int = 16, +) -> list[tuple[float, float]]: + """Generate candidate (dx, dy) offsets in a radial grid.""" + candidates: list[tuple[float, float]] = [] + for dist in distances: + for i in range(n_angles): + angle = 2 * math.pi * i / n_angles + dx = round(dist * math.cos(angle) / 10) * 10 # snap to 10px grid + dy = round(dist * math.sin(angle) / 10) * 10 + if (dx, dy) not in candidates: + candidates.append((dx, dy)) + return candidates + + +def compute_min_clearance( + badge_rect: Rect, + clearance: float, + obstacle_rects: list[Rect], +) -> float: + """Compute the minimum clearance between an expanded badge and all obstacles.""" + expanded = badge_rect.expanded(clearance) + min_c = float("inf") + for obs in obstacle_rects: + c = expanded.min_clearance(obs) + if c < min_c: + min_c = c + return min_c + + +def fix_badges( + tree: ET.ElementTree, + clearance: float = 10.0, + verbose: bool = False, +) -> int: + """Fix badge overlaps in-place. Returns number of badges moved.""" + root_elem = tree.getroot() + + cells: dict[str, ET.Element] = {} + for cell in root_elem.iter("mxCell"): + cid = cell.get("id") + if cid: + cells[cid] = cell + + # Classify all cells + badge_ids: list[str] = [] + obstacle_ids: list[str] = [] + + for cid, cell in cells.items(): + cls = classify_cell(cell) + if cls == "badge": + badge_ids.append(cid) + elif cls == "obstacle": + obstacle_ids.append(cid) + + if verbose: + print(f"Found {len(badge_ids)} badges, {len(obstacle_ids)} obstacles") + + # Semantic-aware placement: tight local search only. + # A badge at the right location with slight overlap is better than + # a badge at the wrong location with no overlap. + # Max 50px displacement keeps badges in their semantic zone (near their arrow). + distances = [10, 20, 30, 40, 50] + drift_penalty = 0.3 # penalize distance from original position + + total_moved = 0 + max_passes = 2 + + for pass_num in range(max_passes): + # Rebuild absolute positions from scratch each pass + geom_cache: dict[str, Rect] = {} + + # Resolve obstacle positions + obstacle_rects: list[Rect] = [] + for oid in obstacle_ids: + rect = resolve_absolute(oid, cells, geom_cache) + if rect: + obstacle_rects.append(rect) + + pass_moved = 0 + + for bid in badge_ids: + badge_abs = resolve_absolute(bid, cells, geom_cache) + if badge_abs is None: + continue + + # Build obstacle list = static obstacles + other badges (not self) + all_obstacles = list(obstacle_rects) + for other_bid in badge_ids: + if other_bid == bid: + continue + other_abs = resolve_absolute(other_bid, cells, geom_cache) + if other_abs: + all_obstacles.append(other_abs) + + # Check current clearance (with the tight clearance threshold) + current_clearance = compute_min_clearance( + badge_abs, clearance, all_obstacles + ) + + # Only move if actually overlapping (clearance < 0) + if current_clearance >= 0: + if verbose and pass_num == 0: + print( + f" Badge {bid} (value={cells[bid].get('value')}): " + f"OK (clearance={current_clearance:.0f})" + ) + continue + + # Generate local candidates (tight radius, stays near original) + candidates = generate_candidates(badge_abs, distances) + + best_dx, best_dy = 0.0, 0.0 + # Score = clearance - penalty * distance_from_origin + # This prefers closer positions even if clearance is slightly worse + current_score = current_clearance # distance=0 so no penalty + best_score = current_score + + for dx, dy in candidates: + candidate = Rect( + badge_abs.x + dx, + badge_abs.y + dy, + badge_abs.w, + badge_abs.h, + ) + c = compute_min_clearance(candidate, clearance, all_obstacles) + dist = (dx**2 + dy**2) ** 0.5 + score = c - drift_penalty * dist + + if score > best_score: + best_score = score + best_dx, best_dy = dx, dy + + if best_dx == 0 and best_dy == 0: + if verbose: + print( + f" Badge {bid} (value={cells[bid].get('value')}): " + f"no better position within 50px " + f"(clearance={current_clearance:.0f})" + ) + continue + + # Apply the move + cell = cells[bid] + for geom in cell: + if geom.tag == "mxGeometry" and geom.get("as") == "geometry": + old_x = float(geom.get("x", "0")) + old_y = float(geom.get("y", "0")) + geom.set("x", str(round(old_x + best_dx, 1))) + geom.set("y", str(round(old_y + best_dy, 1))) + break + + # Invalidate cache + if bid in geom_cache: + del geom_cache[bid] + + disp = (best_dx**2 + best_dy**2) ** 0.5 + new_clearance = best_score + drift_penalty * disp + pass_moved += 1 + if verbose: + print( + f" Badge {bid} (value={cells[bid].get('value')}): " + f"nudged ({best_dx:+.0f}, {best_dy:+.0f}), " + f"clearance {current_clearance:.0f} -> {new_clearance:.0f}, " + f"displacement {disp:.0f}px" + ) + + total_moved += pass_moved + if verbose and max_passes > 1: + print(f" Pass {pass_num + 1}: nudged {pass_moved} badges") + if pass_moved == 0: + break + + return total_moved + + +def main() -> None: + parser = argparse.ArgumentParser( + description="Fix step badge overlap in draw.io XML files" + ) + parser.add_argument("file", help="Path to .drawio file") + parser.add_argument( + "--clearance", + type=float, + default=10.0, + help="Minimum clearance in px around badges (default: 10)", + ) + parser.add_argument( + "--dry-run", + action="store_true", + help="Analyze but don't write changes", + ) + parser.add_argument( + "--verbose", "-v", + action="store_true", + help="Print detailed analysis", + ) + args = parser.parse_args() + + tree = ET.parse(args.file) + moved = fix_badges(tree, args.clearance, args.verbose) + + print(f"Badges moved: {moved}") + + if not args.dry_run and moved > 0: + ET.indent(tree, space=" ") + tree.write(args.file, encoding="unicode", xml_declaration=False) + print(f"Written: {args.file}") + elif args.dry_run and moved > 0: + print("(dry run, no changes written)") + + +if __name__ == "__main__": + main() diff --git a/plugins/deploy-on-aws/scripts/lib/post_process_drawio.py b/plugins/deploy-on-aws/scripts/lib/post_process_drawio.py new file mode 100644 index 00000000..3142ee2e --- /dev/null +++ b/plugins/deploy-on-aws/scripts/lib/post_process_drawio.py @@ -0,0 +1,396 @@ +#!/usr/bin/env python3 +"""Unified post-processing pipeline for draw.io AWS architecture diagrams. + +Chains all fixers in sequence: +1. fix_nesting — flatten Region container=1 to container=0 (fixes edge routing) +2. fix_step_badges — nudge overlapping step badges +3. fix_placement — move external actors below title block +4. fix_legend_size — resize legend panel to match diagram height + +Reads JSON from stdin (PostToolUse hook format) or accepts file path as argument. +""" + +import argparse +import importlib.util +import json +import os +import sys +import defusedxml.ElementTree as ET +from pathlib import Path + +MAX_FILE_SIZE = 2 * 1024 * 1024 # 2 MB + +# Import sibling modules by explicit file path (avoids sys.path manipulation +# that could allow module shadowing — see CWE-426) +SCRIPT_DIR = Path(__file__).parent + + +def _import_from(module_name: str, file_name: str): + spec = importlib.util.spec_from_file_location(module_name, SCRIPT_DIR / file_name) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_fix_icon_colors = _import_from("fix_icon_colors", "fix_icon_colors.py") +_fix_nesting = _import_from("fix_nesting", "fix_nesting.py") +_fix_step_badges = _import_from("fix_step_badges", "fix_step_badges.py") + +fix_icon_colors = _fix_icon_colors.fix_icon_colors +fix_nesting = _fix_nesting.fix_nesting +fix_badges = _fix_step_badges.fix_badges + + +def get_style_dict(style_str: str) -> dict[str, str]: + result: dict[str, str] = {} + if not style_str: + return result + for part in style_str.split(";"): + part = part.strip() + if "=" in part: + k, v = part.split("=", 1) + result[k] = v + elif part: + result[part] = "" + return result + + +def get_geometry(cell: ET.Element) -> dict[str, float] | None: + for geom in cell: + if geom.tag == "mxGeometry" and geom.get("as") == "geometry": + if geom.get("relative") == "1": + return None + return { + "x": float(geom.get("x", "0")), + "y": float(geom.get("y", "0")), + "w": float(geom.get("width", "0")), + "h": float(geom.get("height", "0")), + } + return None + + +def set_geometry(cell: ET.Element, **kwargs: float) -> None: + for geom in cell: + if geom.tag == "mxGeometry" and geom.get("as") == "geometry": + for k, v in kwargs.items(): + attr = {"x": "x", "y": "y", "w": "width", "h": "height"}[k] + geom.set(attr, str(round(v, 1))) + return + + +def fix_placement(tree: ET.ElementTree, verbose: bool = False) -> int: + """Move external actors outside the AWS Cloud boundary. + + External actors must be: + 1. Below the title block (y >= 140) + 2. Horizontally outside the AWS Cloud group rectangle + + If an external actor is horizontally inside AWS Cloud, move it to + x = aws_cloud_x - actor_width - 30 (30px gap left of cloud). + + Returns number of cells moved. + """ + root_elem = tree.getroot() + moved = 0 + title_bottom = 140 + + # Find the AWS Cloud group geometry + aws_cloud_geom = None + for cell in root_elem.iter("mxCell"): + style = cell.get("style", "") + if "group_aws_cloud" in style: + g = get_geometry(cell) + if g: + aws_cloud_geom = g + break + + min_y = title_bottom + if aws_cloud_geom is not None: + min_y = max(title_bottom, aws_cloud_geom["y"]) + + for cell in root_elem.iter("mxCell"): + cid = cell.get("id", "") + parent = cell.get("parent", "") + style = cell.get("style", "") + + # Only check cells at root level (parent="1") + if parent != "1": + continue + + # Skip non-vertices + if cell.get("vertex") != "1": + continue + + # Skip AWS groups, title elements, legend, badges, text + skip_patterns = [ + "mxgraph.aws4.group", + "group;", + "text;", + "line;", + "mxgraph.basic.rect", + "edgeLabel", + ] + if any(p in style for p in skip_patterns): + continue + + # Skip legend-related + if "legend" in cid.lower(): + continue + + # Skip step badges + if "step-" in cid.lower() and "fillColor=#007CBD" in style: + continue + + # Skip service containers (svc-group-*) — these are AWS services, not external actors + # After fix_nesting re-parents region children, services may end up at parent="1" + if cid.lower().startswith("svc-"): + continue + + # Skip cells with AWS service category stroke colors (service containers) + svc_stroke_colors = ["#ED7100", "#C925D1", "#8C4FFF", "#3F8624", "#E7157B", + "#DD344C", "#01A88D", "#1A9C37", "#DE227B"] + style_upper = style.upper() + if any(f"STROKECOLOR={c.upper()}" in style_upper.replace(" ", "") for c in svc_stroke_colors): + continue + + # Skip cells with resIcon for AWS services (not external actors) + if "resIcon=mxgraph.aws4." in style: + # Only external actor icons should be eligible + external_icons = ["users", "mobile_client", "iot_sensor", "iot_thing", + "internet", "traditional_server", "generic_database", "client"] + has_external_icon = any(f"resIcon=mxgraph.aws4.{icon}" in style for icon in external_icons) + if not has_external_icon: + continue + + # Check if this looks like an external actor container + is_external = False + value = (cell.get("value") or "").lower() + if any(kw in value for kw in ["user", "client", "mobile", "on-prem", "sensor", "iot device"]): + is_external = True + if any(kw in cid.lower() for kw in ["user", "client", "mobile", "sensor", "external", "onprem"]): + is_external = True + if "resIcon=mxgraph.aws4.users" in style or "resIcon=mxgraph.aws4.mobile_client" in style: + is_external = True + if "resIcon=mxgraph.aws4.iot_sensor" in style or "resIcon=mxgraph.aws4.iot_thing" in style: + is_external = True + if "fillColor=#f5f5f5" in style.lower() or "fillColor=light-dark(#F5F5F5" in style: + is_external = True + + if not is_external: + continue + + g = get_geometry(cell) + if g is None: + continue + + changed = False + + # Fix 1: Move below title block + if g["y"] < min_y: + old_y = g["y"] + set_geometry(cell, y=min_y) + g["y"] = min_y + changed = True + if verbose: + print(f" Moved {cid} down: y={old_y:.0f} -> y={min_y:.0f}") + + # Fix 2: Move horizontally outside AWS Cloud if overlapping + if aws_cloud_geom is not None: + cloud_x = aws_cloud_geom["x"] + cloud_x2 = cloud_x + aws_cloud_geom["w"] + actor_x = g["x"] + actor_x2 = actor_x + g["w"] + + # Check if actor overlaps AWS Cloud horizontally + if actor_x >= cloud_x and actor_x < cloud_x2: + # Actor starts inside cloud — move to left of cloud + new_x = cloud_x - g["w"] - 30 + if new_x < 20: + new_x = 20 # minimum left margin + set_geometry(cell, x=new_x) + changed = True + if verbose: + print(f" Moved {cid} left: x={actor_x:.0f} -> x={new_x:.0f} (outside AWS Cloud)") + + if changed: + moved += 1 + + return moved + + +def fix_legend_size(tree: ET.ElementTree, verbose: bool = False) -> int: + """Resize legend panel to match the diagram's main content height. + + Finds the legend-outer group and the AWS Cloud / Region group, + then sets legend height to span from the legend's y to the diagram bottom. + + Returns 1 if resized, 0 if no change needed. + """ + root_elem = tree.getroot() + + # Find legend-outer or legend-bg + legend_outer = None + legend_bg = None + for cell in root_elem.iter("mxCell"): + cid = cell.get("id", "") + if cid == "legend-outer": + legend_outer = cell + elif cid == "legend-bg": + legend_bg = cell + + if legend_outer is None: + return 0 + + legend_geom = get_geometry(legend_outer) + if legend_geom is None: + return 0 + + # Find the diagram's main content bottom (AWS Cloud or Region group) + diagram_bottom = 0 + for cell in root_elem.iter("mxCell"): + parent = cell.get("parent", "") + + # Look at root-level elements (parent="1") that are large groups + if parent == "1" and cell.get("vertex") == "1": + g = get_geometry(cell) + if g and g["h"] > 200: # large elements only + bottom = g["y"] + g["h"] + if bottom > diagram_bottom: + diagram_bottom = bottom + + if diagram_bottom <= 0: + return 0 + + # Target: legend spans from its y to diagram_bottom + some padding + target_height = diagram_bottom - legend_geom["y"] + 20 # 20px padding + current_height = legend_geom["h"] + + # Only resize if significantly different (>50px) + if abs(target_height - current_height) < 50: + return 0 + + if verbose: + print(f" Legend resize: {current_height:.0f} -> {target_height:.0f}") + + set_geometry(legend_outer, h=target_height) + + # Also resize legend-bg to match + if legend_bg is not None: + set_geometry(legend_bg, h=target_height) + + return 1 + + +def main() -> None: + parser = argparse.ArgumentParser( + description="Unified post-processing for draw.io diagrams" + ) + parser.add_argument("file", nargs="?", help="Path to .drawio file (or reads JSON from stdin)") + parser.add_argument("--verbose", "-v", action="store_true") + parser.add_argument("--dry-run", action="store_true") + args = parser.parse_args() + + # Determine file path: from argument or stdin JSON (PostToolUse hook) + file_path = args.file + if file_path is None: + try: + data = json.load(sys.stdin) + file_path = data.get("tool_input", {}).get("file_path", "") + except (json.JSONDecodeError, KeyError): + pass # Expected when not invoked as PostToolUse hook (CLI mode) + + if not file_path or not file_path.endswith((".drawio", ".drawio.xml")): + # Not a drawio file, exit silently (hook compatibility) + sys.exit(0) + + # Reject symlinks BEFORE realpath() — realpath follows symlinks, so + # is_symlink() would always be False on the resolved path. + if os.path.islink(file_path): + print(f"Refusing to process symlink: {file_path}", file=sys.stderr) + sys.exit(1) + + # Resolve path to canonical absolute form to prevent path traversal + # (e.g., "../../etc/passwd.drawio") from hook input — see CWE-22. + # All file operations below use only the resolved `path` object. + # Uses os.path.realpath() as the semgrep-recognized taint sanitizer. + path = Path(os.path.realpath(file_path)) + + # Re-validate extension after resolution (traversal could change it) + if not path.suffix == ".drawio" and not str(path).endswith(".drawio.xml"): + sys.exit(0) + + # Reject files exceeding size limit before parsing + try: + file_size = path.stat().st_size + except OSError as e: + print(f"Cannot stat file: {e}", file=sys.stderr) + sys.exit(1) + if file_size > MAX_FILE_SIZE: + print( + f"Skipping: file too large ({file_size // 1024}KB > " + f"{MAX_FILE_SIZE // 1024 // 1024}MB limit)", + file=sys.stderr, + ) + sys.exit(0) + + try: + tree = ET.parse(path) + except (ET.ParseError, FileNotFoundError) as e: + print(f"Error parsing file: {e}", file=sys.stderr) + sys.exit(1) + + # Top-level try/except prevents unhandled exception tracebacks from + # leaking file paths and source code lines into the hook systemMessage + # (stderr is captured via 2>&1 in validate-drawio.sh). + try: + changes = [] + + # 0. Fix Region container nesting (MUST run first — changes coordinates) + regions_fixed = fix_nesting(tree, verbose=args.verbose) + if regions_fixed > 0: + changes.append(f"nesting: {regions_fixed} regions flattened") + + # 1. Fix icon fill colors (before badge/layout fixes) + icons_fixed = fix_icon_colors(tree, verbose=args.verbose) + if icons_fixed > 0: + changes.append(f"icons: {icons_fixed} colors corrected") + + # 2. Fix step badge overlaps (15px clearance for visual breathing room) + badges_moved = fix_badges(tree, clearance=15.0, verbose=args.verbose) + if badges_moved > 0: + changes.append(f"badges: {badges_moved} moved") + + # 3. Fix external actor placement (below title + outside AWS Cloud) + actors_moved = fix_placement(tree, verbose=args.verbose) + if actors_moved > 0: + changes.append(f"placement: {actors_moved} actors repositioned") + + # 4. Fix legend panel sizing (match diagram height) + legend_resized = fix_legend_size(tree, verbose=args.verbose) + if legend_resized > 0: + changes.append("legend: resized to match diagram height") + + if changes: + summary = "; ".join(changes) + print(f"Post-processing: {summary}") + if not args.dry_run: + # Note: XML indentation skipped — defusedxml doesn't expose indent() + # and importing stdlib xml.etree.ElementTree triggers security scanners. + # Output is valid but not pretty-printed. If human-readable XML is needed, + # add a custom indent helper that walks the element tree without stdlib import. + tree.write(path, encoding="unicode", xml_declaration=False) + print(f"Written: {path}") + else: + print("(dry run, no changes written)") + else: + print("Post-processing: no changes needed") + except Exception: + # Generic message only — do not include exception details or tracebacks, + # as they would leak internal file paths and source lines into the agent + # context via the hook's systemMessage. + print("Post-processing: internal error during fixers. Run manually for details.") + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/plugins/deploy-on-aws/scripts/lib/validate_drawio.py b/plugins/deploy-on-aws/scripts/lib/validate_drawio.py new file mode 100755 index 00000000..b74d44d9 --- /dev/null +++ b/plugins/deploy-on-aws/scripts/lib/validate_drawio.py @@ -0,0 +1,296 @@ +#!/usr/bin/env python3 +""" +validate_drawio.py - Validates draw.io XML files for: +1. XML well-formedness +2. Correct draw.io structure (mxfile > diagram > mxGraphModel > root) +3. Valid AWS4 shape references +4. Edge integrity (source/target reference valid cell IDs) +5. Geometry validation (vertices have mxGeometry) +""" + +import io +import json +import os +import re +import sys +import defusedxml.ElementTree as ET +from pathlib import Path + +MAX_FILE_SIZE = 2 * 1024 * 1024 # 2 MB +MAX_XML_DEPTH = 50 +MAX_XML_ELEMENTS = 50_000 + +# Load valid AWS4 shapes +SCRIPT_DIR = Path(__file__).parent +shapes_data = json.loads((SCRIPT_DIR / "aws4-shapes.json").read_text()) + +valid_shapes = set() +for category in shapes_data["categories"].values(): + for shape in category["shapes"]: + valid_shapes.add(shape) + + +def _sanitize_attr(value: str, max_len: int = 80) -> str: + """Sanitize an XML attribute value for safe inclusion in error messages. + + Strips non-printable characters, collapses whitespace, truncates, and + restricts to safe characters to prevent prompt injection through crafted + draw.io attributes that flow into agent systemMessage context. + """ + if not value: + return "(empty)" + sanitized = re.sub(r"[\x00-\x1f\x7f-\x9f]", "", value) + sanitized = re.sub(r"\s+", " ", sanitized).strip() + sanitized = re.sub(r"[^\w\-.:# ]", "_", sanitized) + if len(sanitized) > max_len: + sanitized = sanitized[:max_len] + "...(truncated)" + return sanitized + + +def _check_xml_limits(xml_text: str) -> str | None: + """Pre-flight check for element depth and count using streaming parse. + + Uses iterparse to process elements one at a time without building the + full tree in memory, avoiding C-stack overflow on deeply nested files. + Returns an error message string, or None if within limits. + """ + depth = 0 + count = 0 + for event, _ in ET.iterparse( + io.BytesIO(xml_text.encode("utf-8")), events=("start", "end") + ): + if event == "start": + depth += 1 + count += 1 + if depth > MAX_XML_DEPTH: + return f"XML nesting depth exceeds {MAX_XML_DEPTH} levels" + if count > MAX_XML_ELEMENTS: + return f"XML element count exceeds {MAX_XML_ELEMENTS:,}" + else: + depth -= 1 + return None + + +def validate(file_path): + errors = [] + warnings = [] + + # 1. Read file — resolve to canonical absolute path to prevent path + # traversal (e.g., "../../etc/passwd.drawio") from hook input (CWE-22). + # Uses os.path.realpath() as the semgrep-recognized taint sanitizer. + path = Path(os.path.realpath(file_path)) + try: + file_size = path.stat().st_size + except OSError as e: + errors.append(f"Cannot stat file: {e}") + return errors, warnings + + if file_size > MAX_FILE_SIZE: + errors.append( + f"File too large ({file_size // 1024}KB > " + f"{MAX_FILE_SIZE // 1024 // 1024}MB limit)" + ) + return errors, warnings + + try: + xml_text = path.read_text(encoding="utf-8") + except Exception as e: + errors.append(f"Cannot read file: {e}") + return errors, warnings + + if not xml_text.strip(): + errors.append("File is empty") + return errors, warnings + + # Pre-flight: check depth and element count before full parse + limit_error = _check_xml_limits(xml_text) + if limit_error: + errors.append(limit_error) + return errors, warnings + + # Parse XML + try: + root = ET.fromstring(xml_text) + except ET.ParseError as e: + errors.append(f"XML parse error: {e}") + return errors, warnings + + # 2. Validate structure - require wrapper per SKILL.md contract + tag = root.tag + if tag == "mxfile": + diagrams = root.findall("diagram") + if not diagrams: + errors.append("Missing element inside .") + return errors, warnings + + models = root.findall(".//mxGraphModel") + if not models: + # Check for compressed content + for diag in diagrams: + content = (diag.text or "").strip() + if content and not content.startswith("<"): + name = diag.get("name", str(diagrams.index(diag))) + warnings.append( + f'Diagram "{name}" appears to be compressed. ' + "For full validation, use uncompressed XML format." + ) + if warnings: + return errors, warnings + errors.append("Missing element.") + return errors, warnings + + diagram_count = len(diagrams) + + elif tag == "mxGraphModel": + errors.append( + "Missing wrapper. Output must use " + " structure." + ) + return errors, warnings + else: + errors.append( + "Missing root element. draw.io files must start with ." + ) + return errors, warnings + + # 3. Validate cells + cells = root.iter("mxCell") if tag == "mxGraphModel" else root.findall(".//mxCell") + cells = list(cells) + + cell_ids = set() + has_root_cell = False + has_default_layer = False + aws_shapes_used = [] + invalid_shapes = [] + + for cell in cells: + cell_id = cell.get("id") + style = cell.get("style", "") + is_vertex = cell.get("vertex") == "1" + + if cell_id: + cell_ids.add(cell_id) + if cell_id == "0": + has_root_cell = True + if cell_id == "1" and cell.get("parent") == "0": + has_default_layer = True + + # Check AWS4 shapes + shape_match = re.search(r"shape=mxgraph\.aws4\.(\w+)", style) + if shape_match: + shape_name = shape_match.group(1) + aws_shapes_used.append(shape_name) + if shape_name not in valid_shapes: + invalid_shapes.append(shape_name) + + # Check resIcon references + res_icon_match = re.search(r"resIcon=mxgraph\.aws4\.(\w+)", style) + if res_icon_match: + res_icon_name = res_icon_match.group(1) + if res_icon_name not in valid_shapes: + invalid_shapes.append(f"resIcon:{res_icon_name}") + + # 5. Geometry validation for vertices + if is_vertex: + geometries = cell.findall("mxGeometry") + if not geometries: + warnings.append(f'Vertex cell id="{_sanitize_attr(cell_id)}" is missing .') + + if not has_root_cell: + errors.append('Missing root cell (mxCell id="0").') + if not has_default_layer: + errors.append('Missing default layer cell (mxCell id="1" parent="0").') + + # 4. Edge integrity + for cell in cells: + if cell.get("edge") == "1": + source = cell.get("source") + target = cell.get("target") + if source and source not in cell_ids: + errors.append( + f'Edge id="{_sanitize_attr(cell.get("id"))}" references ' + f'non-existent source="{_sanitize_attr(source)}".' + ) + if target and target not in cell_ids: + errors.append( + f'Edge id="{_sanitize_attr(cell.get("id"))}" references ' + f'non-existent target="{_sanitize_attr(target)}".' + ) + + # Report invalid AWS shapes + if invalid_shapes: + unique_invalid = list(dict.fromkeys(invalid_shapes)) + errors.append( + f"Invalid AWS4 shape references: {', '.join(unique_invalid)}. " + "Check the aws4-shapes.json registry for valid shape names." + ) + + # --- AWS Diagram Guideline Compliance Checks --- + + # 1. Background color check — no hardcoded background allowed (breaks dark mode) + graph_models = [root] if tag == "mxGraphModel" else root.findall(".//mxGraphModel") + for model in graph_models: + bg = model.get("background") + if bg is not None: + errors.append( + f'mxGraphModel has background="{_sanitize_attr(bg)}". ' + "Do not set a background attribute — it breaks dark mode adaptive contrast." + ) + break + + # 2. Font size check — minimum 10px per style guide (service labels use 10px) + has_small_font = False + for cell in cells: + style = cell.get("style", "") + if cell.get("vertex") == "1": + fs_match = re.search(r"fontSize=(\d+)", style) + if fs_match and int(fs_match.group(1)) < 10: + has_small_font = True + break + if has_small_font: + warnings.append( + "Some shapes use fontSize below 10px. " + "Minimum per style guide: 10px for service labels." + ) + + # Summary info + if aws_shapes_used: + unique_count = len(set(aws_shapes_used)) + warnings.append( + f"AWS4 shapes used: {unique_count} unique shapes across {diagram_count} diagram(s)." + ) + + return errors, warnings + + +def main(): + if len(sys.argv) < 2: + print("Usage: validate_drawio.py ", file=sys.stderr) + sys.exit(1) + + file_path = sys.argv[1] + + # Top-level try/except prevents unhandled exception tracebacks from + # leaking file paths and source code lines into the hook systemMessage + # (stderr is captured via 2>&1 in validate-drawio.sh). + try: + errors, warnings = validate(file_path) + except Exception: + print("VALIDATION FAILED: internal error during validation. Run manually for details.") + sys.exit(1) + + if errors: + print(f"VALIDATION FAILED for {file_path}:") + for e in errors: + print(f" ERROR: {e}") + if warnings: + for w in warnings: + print(f" INFO: {w}") + if not errors: + print(f"VALIDATION PASSED for {file_path}.") + + sys.exit(1 if errors else 0) + + +if __name__ == "__main__": + main() diff --git a/plugins/deploy-on-aws/scripts/requirements.txt b/plugins/deploy-on-aws/scripts/requirements.txt new file mode 100644 index 00000000..c19d53ed --- /dev/null +++ b/plugins/deploy-on-aws/scripts/requirements.txt @@ -0,0 +1 @@ +defusedxml>=0.7.1 diff --git a/plugins/deploy-on-aws/scripts/validate-drawio.sh b/plugins/deploy-on-aws/scripts/validate-drawio.sh new file mode 100755 index 00000000..26307f08 --- /dev/null +++ b/plugins/deploy-on-aws/scripts/validate-drawio.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash +# validate-drawio.sh - PostToolUse hook for validating draw.io XML files +# Receives JSON on stdin with tool_input.file_path +# Outputs JSON with systemMessage field +# After validation passes, generates a draw.io URL for instant browser preview + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Ensure defusedxml is available (required for safe XML parsing) +# See scripts/requirements.txt or plugin README for installation instructions +python3 -c "import defusedxml" 2>/dev/null || { + echo '{"systemMessage": "Missing required dependency: defusedxml. Install it with: pip3 install defusedxml>=0.7.1"}' + exit 0 +} + +# Read stdin (hook input JSON) +INPUT=$(cat) + +# Extract file path from the hook input +FILE_PATH=$(echo "$INPUT" | python3 -c " +import sys, json +try: + data = json.load(sys.stdin) + path = data.get('tool_input', {}).get('file_path', '') + print(path) +except (json.JSONDecodeError, KeyError, TypeError, ValueError): + print('') +" 2>/dev/null || echo "") + +# Only validate .drawio or .drawio.xml files +if [[ -z "$FILE_PATH" ]]; then + exit 0 +fi + +if [[ ! "$FILE_PATH" =~ \.(drawio|drawio\.xml)$ ]]; then + exit 0 +fi + +if [[ ! -f "$FILE_PATH" ]]; then + exit 0 +fi + +# Step 0: Run post-processing fixers BEFORE validation +# This fixes badge overlaps, external actor placement, and legend sizing +# timeout prevents runaway processes from blocking the hook indefinitely +POST_RESULT=$(timeout 10 python3 "$SCRIPT_DIR/lib/post_process_drawio.py" "$FILE_PATH" 2>&1) || true + +# Step 1: Run the Python validator on the post-processed file +VALIDATE_RESULT=$(timeout 10 python3 "$SCRIPT_DIR/lib/validate_drawio.py" "$FILE_PATH" 2>&1) || true +VALIDATION_PASSED=false +if echo "$VALIDATE_RESULT" | grep -q "VALIDATION PASSED"; then + VALIDATION_PASSED=true +fi + +# Step 2: Only generate draw.io preview URL AFTER validation passes +URL_RESULT="" +if [[ "$VALIDATION_PASSED" == "true" ]]; then + URL_RESULT=$(timeout 5 python3 "$SCRIPT_DIR/lib/drawio_url.py" "$FILE_PATH" 2>/dev/null) || true +fi + +# Build the response message +FULL_RESULT="" +if [[ -n "$POST_RESULT" ]] && [[ "$POST_RESULT" != *"no changes needed"* ]]; then + FULL_RESULT="POST-PROCESSING: ${POST_RESULT} +" +fi +FULL_RESULT="${FULL_RESULT}${VALIDATE_RESULT}" +if [[ -n "$URL_RESULT" ]]; then + FULL_RESULT="${FULL_RESULT} +PREVIEW URL: ${URL_RESULT}" +fi + +if [[ -n "$FULL_RESULT" ]]; then + ESCAPED=$(echo "$FULL_RESULT" | python3 -c "import sys,json; print(json.dumps(sys.stdin.read().strip()))" 2>/dev/null) + if [[ -z "$ESCAPED" ]]; then + ESCAPED='"draw.io validation completed but output encoding failed. Run validator manually for details."' + fi + echo "{\"systemMessage\": $ESCAPED}" +else + echo '{"systemMessage": "draw.io XML validation passed. All AWS shapes are valid."}' +fi + +exit 0 diff --git a/plugins/deploy-on-aws/skills/aws-architecture-diagram/SKILL.md b/plugins/deploy-on-aws/skills/aws-architecture-diagram/SKILL.md new file mode 100644 index 00000000..2f04c969 --- /dev/null +++ b/plugins/deploy-on-aws/skills/aws-architecture-diagram/SKILL.md @@ -0,0 +1,239 @@ +--- +name: aws-architecture-diagram +description: "Generate validated AWS architecture diagrams as draw.io XML using official AWS4 icon libraries. Use this skill whenever the user wants to create, generate, or design AWS architecture diagrams, cloud infrastructure diagrams, or system design visuals. Also triggers for requests to visualize existing infrastructure from CloudFormation, CDK, or Terraform code. Supports two modes: analyze an existing codebase to auto-generate diagrams, or brainstorm interactively from scratch. Exports .drawio files with optional PNG/SVG/PDF export via draw.io desktop CLI." +argument-hint: "[describe your architecture or say 'analyze' to scan codebase]" +allowed-tools: Bash, Write, Read, Glob, Grep +user-invocable: true +--- + +You are an AWS architecture diagram generator that produces draw.io XML files with official AWS4 icons. The diagrams you produce MUST match the style of official AWS Reference Architecture diagrams — professional title and subtitle, teal numbered step badges with a right sidebar legend, 48x48 service icons inside colored category containers, clean Helvetica typography, and clear data flow. + +## Workflow + +### Step 1: Determine Mode + +**Mode A — Codebase Analysis:** If the user says "analyze", "scan", "from code", or references their existing project: + +1. Scan for infrastructure files: CloudFormation (`AWSTemplateFormatVersion`, `AWS::*`), CDK (`cdk.json`, construct definitions), Terraform (`resource "aws_*"`) +2. Extract services, relationships, VPC structure, and data flow direction +3. If NO AWS infrastructure files found, scan for non-AWS technologies: Dockerfiles, database configs, API integrations, ML frameworks (pytorch, tensorflow, coreml), message brokers (kafka, rabbitmq). Map discovered technologies using `references/general-icons.md` +4. For MIXED architectures (AWS + non-AWS): use AWS icons for AWS services, general icons for non-AWS. Same layout rules apply. +5. Confirm discovered architecture with user before generating +6. Ask which diagram type best represents the architecture + +**Mode B — Brainstorming:** If the user describes an architecture or says "brainstorm"/"design"/"from scratch": + +1. Ask 3-5 focused questions (purpose, services, scale, security, traffic pattern) +2. Propose the architecture with service recommendations and data flow +3. Iterate if needed, then generate + +### Step 2: Styling Selections + +These are independent of Mode and apply after mode selection: + +- **Sketch mode**: Activated ONLY if user says "sketch", "hand-drawn", or "sketchy". Default: OFF (Helvetica, no sketch attributes). See Sketch Mode in Style Rules below. +- **Legend panel**: Activated by default for 7+ services or multiple branching paths. Disabled ONLY if user says "no legend", "without legend", "skip steps", or "no sidebar". +- **Export format**: Check for format keywords (png, svg, pdf). Default: `.drawio` only. + +### Step 3: Generate Diagram XML + +**Load references now** (not before this step): + +1. Read `references/xml-rules.md` for shape styles, label placement, and structural rules +2. Read `references/style-guide.md` for colors, fonts, and dark mode +3. Read `references/xml-templates-structure.md` for XML code blocks +4. Read `references/layout-guidelines.md` for spacing and edge routing +5. Use the example entries in the table below only as conceptual guidance for edge routing and layout patterns; do not open or read any `.drawio` files as reference. + +**Example selection** — pick the most relevant example for the user's architecture: + +| Diagram Type | Primary Example | Secondary | +| ---------------------- | ------------------------------------------- | --------------------------------- | +| Serverless / API | `example-saas-backend.drawio` | `example-event-driven.drawio` | +| Event-driven / async | `example-event-driven.drawio` | `example-microservices.drawio` | +| Microservices / ECS | `example-microservices.drawio` | `example-complex-platform.drawio` | +| Multi-region | `example-multi-region-active-active.drawio` | — | +| Complex (13+ services) | `example-complex-platform.drawio` | `example-saas-backend.drawio` | +| AI / AgentCore | `example-agentcore.drawio` | `example-event-driven.drawio` | +| Sketch mode | `example-sketch.drawio` | + one from above | + +1. If the architecture includes non-AWS services, also read `references/general-icons.md` +2. Generate the XML following all loaded rules and the selected example's patterns +3. Apply styling selections from Step 2 + +### Step 4: Validate and Export + +1. Write the `.drawio` file to `./docs/` +2. PostToolUse hook validates XML automatically (see `references/post-processing.md` for the fixer pipeline) +3. If validation fails, fix errors and rewrite +4. Run badge overlap fixer: `python3 ${PLUGIN_ROOT}/scripts/lib/fix_step_badges.py ./docs/.drawio` +5. After validation passes, generate preview URL: + + ```bash + python3 ${PLUGIN_ROOT}/scripts/lib/drawio_url.py ./docs/.drawio --open + ``` + +6. If export format requested, run draw.io CLI (see `references/cli-export.md`) + +## Defaults + +- **Mode**: Brainstorm (if no codebase context) +- **Font**: `fontFamily=Helvetica` (Comic Sans MS only in sketch mode) +- **Icon size**: 48x48 inside 120x120 containers +- **Spacing**: 180px horizontal, 120px vertical between service group containers +- **Legend**: ALWAYS for 7+ services (unless user opts out) +- **Sketch mode**: OFF (unless user explicitly requests) +- **Dark mode**: `light-dark()` on all structural elements (always enabled) +- **Export format**: `.drawio` (unless user requests png/svg/pdf) +- **Grid**: OFF (`grid=0`) +- **File location**: `./docs/` directory +- **XML format**: Uncompressed, wrapped in `` + +## Error Handling + +- **XML validation failure**: Fix reported errors (malformed tags, missing IDs, invalid shapes), rewrite the file, re-validate +- **Shape not found**: Check `references/aws4-shapes-services.md` for valid `mxgraph.aws4.*` names +- **draw.io CLI not found**: Write `.drawio` file only, skip export, inform user to install draw.io desktop +- **Invalid edge source/target**: Verify all `source=` and `target=` IDs reference existing `mxCell` elements +- **Double hyphens in XML comments**: `--` is illegal inside `` per XML spec; use single hyphens or rephrase +- **Special characters**: Escape `&`, `<`, `>`, `"` in attribute values + +## Style Rules + +Full style details in `references/style-guide.md`. Critical rules that MUST be followed: + +- **Font**: ALL text MUST use `fontFamily=Helvetica;` (Comic Sans MS only in sketch mode) +- **Dark mode**: ALL structural elements MUST use `light-dark()` fills with `fillStyle=auto;`. See style-guide.md for the full color table. +- **Region groups**: MUST use `container=0` (decoration-only). Services use `parent="aws-cloud"` with absolute coords. +- **Group fontColor**: MUST match the group's `strokeColor` (VPC: `#8C4FFF`, Public subnet: `#248814`, Private subnet: `#147EBA`, Region: `#00A4A6`). NEVER use `fontColor=#AAB7B8`. +- **Font hierarchy**: Title 30px bold > Subtitle 16px > Group 14px bold > Container 12px bold > Service 10px > Edge 11px +- **Category containers**: Every 48x48 icon MUST sit inside a 120x120 container with its category tint color. See style-guide.md for the tint color table. +- **AgentCore**: Use `resIcon=mxgraph.aws4.bedrock_agentcore` (NOT `mxgraph.aws4.bedrock`) +- **Sketch mode**: Only when user requests it. Add `sketch=1;curveFitting=1;jiggle=2` to non-icon elements. Keep `sketch=0` on service icons. +- **Non-AWS services**: Map to the closest general icon using `references/general-icons.md`. Same 120x120 container + 48x48 icon pattern. Apply category tint colors by functional role (database, compute, etc.). Labels are critical since icons are generic. + +## Diagram Types + +- **VPC/Network**: VPC, subnets, security groups, NAT gateways, load balancers with group shapes +- **Serverless**: API Gateway, Lambda, DynamoDB, S3, Step Functions, EventBridge +- **Multi-Region**: Multiple regions with replication, Route 53, Global Accelerator +- **CI/CD Pipeline**: CodeCommit/GitHub -> CodeBuild -> CodeDeploy -> targets +- **Data Flow/Analytics**: Kinesis, S3, Glue, Athena, Redshift, QuickSight pipelines +- **Container**: ECS/EKS clusters, ECR, Fargate, load balancing +- **Hybrid**: On-premises + AWS with Direct Connect, VPN, Transit Gateway + +See `references/diagram-templates-basic.md` and `references/diagram-templates-advanced.md` for layout patterns. + +## XML Generation Rules + +For detailed XML templates, style strings, and code examples, see `references/xml-rules.md`. Key structural rules: + +### Required Structure + +Always use the full `mxfile` wrapper: + +```xml + + + + + + + + + + + +``` + +- Cell `id="0"` is the root layer; cell `id="1"` is the default parent (both always required) +- All diagram elements use `parent="1"` unless nested inside a container +- Use descriptive cell IDs: `vpc-1`, `lambda-orders`, `s3-assets`, `edge-lambda-to-dynamo` + +### Key Principles + +- ALWAYS use `mxgraph.aws4.*` namespace. Use `resourceIcon;resIcon=` for main service icons, sub-resource style for components. +- Container `value` = category label (e.g., "DNS", "Compute"). Icon `value` = service name + optional italic sub-label. NEVER put the service name on the container. +- Edges connect to service icons, not containers. Use `exitX`/`exitY` and `entryX`/`entryY` (0-1) to control connection sides. +- Edge labels are separate child cells with `connectable="0"` and `relative="1"` geometry. +- Region groups use `container=0` (decoration-only). VPC/subnets use `container=1`. +- Prefer flat layouts. Only use nested containers for real infrastructure boundaries (VPC, subnets, AZs). +- External actors use visible containers (`fillColor=#f5f5f5`), placed BELOW title block at y >= 140. + +## Layout Guidelines + +For detailed spacing rules, edge routing patterns, and placement tables, see `references/layout-guidelines.md`. Key rules: + +- **Spacing**: 180px horizontal / 120px vertical gaps. For 13+ services, increase to 220px/160px. +- **Edge routing**: Use `orthogonalEdgeStyle`. Add explicit waypoints for non-adjacent routing. Edges leave perpendicular to container face. +- **Multiple edges**: Each outgoing edge MUST exit from a different point. Spread entry points when multiple edges enter the same target. +- **Step badges/legend**: Teal `#007CBD` 28x28 badges near arrow sources. Right sidebar legend for 7+ services. Legend height MUST match diagram height. +- **Auxiliary services**: Only CloudWatch, CloudTrail, X-Ray, IAM. No step numbers, no edges. Place in dashed "Auxiliary Services" group inside AWS Cloud boundary. +- **All other services are primary** — MUST have edges and step numbers. + +## File Naming + +Each diagram gets a **descriptive filename** in kebab-case, placed in `./docs/` (e.g., `docs/healthcare-appointment-agent.drawio`, `docs/3-tier-vpc-webapp.drawio`). Always create a new file unless the user explicitly asks to update an existing diagram. + +## Output + +1. Create the `docs/` directory if it does not exist +2. Derive the filename from the user's prompt (see File Naming above) +3. **Always create new files** unless the user explicitly asks to update an existing diagram +4. Save the diagram to `./docs/.drawio` +5. After writing, the PostToolUse hook will automatically: + a. Validate the XML (structure, AWS shapes, edges, geometry) + b. If validation passes, generate a draw.io preview URL +6. If validation fails, fix the errors and rewrite the file +7. **Only after validation passes**, generate the browser preview link by running: + + ```bash + python3 ${PLUGIN_ROOT}/scripts/lib/drawio_url.py ./docs/.drawio --open + ``` + + This compresses the XML and opens `app.diagrams.net` with the diagram loaded instantly. Do NOT run this if validation failed. +8. If the user requested an export format (png, svg, pdf): + a. Check if draw.io desktop CLI is available + b. Export with `--embed-diagram` to `./docs/.drawio.` + c. Delete the intermediate `.drawio` file on success +9. **Always present to the user**: + - File path + - Diagram type and services included + - Validation status + - The draw.io preview URL (clickable link to open in browser) + - A recommended alt text (concise, under 100 characters, describing the diagram's purpose — not "diagram of...") + +## CRITICAL: XML Well-Formedness + +- **NEVER use double hyphens (`--`) inside XML comments.** `--` is illegal inside `` per the XML spec and causes parse errors. Use single hyphens or rephrase. +- Escape special characters in attribute values: `&`, `<`, `>`, `"` +- Always use unique `id` values for each `mxCell` + +## Important Rules + +- NEVER use compressed/base64 diagram content +- NEVER invent shape names — only use shapes from `references/aws4-shapes-services.md` +- ALWAYS wrap XML in `` — not bare `` +- ALWAYS include cells id="0" and id="1" as root and default layer +- ALWAYS use `resourceIcon;resIcon=` style for main service icons +- ALWAYS set `container=1;pointerEvents=0;` on group shapes +- ALWAYS validate edge source/target IDs reference existing cells +- ALWAYS include a title block at the top of every diagram +- ALWAYS place 48x48 service icons inside colored category containers +- ALWAYS use `fontFamily=Helvetica;` in every style attribute +- For complex diagrams (7+ services), ALWAYS add step badges and legend +- Use descriptive cell IDs, not random strings (e.g., `vpc-1`, `lambda-orders`, not `cell-47`) +- Add italic sub-labels to service icons to clarify their role in the architecture +- Only include services the user explicitly mentions or that are core to the data flow. Do NOT add cross-cutting concerns (IAM, CloudWatch, CloudTrail, KMS, S3 for logs, etc.) unless the user asks for them +- Include a title/label on the diagram describing the architecture +- NEVER set a `background` attribute on mxGraphModel — any hardcoded background breaks dark mode adaptive contrast + +## Reference Priority + +When generating diagrams, follow this priority order: + +1. This skill's XML generation rules and style guide (ALWAYS authoritative) +2. This skill's example `.drawio` files in `references/` (Step 3 selection table) +3. The user's existing `.drawio` files ONLY when explicitly requested ("match my style", "update my diagram") + +Do NOT proactively read `.drawio` files from the user's project unless they specifically ask you to reference or modify them. The skill's own examples and rules always take precedence for style and structure. diff --git a/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/aws4-shapes-resources.md b/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/aws4-shapes-resources.md new file mode 100644 index 00000000..8ed940dd --- /dev/null +++ b/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/aws4-shapes-resources.md @@ -0,0 +1,101 @@ +# AWS4 Sub-Resource and Misc Shapes + +These are service sub-components and utility shapes, not main service icons. Use `shape=mxgraph.aws4.{name}` style (not `resourceIcon`) at 48x48. + +## Sub-Resource Icons + +**Alexa**: `alexa_enabled_device`, `alexa_skill`, `alexa_smart_home_skill`, `alexa_voice_service` + +**Application**: `application_discovery_service_aws_agentless_collector`, `application_discovery_service_aws_discovery_agent`, `application_discovery_service_migration_evaluator_collector`, `application_load_balancer` + +**Aws**: `aws_backup_for_aws_cloudformation`, `aws_backup_legal_hold`, `aws_backup_support_for_amazon_fsx_for_netapp_ontap`, `aws_backup_vault_lock`, `aws_backup_virtual_machine_monitor`, `aws_cloud`, `aws_glue_data_quality`, `aws_glue_for_ray`, `aws_user_notifications` + +**Backup**: `backup_audit_manager`, `backup_aws_backup_support_for_amazon_s3`, `backup_aws_backup_support_for_vmware_workloads`, `backup_backup_plan`, `backup_backup_restore`, `backup_compliance_reporting`, `backup_compute`, `backup_database`, `backup_gateway`, `backup_plan`, `backup_recovery_point_objective`, `backup_recovery_time_objective`, `backup_restore`, `backup_storage`, `backup_vault`, `backup_virtual_machine`, `backup_virtual_machine_monitor` + +**Braket**: `braket_chandelier`, `braket_chip`, `braket_embedded_simulator`, `braket_managed_simulator`, `braket_noise_simulator`, `braket_qpu`, `braket_simulator`, `braket_simulator_1`, `braket_simulator_2`, `braket_simulator_3`, `braket_simulator_4`, `braket_state_vector`, `braket_tensor_network` + +**Checklist**: `checklist_cost`, `checklist_fault_tolerant`, `checklist_performance`, `checklist_security` + +**Cloud**: `cloud_digital_interface`, `cloud_extension_ros`, `cloud_map_resource`, `cloud_wan_segment_network`, `cloud_wan_transit_gateway_route_table_attachment`, `cloud_wan_virtual_pop` + +**Cloudwatch**: `cloudwatch_cross_account_observability`, `cloudwatch_data_protection`, `cloudwatch_evidently`, `cloudwatch_logs`, `cloudwatch_metrics_insights`, `cloudwatch_rum`, `cloudwatch_synthetics` + +**Container**: `container_1`, `container_2`, `container_3`, `container_registry_image` + +**Data**: `data_encryption_key`, `data_exchange_for_apis`, `data_lake_resource_icon`, `data_set`, `data_stream`, `data_table` + +**Datazone**: `datazone_business_data_catalog`, `datazone_data_portal`, `datazone_data_projects` + +**Db**: `db_instance`, `db_instance_read_replica`, `db_instance_standby`, `db_on_instance`, `db_on_instance2` + +**Dynamodb**: `dynamodb_dax`, `dynamodb_standard_access_table_class`, `dynamodb_standard_infrequent_access_table_class`, `dynamodb_stream` + +**Ec2**: `ec2_aws_microservice_extractor_for_net`, `ec2_c6a_instance`, `ec2_c6gn_instance`, `ec2_c6i_instance`, `ec2_c6in_instance`, `ec2_c7g_instance`, `ec2_c7gn_instance`, `ec2_dl1_instance`, `ec2_g5_instance`, `ec2_g5g_instance`, `ec2_hpc6a_instance`, `ec2_hpc6id_instance`, `ec2_i4i_instance`, `ec2_im4gn_instance`, `ec2_inf2_instance`, `ec2_instance_contents`, `ec2_is4gen_instance`, `ec2_m1_mac_instance`, `ec2_m6a_instance`, `ec2_m6i_instance`, `ec2_m6idn_instance`, `ec2_m6in_instance`, `ec2_p4de_instance`, `ec2_r6a_instance`, `ec2_r6i_instance`, `ec2_r6idn_instance`, `ec2_r6in_instance`, `ec2_r7iz_instance`, `ec2_trn1_instance`, `ec2_vt1_instance`, `ec2_x2gd_instance`, `ec2_x2idn_instance`, `ec2_x2iedn_instance`, `ec2_x2iezn_instance` + +**Ecs**: `ecs_copilot_cli`, `ecs_service`, `ecs_service_connect`, `ecs_task` + +**Elastic**: `elastic_block_store_amazon_data_lifecycle_manager`, `elastic_block_store_volume_gp3`, `elastic_file_system_elastic_throughput`, `elastic_file_system_infrequent_access`, `elastic_file_system_intelligent_tiering`, `elastic_file_system_one_zone`, `elastic_file_system_one_zone_infrequent_access`, `elastic_file_system_one_zone_standard`, `elastic_file_system_standard`, `elastic_file_system_standard_infrequent_access`, `elastic_inference`, `elastic_ip_address`, `elastic_network_adapter`, `elastic_network_interface` + +**Elasticache**: `elasticache_for_memcached`, `elasticache_for_redis`, `elasticache_for_valkey` + +**Emr**: `emr_engine`, `emr_engine_mapr_m3`, `emr_engine_mapr_m5`, `emr_engine_mapr_m7` + +**Event**: `event_event_based`, `event_resource`, `event_time_based` + +**Eventbridge**: `eventbridge_custom_event_bus_resource`, `eventbridge_default_event_bus_resource`, `eventbridge_pipes`, `eventbridge_saas_partner_event_bus_resource`, `eventbridge_scheduler`, `eventbridge_schema`, `eventbridge_schema_registry` + +**File**: `file_cache_hybrid_nfs_linked_datasets`, `file_cache_on_premises_nfs_linked_datasets`, `file_cache_s3_linked_datasets`, `file_gateway`, `file_system` + +**Generic**: `generic_application`, `generic_database`, `generic_firewall` + +**Group**: `group_availability_zone`, `group_elastic_load_balancing`, `group_subnet`, `group_vpc` + +**Illustration**: `illustration_desktop`, `illustration_devices`, `illustration_notification`, `illustration_office_building`, `illustration_users` + +**Internet**: `internet_alt1`, `internet_alt2`, `internet_alt22`, `internet_gateway` + +**Iot**: `iot_analytics_channel`, `iot_analytics_data_store`, `iot_analytics_dataset`, `iot_analytics_pipeline`, `iot_core_device_advisor`, `iot_core_device_location`, `iot_device_defender_iot_device_jobs`, `iot_device_gateway`, `iot_device_jobs_resource`, `iot_device_management_fleet`, `iot_device_tester`, `iot_greengrass_artifact`, `iot_greengrass_component`, `iot_greengrass_component_machine_learning`, `iot_greengrass_component_nucleus`, `iot_greengrass_component_private`, `iot_greengrass_component_public`, `iot_greengrass_interprocess_communication`, `iot_greengrass_protocol`, `iot_greengrass_recipe`, `iot_greengrass_stream_manager`, `iot_lorawan_protocol`, `iot_over_the_air_update`, `iot_sailboat`, `iot_sitewise_asset`, `iot_sitewise_asset_hierarchy`, `iot_sitewise_asset_model`, `iot_sitewise_asset_properties`, `iot_sitewise_data_streams`, `iot_thing_freertos_device`, `iot_thing_humidity_sensor`, `iot_thing_industrial_pc`, `iot_thing_plc`, `iot_thing_relay`, `iot_thing_stacklight`, `iot_thing_temperature_humidity_sensor`, `iot_thing_temperature_sensor`, `iot_thing_temperature_vibration_sensor`, `iot_thing_vibration_sensor` + +**Location**: `location_service_geofence`, `location_service_map`, `location_service_place`, `location_service_routes`, `location_service_track` + +**Mainframe**: `mainframe_modernization_analyzer`, `mainframe_modernization_compiler`, `mainframe_modernization_converter`, `mainframe_modernization_developer`, `mainframe_modernization_runtime` + +**Migration**: `migration_hub_refactor_spaces_applications`, `migration_hub_refactor_spaces_environments`, `migration_hub_refactor_spaces_services` + +**Network**: `network_access_control_list`, `network_firewall_endpoints`, `network_load_balancer` + +**Opensearch**: `opensearch_dashboards`, `opensearch_ingestion`, `opensearch_observability`, `opensearch_service_cluster_administrator_node`, `opensearch_service_data_node`, `opensearch_service_index`, `opensearch_service_traces`, `opensearch_service_ultrawarm_node` + +**Organizations**: `organizations_account`, `organizations_account2`, `organizations_management_account`, `organizations_management_account2`, `organizations_organizational_unit`, `organizations_organizational_unit2` + +**Rds**: `rds_blue_green_deployments`, `rds_instance`, `rds_instance_alt`, `rds_mariadb_instance`, `rds_mariadb_instance_alt`, `rds_multi_az`, `rds_multi_az_db_cluster`, `rds_mysql_instance`, `rds_mysql_instance_alt`, `rds_optimized_writes`, `rds_oracle_instance`, `rds_oracle_instance_alt`, `rds_piop`, `rds_piops`, `rds_postgresql_instance`, `rds_postgresql_instance_alt`, `rds_proxy`, `rds_proxy_alt`, `rds_sql_server_instance`, `rds_sql_server_instance_alt`, `rds_trusted_language_extensions_for_postgresql` + +**Redshift**: `redshift_auto_copy`, `redshift_data_sharing_governance`, `redshift_ml`, `redshift_query_editor_v20_light`, `redshift_ra3`, `redshift_streaming_ingestion` + +**Route**: `route_53_application_recovery_controller`, `route_53_readiness_checks`, `route_53_resolver`, `route_53_resolver_dns_firewall`, `route_53_resolver_query_logging`, `route_53_routing_controls`, `route_table` + +**S3**: `s3_batch_operations`, `s3_express_one_zone`, `s3_file_gateway`, `s3_multi_region_access_points`, `s3_object_lambda`, `s3_object_lambda_access_points`, `s3_object_lock`, `s3_on_outposts`, `s3_replication_time_control`, `s3_select`, `s3_storage_lens`, `s3_tables`, `s3_vectors` + +**Sagemaker**: `sagemaker_canvas`, `sagemaker_geospatial_ml`, `sagemaker_model`, `sagemaker_notebook`, `sagemaker_shadow_testing`, `sagemaker_train` + +**Simple**: `simple_ad`, `simple_storage_service_directory_bucket`, `simple_storage_service_s3_glacier_instant_retrieval` + +**Systems**: `systems_manager_application_manager`, `systems_manager_change_calendar`, `systems_manager_change_manager`, `systems_manager_compliance`, `systems_manager_distributor`, `systems_manager_opscenter`, `systems_manager_session_manager` + +**Transfer**: `transfer_family_aws_as2`, `transfer_for_ftp_resource`, `transfer_for_ftps_resource`, `transfer_for_sftp_resource` + +**Virtual**: `virtual_gateway`, `virtual_node`, `virtual_private_cloud`, `virtual_router`, `virtual_service`, `virtual_tape_library` + +**Vpc**: `vpc_access_points`, `vpc_carrier_gateway`, `vpc_network_access_analyzer`, `vpc_reachability_analyzer`, `vpc_traffic_mirroring`, `vpc_virtual_private_cloud_vpc` + +**Waf**: `waf_bad_bot`, `waf_bot`, `waf_bot_control`, `waf_labels`, `waf_managed_rule`, `waf_rule` + +**Workspaces**: `workspaces_family_amazon_workspaces`, `workspaces_family_amazon_workspaces_core`, `workspaces_workspaces_web` + +## Misc Shapes + +`action`, `actuator`, `addon`, `agent`, `agent2`, `alarm`, `alert`, `ami`, `application`, `apps`, `archive`, `attribute`, `attributes`, `automation`, `bank`, `bucket`, `budgets`, `bycicle`, `c5a`, `c5ad`, `c5d`, `c6gd`, `camera`, `camera2`, `car`, `cart`, `chat`, `checklist`, `client`, `cloudsearch`, `cloudwatch`, `cluster`, `connector`, `credentials`, `deployment`, `deployments`, `disk`, `document`, `documents`, `documents2`, `documents3`, `echo`, `email`, `endpoint`, `endpoints`, `event`, `factory`, `finding`, `firetv`, `folder`, `folders`, `forums`, `g4dn`, `gamelift`, `gateway`, `gear`, `generic`, `globe`, `house`, `i2`, `i3en`, `inf1`, `inferentia`, `instance`, `instance2`, `instances`, `internet`, `inventory`, `item`, `items`, `layers`, `lightbulb`, `logs`, `m5n`, `mesh`, `message`, `metrics`, `monitoring`, `multimedia`, `mxgraph.aws4`, `namespace`, `notebook`, `nova`, `object`, `peering`, `permissions`, `policy`, `privatelink`, `question`, `queue`, `r5n`, `recover`, `registry`, `rekognition`, `replication`, `rescue`, `resource`, `resources`, `role`, `router`, `rule`, `sensor`, `servers`, `service`, `servo`, `shadow`, `shield2`, `simulation`, `simulator`, `snapshot`, `stack`, `stack2`, `sts`, `table`, `template`, `thermostat`, `topic`, `travel`, `user`, `users`, `utility`, `vault`, `volume`, `windfarm` + +## Other Sub-Resource Icons + +`a1_instance`, `access_analyzer`, `ad_connector`, `amplify_aws_amplify_studio`, `athena_data_source_connectors`, `aurora_instance`, `aurora_instance_alt`, `authenticated_user`, `auto_scaling`, `blockchain_resource`, `bucket_with_objects`, `c4_instance`, `c5_instance`, `c5n_instance`, `c6g_instance`, `cache_node`, `cached_volume`, `certificate_manager`, `certificate_manager_2`, `change_set`, `classic_load_balancer`, `cloudfront_functions`, `cloudtrail_cloudtrail_lake`, `coffee_pot`, `cold_storage`, `corporate_data_center`, `corporate_data_center2`, `custom_event_bus_resource`, `customer_gateway`, `d2_instance`, `d3_instance`, `d3en_instance`, `database_migration_workflow_job`, `datasync_discovery`, `default_event_bus_resource`, `dense_compute_node`, `dense_storage_node`, `desired_state`, `development_environment`, `devops_guru_insights`, `documentdb_elastic_clusters`, `door_lock`, `download_distribution`, `edge_location`, `eks_on_outposts`, `email_2`, `email_notification`, `encrypted_data`, `external_sdk`, `external_toolkit`, `f1_instance`, `filtering_rule`, `firetv_stick`, `fleet_management`, `flow_logs`, `fsx_file_gateway`, `g3_instance`, `g4ad_instance`, `game_tech`, `game_tech2`, `gateway_load_balancer`, `general_access_points`, `git_repository`, `glacier_deep_archive`, `global_secondary_index`, `glue_crawlers`, `glue_data_catalog`, `h1_instance`, `habana_gaudi`, `hardware_board`, `hdfs_cluster`, `high_memory_instance`, `hosted_zone`, `http2_protocol`, `http_notification`, `http_protocol`, `i3_instance`, `identity_access_management_iam_roles_anywhere`, `import_export`, `instance_with_cloudwatch`, `instance_with_cloudwatch2`, `instances_2`, `intelligent_tiering`, `json_script`, `key_management_service_external_key_store`, `lambda_function`, `license_manager_application_discovery`, `license_manager_license_blending`, `long_term_security_credential`, `m4_instance`, `m5_instance`, `m5a_instance`, `m5d_instance`, `m5dn_instance`, `m5n_instance`, `m5zn_instance`, `m6g_instance`, `m6gd_instance`, `mac_instance`, `magnifying_glass`, `magnifying_glass_2`, `maintenance_windows`, `managed_ms_ad`, `management_console2`, `mediaconnect_gateway`, `medical_emergency`, `mfa_token`, `mobile_client`, `mobile_hub`, `mq_broker`, `mqtt_protocol`, `ms_sql_instance`, `ms_sql_instance_alternate`, `msk_amazon_msk_connect`, `multiple_volumes_resource`, `mysql_db_instance`, `mysql_db_instance_alternate`, `nat_gateway`, `non_cached_volume`, `office_building`, `one_zone_ia`, `open_3d_engine`, `opsworks_apps`, `opsworks_permissions`, `optimized_instance`, `oracle_db_instance`, `oracle_db_instance_alternate`, `p2_instance`, `p3_instance`, `p3dn_instance`, `p4_instance`, `p4d_instance`, `parameter_store`, `patch_manager`, `permissions_2`, `pinpoint_journey`, `police_emergency`, `postgresql_instance`, `programming_language`, `quicksight_paginated_reports`, `r4_instance`, `r5_instance`, `r5a_instance`, `r5ad_instance`, `r5b_instance`, `r5d_instance`, `r5gd_instance`, `r5n_instance`, `r6g_instance`, `rdn_instance`, `rekognition_image`, `rekognition_video`, `replication_time_control`, `reported_state`, `rule_2`, `rule_3`, `run_command`, `saas_event_bus_resource`, `saml_token`, `search_documents`, `security_group`, `security_hub_finding`, `shield_shield_advanced`, `source_code`, `spot_instance`, `sql_primary`, `sql_replica`, `ssl_padlock`, `standard_ia`, `state_manager`, `streaming_distribution`, `sts_alternate`, `t2_instance`, `t3_instance`, `t3a_instance`, `t4g_instance`, `tape_gateway`, `tape_storage`, `temporary_security_credential`, `textract_analyze_lending`, `topic_2`, `traditional_server`, `trainium_instance`, `transit_gateway_attachment`, `volume_gateway`, `vpn_connection`, `vpn_gateway`, `well_architected_tool`, `work_package`, `x1_instance`, `x1_instance2`, `x1e_instance`, `z1d_instance` diff --git a/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/aws4-shapes-services.md b/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/aws4-shapes-services.md new file mode 100644 index 00000000..20ed9293 --- /dev/null +++ b/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/aws4-shapes-services.md @@ -0,0 +1,69 @@ +# AWS4 Service Icon Shapes + +All service icons use `resourceIcon;resIcon=mxgraph.aws4.{name}` style at 78x78. Category colors are in `style-guide.md`. + +**Customer Enablement**: `activate`, `customer_enablement`, `iq`, `managed_services`, `professional_services`, `repost`, `repost_private`, `support`, `training_certification` + +**Business Applications**: `alexa_for_business`, `appfabric`, `business_application`, `chime`, `chime_sdk`, `connect`, `end_user_messaging`, `honeycode`, `pinpoint`, `quick_suite`, `simple_email_service`, `supply_chain`, `wickr`, `workdocs`, `workmail` + +**General**: `all_products`, `general`, `marketplace`, `productIcon`, `resourceIcon` + +**Front End Web Mobile**: `amplify`, `device_farm`, `location_service`, `mobile` + +**Analytics**: `analytics`, `athena`, `clean_rooms`, `cloudsearch2`, `data_exchange`, `data_pipeline`, `datazone`, `elasticsearch_service`, `emr`, `entity_resolution`, `finspace`, `glue`, `glue_databrew`, `glue_elastic_views`, `kinesis`, `kinesis_data_analytics`, `kinesis_data_firehose`, `kinesis_data_streams`, `kinesis_video_streams`, `lake_formation`, `managed_service_for_apache_flink`, `managed_streaming_for_kafka`, `quicksight`, `redshift`, `sagemaker_2`, `sql_workbench` + +**AI/ML**: `apache_mxnet_on_aws`, `app_studio`, `augmented_ai`, `bedrock`, `bedrock_agentcore`, `codeguru_2`, `codewhisperer`, `comprehend`, `comprehend_medical`, `deep_learning_amis`, `deep_learning_containers`, `deepcomposer`, `deeplens`, `deepracer`, `devops_guru`, `elastic_inference_2`, `forecast`, `fraud_detector`, `healthimaging`, `healthlake`, `healthscribe`, `kendra`, `lex`, `lookout_for_equipment`, `lookout_for_metrics`, `lookout_for_vision`, `machine_learning`, `monitron`, `neuron_ml_sdk`, `nova2`, `omics`, `panorama`, `personalize`, `polly`, `q`, `rekognition_2`, `sagemaker`, `sagemaker_ground_truth`, `sagemaker_studio_lab`, `tensorflow_on_aws`, `textract`, `torchserve`, `transcribe`, `translate` + +**Application Integration**: `api_gateway`, `appflow`, `application_integration`, `appsync`, `b2b_data_interchange`, `eventbridge`, `express_workflow`, `managed_workflows_for_apache_airflow`, `mobile_application`, `mq`, `sns`, `sqs`, `step_functions` + +**Management Governance**: `app_config`, `app_wizard`, `application_auto_scaling`, `autoscaling`, `backint_agent`, `chatbot`, `cloudformation`, `cloudtrail`, `cloudwatch_2`, `codeguru`, `config`, `control_tower`, `devops_agent`, `distro_for_opentelemetry`, `license_manager`, `managed_service_for_grafana`, `managed_service_for_prometheus`, `management_and_governance`, `management_console`, `opsworks`, `organizations`, `partner_central`, `personal_health_dashboard`, `proton`, `resilience_hub`, `resource_explorer`, `service_catalog`, `service_management_connector`, `systems_manager`, `systems_manager_incident_manager`, `telco_network_builder`, `trusted_advisor`, `user_notifications`, `well_architect_tool` + +**Networking**: `app_mesh`, `application_recovery_controller`, `client_vpn`, `cloud_directory`, `cloud_map`, `cloud_wan`, `cloudfront`, `direct_connect`, `global_accelerator`, `networking_and_content_delivery`, `private_5g`, `route_53`, `rtb_fabric`, `site_to_site_vpn`, `transit_gateway`, `verified_access`, `vpc`, `vpc_lattice`, `vpc_privatelink` + +**Compute**: `app_runner`, `auto_scaling2`, `auto_scaling3`, `batch`, `bottlerocket`, `compute`, `compute_optimizer`, `ec2`, `ec2_image_builder`, `elastic_beanstalk`, `elastic_fabric_adapter`, `elastic_load_balancing`, `elastic_vmware_service`, `fargate`, `genomics_cli`, `lambda`, `lightsail`, `lightsail_for_research`, `local_zones`, `nice_dcv`, `nice_enginframe`, `nitro_enclaves`, `outposts`, `outposts_1u_and_2u_servers`, `outposts_family`, `parallel_cluster`, `parallel_computing_service`, `serverless_application_repository`, `simspace_weaver`, `vmware_cloud_on_aws`, `wavelength` + +**IoT (Thing shapes)**: `iot`, `iot_core`, `iot_greengrass`, `iot_analytics`, `iot_events`, `iot_sitewise`, `iot_device_defender`, `iot_device_management`, `iot_things_graph`, `iot_1click`, `iot_button`, `iot_certificate`, `iot_action`, `iot_actuator`, `iot_alexa_enabled_device`, `iot_alexa_skill`, `iot_alexa_voice_service`, `iot_bank`, `iot_bicycle`, `iot_camera`, `iot_car`, `iot_cart`, `iot_coffee_pot`, `iot_desired_state`, `iot_device_gateway`, `iot_dog`, `iot_door_lock`, `iot_factory`, `iot_fire_tv`, `iot_fire_tv_stick`, `iot_generic`, `iot_house`, `iot_http`, `iot_http2`, `iot_lambda`, `iot_lightbulb`, `iot_medical_emergency`, `iot_mqtt`, `iot_over_the_air_update`, `iot_police_emergency`, `iot_policy`, `iot_reported_state`, `iot_rule`, `iot_sensor`, `iot_servo`, `iot_shadow`, `iot_simulator`, `iot_thermostat`, `iot_topic`, `iot_travel`, `iot_utility`, `iot_windfarm`, `freertos`, `iot_device_defender2`, `iot_fleet_hub`, `iot_expresslink`, `iot_fleetwise`, `iot_roborunner`, `iot_twinmaker` + +**Developer Tools**: `application_composer`, `cloud9`, `cloud_control_api`, `cloud_development_kit`, `cloudshell`, `codeartifact`, `codebuild`, `codecatalyst`, `codecommit`, `codedeploy`, `codepipeline`, `codestar`, `command_line_interface`, `corretto`, `developer_tools`, `fault_injection_simulator`, `tools_and_sdks`, `xray` + +**Cloud Financial Management**: `application_cost_profiler`, `budgets_2`, `cost_and_usage_report`, `cost_explorer`, `cost_management`, `custom_billing_manager`, `reserved_instance_reporting`, `savings_plans` + +**Migration Modernization**: `application_discovery_service`, `cloudendure_migration`, `data_transfer_terminal`, `datasync`, `mainframe_modernization`, `migration_and_transfer`, `migration_evaluator`, `migration_hub`, `server_migration_service`, `snowball`, `snowball_edge`, `snowmobile`, `transfer_family`, `transfer_for_sftp`, `transform` + +**End User Computing**: `appstream_20`, `desktop_and_app_streaming`, `worklink`, `workspaces`, `workspaces_family`, `workspaces_thin_client` + +**AR/VR**: `ar_vr`, `sumerian` + +**Security**: `artifact`, `audit_manager`, `certificate_manager_3`, `cloudhsm`, `cognito`, `detective`, `directory_service`, `firewall_manager`, `guardduty`, `identity_and_access_management`, `inspector`, `key_management_service`, `macie`, `network_firewall`, `payment_cryptography`, `private_certificate_authority`, `resource_access_manager`, `secrets_manager`, `security_agent`, `security_hub`, `security_identity_and_compliance`, `security_incident_response`, `security_lake`, `shield`, `signer`, `single_sign_on`, `verified_permissions`, `waf` + +**Database**: `aurora`, `database`, `database_migration_service`, `documentdb_with_mongodb_compatibility`, `dynamodb`, `elasticache`, `keyspaces`, `managed_apache_cassandra_service`, `memorydb_for_redis`, `neptune`, `oracle_database_at_aws`, `rds`, `rds_on_vmware`, `timestream` + +**Storage**: `backup`, `cloudendure_disaster_recovery`, `efs_infrequentaccess`, `efs_standard`, `elastic_block_store`, `elastic_file_system`, `file_cache`, `fsx`, `fsx_for_lustre`, `fsx_for_netapp_ontap`, `fsx_for_openzfs`, `fsx_for_windows_file_server`, `glacier`, `infrequent_access_storage_class`, `s3`, `s3_on_outposts_storage`, `snowcone`, `storage`, `storage_gateway` + +**Blockchain**: `blockchain`, `managed_blockchain`, `quantum_ledger_database` + +**Quantum Technologies**: `braket`, `quantum_technologies` + +**Contact Center**: `contact_center` + +**Containers**: `containers`, `ecr`, `ecs`, `ecs_anywhere`, `eks`, `eks_anywhere`, `eks_cloud`, `eks_distro`, `red_hat_openshift` + +**Customer Engagement**: `customer_engagement` + +**Media Services**: `deadline_cloud`, `elastic_transcoder`, `elemental`, `elemental_link`, `elemental_mediaconnect`, `elemental_mediaconvert`, `elemental_medialive`, `elemental_mediapackage`, `elemental_mediastore`, `elemental_mediatailor`, `interactive_video`, `media_services`, `nimble_studio`, `thinkbox_deadline`, `thinkbox_draft`, `thinkbox_frost`, `thinkbox_krakatoa`, `thinkbox_sequoia`, `thinkbox_stoke`, `thinkbox_xmesh` + +**IoT (Service Icons)**: `freertos`, `greengrass`, `internet_of_things`, `iot_1click`, `iot_analytics`, `iot_button`, `iot_core`, `iot_device_defender`, `iot_device_management`, `iot_edukit`, `iot_events`, `iot_expresslink`, `iot_fleetwise`, `iot_roborunner`, `iot_sitewise`, `iot_things_graph`, `iot_twinmaker` + +**Games**: `gamekit`, `gamelift_2`, `gamelift_streams`, `games`, `gamesparks`, `lumberyard`, `open_3d_engine_2` + +**Satellite**: `ground_station`, `satellite` + +**Robotics**: `robomaker`, `robotics` + +**Serverless**: `serverless` + +## Group Shapes (for boundaries) + +`group_account`, `group_auto_scaling_group`, `group_aws_cloud`, `group_aws_cloud_alt`, `group_aws_step_functions_workflow`, `group_corporate_data_center`, `group_ec2_instance_contents`, `group_elastic_beanstalk`, `group_iot_greengrass`, `group_iot_greengrass_deployment`, `group_on_premise`, `group_region`, `group_security_group`, `group_spot_fleet`, `group_vpc2` + +**AgentCore**: Use `resIcon=mxgraph.aws4.bedrock_agentcore` (NOT `mxgraph.aws4.bedrock`) for Amazon Bedrock AgentCore services (Gateway, Runtime, Memory). See `references/example-agentcore.drawio`. diff --git a/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/cli-export.md b/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/cli-export.md new file mode 100644 index 00000000..51e89e37 --- /dev/null +++ b/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/cli-export.md @@ -0,0 +1,50 @@ +# draw.io CLI Export Reference + +The draw.io desktop app includes a CLI for exporting diagrams to PNG, SVG, or PDF. + +## Locating the CLI + +Try `drawio` first (works if on PATH), then fall back to platform-specific path: + +- **macOS**: `/Applications/draw.io.app/Contents/MacOS/draw.io` +- **Linux**: `drawio` (typically on PATH via snap/apt/flatpak) +- **Windows**: `"C:\Program Files\draw.io\draw.io.exe"` + +Use `which drawio` (or `where drawio` on Windows) to check PATH first. + +## Export Command + +```bash +drawio -x -f -e -b 10 -o +``` + +Key flags: + +- `-x` / `--export`: export mode (required) +- `-f` / `--format`: output format (png, svg, pdf) +- `-e` / `--embed-diagram`: embed diagram XML in the output (remains editable in draw.io) +- `-o` / `--output`: output file path +- `-b` / `--border`: border width (default: 0, use 10) +- `-t` / `--transparent`: transparent background (PNG only) +- `-s` / `--scale`: scale the diagram size +- `--width` / `--height`: fit into specified dimensions (preserves aspect ratio) +- `-a` / `--all-pages`: export all pages (PDF only) +- `-p` / `--page-index`: select a specific page (0-based) + +## Supported Formats + +| Format | Embed XML | Notes | +| ------ | ---------- | ---------------------------------------- | +| `png` | Yes (`-e`) | Viewable everywhere, editable in draw.io | +| `svg` | Yes (`-e`) | Scalable, editable in draw.io | +| `pdf` | Yes (`-e`) | Printable, editable in draw.io | + +## File Naming + +Use double extensions: `architecture.drawio.png`, `architecture.drawio.svg`, `architecture.drawio.pdf`. This signals the file contains embedded diagram XML. Opening in draw.io recovers the editable diagram. After a successful export, delete the intermediate `.drawio` file since the exported file contains the full diagram. + +## Opening the Result + +- **macOS**: `open ` +- **Linux**: `xdg-open ` +- **Windows**: `start ` diff --git a/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/diagram-templates-advanced.md b/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/diagram-templates-advanced.md new file mode 100644 index 00000000..50c82cd0 --- /dev/null +++ b/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/diagram-templates-advanced.md @@ -0,0 +1,50 @@ +# Diagram Templates — Advanced + +Multi-region, hybrid, and sizing guidelines. See `diagram-templates-basic.md` for basic patterns. + +## Multi-Region Active-Active + +``` +Layout (two regions side by side): + [Route 53 (latency-based routing)] at top + + [Region 1: us-east-1] [Region 2: eu-west-1] + [CloudFront] [CloudFront] + [ALB] [ALB] + [ECS/Lambda] [ECS/Lambda] + [Aurora (primary)] ←──replication──→ [Aurora (replica)] + [DynamoDB Global Table] ←──sync──→ [DynamoDB Global Table] + [S3] ←──replication──→ [S3] + + [Global Accelerator] optionally at top +``` + +## Hybrid Architecture (On-Premises + AWS) + +``` +Layout: + [Corporate Data Center] + [Traditional Server] + [Database] + [Active Directory] + + ←── [Direct Connect] / [Site-to-Site VPN] ──→ + + [AWS Cloud] + [Transit Gateway] + [VPC - Production] + [workloads] + [VPC - Shared Services] + [Directory Service] + [Systems Manager] + [VPC - Development] + [workloads] +``` + +## Sizing Guidelines for Templates + +- **Small diagram** (3-5 services): pageWidth=800, pageHeight=600 +- **Medium diagram** (6-12 services): pageWidth=1169, pageHeight=827 (default A3) +- **Large diagram** (13+ services): pageWidth=1600, pageHeight=1200 +- **Multi-region**: pageWidth=2000, pageHeight=1000 +- **Complex VPC**: pageWidth=1400, pageHeight=1000 diff --git a/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/diagram-templates-basic.md b/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/diagram-templates-basic.md new file mode 100644 index 00000000..49920070 --- /dev/null +++ b/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/diagram-templates-basic.md @@ -0,0 +1,101 @@ +# Diagram Templates — Basic + +Ready-to-use patterns for common AWS architectures. Use these as starting points. + +## Serverless Web Application + +Services: CloudFront → API Gateway → Lambda → DynamoDB, with S3 for static assets and Cognito for auth. + +``` +Layout (left to right): + [Users] → [CloudFront] → [S3 Bucket (static)] + → [API Gateway] → [Lambda] → [DynamoDB] + → [S3 Bucket (data)] + [Cognito] connected to [API Gateway] (auth) +``` + +Typical cell arrangement: + +- Users icon at x=50 +- CloudFront at x=250 +- S3 static at x=450, y offset up +- API Gateway at x=450 +- Lambda at x=650 +- DynamoDB at x=850 +- Cognito at x=450, y offset down + +## VPC with Public/Private Subnets + +``` +Layout (nested groups): + [AWS Cloud] + [Region] + [VPC 10.0.0.0/16] + [AZ-1] + [Public Subnet 10.0.1.0/24] + - NAT Gateway + - ALB + [Private Subnet 10.0.2.0/24] + - EC2 / ECS instances + - RDS (primary) + [AZ-2] + [Public Subnet 10.0.3.0/24] + - NAT Gateway + [Private Subnet 10.0.4.0/24] + - EC2 / ECS instances + - RDS (standby) + [Internet Gateway] at VPC boundary + [Users] → [Internet Gateway] → [ALB] → [EC2/ECS] +``` + +## Microservices on ECS/EKS + +``` +Layout: + [Route 53] → [CloudFront] → [ALB] + + [VPC] + [ECS/EKS Cluster] + [Service A] ←→ [Service B] ←→ [Service C] + + [ElastiCache] connected to services + [RDS Aurora] connected to services + + [ECR] connected to [ECS/EKS Cluster] + [CloudWatch] monitoring all services +``` + +## Data Pipeline / Analytics + +``` +Layout (left to right, pipeline flow): + [Data Sources] + - [Kinesis Data Streams] ← external data + - [S3 Bucket (raw)] ← batch uploads + + [Processing] + - [Kinesis Data Firehose] → [S3 (processed)] + - [Glue ETL Jobs] → [S3 (curated)] + - [Lambda] for real-time transforms + + [Storage] + - [S3 Data Lake] (multiple buckets) + - [Glue Data Catalog] + + [Analytics] + - [Athena] → queries S3 + - [Redshift] → warehouse + - [QuickSight] → dashboards + + [Lake Formation] governing access across the pipeline +``` + +## CI/CD Pipeline + +``` +Layout (left to right, pipeline stages): + [Developer] → [CodeCommit] → [CodePipeline] → [CodeBuild] → [S3 (artifacts)] + [CodePipeline] → [CodeDeploy (staging)] → [Manual Approval] → [CodeDeploy (prod)] + [ECR] if deploying containers + [CloudWatch] monitoring, [SNS] for notifications +``` diff --git a/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/example-agentcore.drawio b/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/example-agentcore.drawio new file mode 100644 index 00000000..ec269a48 --- /dev/null +++ b/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/example-agentcore.drawio @@ -0,0 +1,206 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/example-complex-platform.drawio b/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/example-complex-platform.drawio new file mode 100644 index 00000000..e32d5aba --- /dev/null +++ b/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/example-complex-platform.drawio @@ -0,0 +1,355 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/example-event-driven.drawio b/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/example-event-driven.drawio new file mode 100644 index 00000000..cb3bfd69 --- /dev/null +++ b/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/example-event-driven.drawio @@ -0,0 +1,349 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/example-microservices.drawio b/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/example-microservices.drawio new file mode 100644 index 00000000..bcf1a22a --- /dev/null +++ b/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/example-microservices.drawio @@ -0,0 +1,370 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/example-multi-region-active-active.drawio b/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/example-multi-region-active-active.drawio new file mode 100644 index 00000000..27351ee2 --- /dev/null +++ b/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/example-multi-region-active-active.drawio @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/example-saas-backend.drawio b/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/example-saas-backend.drawio new file mode 100644 index 00000000..19d871ec --- /dev/null +++ b/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/example-saas-backend.drawio @@ -0,0 +1,454 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/example-sketch.drawio b/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/example-sketch.drawio new file mode 100644 index 00000000..a1798595 --- /dev/null +++ b/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/example-sketch.drawio @@ -0,0 +1,235 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/general-icons.md b/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/general-icons.md new file mode 100644 index 00000000..46d113cb --- /dev/null +++ b/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/general-icons.md @@ -0,0 +1,87 @@ +# General Architecture Icons + +When the architecture includes non-AWS services (on-premises, third-party, open-source, or platform-native), use these mappings. Same 120x120 container + 48x48 icon pattern as AWS services. Category tint colors match the AWS palette so mixed diagrams look consistent. + +The icon is generic — **the container label and service name are critical** for identification. + +## Technology to Shape Mapping + +### Databases + +| Technology | resIcon shape | Category (tint / stroke) | +| ------------------------------------ | ------------------ | ---------------------------- | +| PostgreSQL, MySQL, MariaDB, SQLite | `generic_database` | Database (#F5E6F7 / #C925D1) | +| MongoDB, Redis, Elasticsearch, Neo4j | `generic_database` | Database (#F5E6F7 / #C925D1) | + +### Compute and Runtime + +| Technology | resIcon shape | Category (tint / stroke) | +| ------------------------------- | --------------------- | --------------------------- | +| Docker, Kubernetes, VMs | `container_1` | Compute (#FFF2E8 / #ED7100) | +| On-premises servers, bare metal | `traditional_server` | Compute (#FFF2E8 / #ED7100) | +| macOS, iOS, Android native app | `mobile_client` | Compute (#FFF2E8 / #ED7100) | +| Desktop application, CLI tool | `generic_application` | Compute (#FFF2E8 / #ED7100) | +| Web server (nginx, Apache) | `traditional_server` | Compute (#FFF2E8 / #ED7100) | + +### External Services and APIs + +| Technology | resIcon shape | Category (tint / stroke) | +| -------------------------- | --------------------- | ----------------------------------- | +| GitHub, GitLab, Bitbucket | `internet` | Networking (#EDE7F6 / #8C4FFF) | +| REST/GraphQL API endpoints | `internet` | Networking (#EDE7F6 / #8C4FFF) | +| Stripe, Twilio, SendGrid | `generic_application` | App Integration (#FCE4EC / #E7157B) | +| CDN (Cloudflare, Fastly) | `internet` | Networking (#EDE7F6 / #8C4FFF) | +| DNS providers | `internet` | Networking (#EDE7F6 / #8C4FFF) | + +### AI and ML + +| Technology | resIcon shape | Category (tint / stroke) | +| ------------------------------ | --------------------- | ------------------------- | +| HuggingFace, OpenAI, Anthropic | `generic_application` | AI/ML (#E0F2F1 / #01A88D) | +| CoreML, TensorFlow, PyTorch | `generic_application` | AI/ML (#E0F2F1 / #01A88D) | +| ML model files, ONNX runtime | `generic_application` | AI/ML (#E0F2F1 / #01A88D) | + +### Storage + +| Technology | resIcon shape | Category (tint / stroke) | +| ---------------------------- | --------------------- | --------------------------- | +| Local filesystem, NFS, CIFS | `generic_application` | Storage (#E8F5E9 / #3F8624) | +| Object storage (MinIO, Ceph) | `generic_application` | Storage (#E8F5E9 / #3F8624) | + +### Messaging and Streaming + +| Technology | resIcon shape | Category (tint / stroke) | +| --------------------------- | --------------------- | ----------------------------------- | +| Kafka, RabbitMQ, NATS, MQTT | `generic_application` | App Integration (#FCE4EC / #E7157B) | +| WebSocket, gRPC, pub/sub | `generic_application` | App Integration (#FCE4EC / #E7157B) | + +### Security and Auth + +| Technology | resIcon shape | Category (tint / stroke) | +| --------------------------- | --------------------- | ---------------------------- | +| OAuth, OIDC, SAML, LDAP | `generic_application` | Security (#FFEBEE / #DD344C) | +| Vault (HashiCorp), KeyCloak | `generic_application` | Security (#FFEBEE / #DD344C) | + +### Monitoring and Observability + +| Technology | resIcon shape | Category (tint / stroke) | +| ---------------------------- | --------------------- | ------------------------------ | +| Prometheus, Grafana, Datadog | `generic_application` | Management (#FCE4EC / #E7157B) | +| ELK stack, Splunk, PagerDuty | `generic_application` | Management (#FCE4EC / #E7157B) | + +### External Actors (outside the system boundary) + +| Actor | resIcon shape | Container style | +| --------------------- | ----------------------- | ----------------------------------------------------- | +| End users / people | `users` | fillColor=#f5f5f5, stroke=light-dark(#666666,#D4D4D4) | +| Mobile users | `mobile_client` | fillColor=#f5f5f5, stroke=light-dark(#666666,#D4D4D4) | +| IoT devices / sensors | `sensor` | fillColor=#f5f5f5, stroke=light-dark(#666666,#D4D4D4) | +| Corporate data center | `corporate_data_center` | fillColor=#f5f5f5, stroke=light-dark(#666666,#D4D4D4) | + +## Boundary Groups for Non-AWS + +For non-AWS architectures, replace the AWS Cloud boundary group with a generic system boundary: + +- Use `group_corporate_data_center` style for on-premises boundaries +- Use a plain `group` style with dashed border for logical boundaries +- Keep the same `container=0` pattern for decorative region/zone groups diff --git a/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/group-styles.md b/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/group-styles.md new file mode 100644 index 00000000..f065bc3a --- /dev/null +++ b/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/group-styles.md @@ -0,0 +1,103 @@ +# AWS Group and Edge Style Templates + +Style strings for draw.io AWS groups and edges. Copy directly into `style=` attributes. + +## AWS Cloud + +``` +points=[[0,0],[0.25,0],[0.5,0],[0.75,0],[1,0],[1,0.25],[1,0.5],[1,0.75],[1,1],[0.75,1],[0.5,1],[0.25,1],[0,1],[0,0.75],[0,0.5],[0,0.25]];outlineConnect=0;gradientColor=none;html=1;whiteSpace=wrap;fontSize=12;fontStyle=0;shape=mxgraph.aws4.group;grIcon=mxgraph.aws4.group_aws_cloud;strokeColor=#232F3E;fillColor=light-dark(#232F3E0D,#232F3E0D);fillStyle=auto;verticalAlign=top;align=left;spacingLeft=30;fontColor=#232F3E;dashed=0;container=1;pointerEvents=0;collapsible=0;recursiveResize=0 +``` + +## Region + +**CRITICAL**: Region MUST use `container=0` (NOT `container=1`). Services are placed at `parent="1"` (root) with absolute coordinates positioned VISUALLY inside the region rectangle. This prevents nesting depth issues that break edge auto-routing. + +``` +points=[[0,0],[0.25,0],[0.5,0],[0.75,0],[1,0],[1,0.25],[1,0.5],[1,0.75],[1,1],[0.75,1],[0.5,1],[0.25,1],[0,1],[0,0.75],[0,0.5],[0,0.25]];outlineConnect=0;gradientColor=none;html=1;whiteSpace=wrap;fontSize=14;fontStyle=1;shape=mxgraph.aws4.group;grIcon=mxgraph.aws4.group_region;strokeColor=#00A4A6;fillColor=light-dark(#0C7B7D0D,#0C7B7D0D);fillStyle=auto;verticalAlign=top;align=left;spacingLeft=30;fontColor=#00A4A6;dashed=1;container=0;pointerEvents=0;collapsible=0;recursiveResize=0 +``` + +## Availability Zone + +``` +fillColor=none;strokeColor=#147EBA;dashed=1;verticalAlign=top;fontStyle=0;fontColor=#147EBA;whiteSpace=wrap;html=1;container=1;pointerEvents=0;collapsible=0;recursiveResize=0 +``` + +## VPC + +``` +points=[[0,0],[0.25,0],[0.5,0],[0.75,0],[1,0],[1,0.25],[1,0.5],[1,0.75],[1,1],[0.75,1],[0.5,1],[0.25,1],[0,1],[0,0.75],[0,0.5],[0,0.25]];outlineConnect=0;gradientColor=none;html=1;whiteSpace=wrap;fontSize=12;fontStyle=0;shape=mxgraph.aws4.group;grIcon=mxgraph.aws4.group_vpc2;strokeColor=#8C4FFF;fillColor=light-dark(#8C4FFF0D,#8C4FFF0D);fillStyle=auto;verticalAlign=top;align=left;spacingLeft=30;fontColor=#8C4FFF;dashed=0;container=1;pointerEvents=0;collapsible=0;recursiveResize=0 +``` + +## Public Subnet + +``` +points=[[0,0],[0.25,0],[0.5,0],[0.75,0],[1,0],[1,0.25],[1,0.5],[1,0.75],[1,1],[0.75,1],[0.5,1],[0.25,1],[0,1],[0,0.75],[0,0.5],[0,0.25]];outlineConnect=0;gradientColor=none;html=1;whiteSpace=wrap;fontSize=12;fontStyle=0;shape=mxgraph.aws4.group;grIcon=mxgraph.aws4.group_public_subnet;strokeColor=#248814;fillColor=light-dark(#2488140D,#2488140D);fillStyle=auto;verticalAlign=top;align=left;spacingLeft=30;fontColor=#248814;dashed=0;container=1;pointerEvents=0;collapsible=0;recursiveResize=0 +``` + +## Private Subnet + +``` +points=[[0,0],[0.25,0],[0.5,0],[0.75,0],[1,0],[1,0.25],[1,0.5],[1,0.75],[1,1],[0.75,1],[0.5,1],[0.25,1],[0,1],[0,0.75],[0,0.5],[0,0.25]];outlineConnect=0;gradientColor=none;html=1;whiteSpace=wrap;fontSize=12;fontStyle=0;shape=mxgraph.aws4.group;grIcon=mxgraph.aws4.group_private_subnet;strokeColor=#147EBA;fillColor=light-dark(#147EBA0D,#147EBA0D);fillStyle=auto;verticalAlign=top;align=left;spacingLeft=30;fontColor=#147EBA;dashed=0;container=1;pointerEvents=0;collapsible=0;recursiveResize=0 +``` + +## Security Group + +``` +points=[[0,0],[0.25,0],[0.5,0],[0.75,0],[1,0],[1,0.25],[1,0.5],[1,0.75],[1,1],[0.75,1],[0.5,1],[0.25,1],[0,1],[0,0.75],[0,0.5],[0,0.25]];outlineConnect=0;gradientColor=none;html=1;whiteSpace=wrap;fontSize=12;fontStyle=0;shape=mxgraph.aws4.group;grIcon=mxgraph.aws4.group_security_group;strokeColor=#DD3522;fillColor=none;verticalAlign=top;align=left;spacingLeft=30;fontColor=#DD3522;dashed=0;container=1;pointerEvents=0;collapsible=0;recursiveResize=0 +``` + +## Auto Scaling Group + +``` +points=[[0,0],[0.25,0],[0.5,0],[0.75,0],[1,0],[1,0.25],[1,0.5],[1,0.75],[1,1],[0.75,1],[0.5,1],[0.25,1],[0,1],[0,0.75],[0,0.5],[0,0.25]];outlineConnect=0;gradientColor=none;html=1;whiteSpace=wrap;fontSize=12;fontStyle=0;shape=mxgraph.aws4.group;grIcon=mxgraph.aws4.group_auto_scaling_group;strokeColor=#ED7100;fillColor=none;verticalAlign=top;align=left;spacingLeft=30;fontColor=#ED7100;dashed=1;container=1;pointerEvents=0;collapsible=0;recursiveResize=0 +``` + +## Account + +``` +points=[[0,0],[0.25,0],[0.5,0],[0.75,0],[1,0],[1,0.25],[1,0.5],[1,0.75],[1,1],[0.75,1],[0.5,1],[0.25,1],[0,1],[0,0.75],[0,0.5],[0,0.25]];outlineConnect=0;gradientColor=none;html=1;whiteSpace=wrap;fontSize=12;fontStyle=0;shape=mxgraph.aws4.group;grIcon=mxgraph.aws4.group_account;strokeColor=#CD2264;fillColor=none;verticalAlign=top;align=left;spacingLeft=30;fontColor=#CD2264;dashed=0;container=1;pointerEvents=0;collapsible=0;recursiveResize=0 +``` + +## Step Functions Workflow + +``` +points=[[0,0],[0.25,0],[0.5,0],[0.75,0],[1,0],[1,0.25],[1,0.5],[1,0.75],[1,1],[0.75,1],[0.5,1],[0.25,1],[0,1],[0,0.75],[0,0.5],[0,0.25]];outlineConnect=0;gradientColor=none;html=1;whiteSpace=wrap;fontSize=12;fontStyle=0;container=1;pointerEvents=0;collapsible=0;recursiveResize=0;shape=mxgraph.aws4.group;grIcon=mxgraph.aws4.group_aws_step_functions_workflow;strokeColor=#CD2264;fillColor=none;verticalAlign=top;align=left;spacingLeft=30;fontColor=#CD2264;dashed=0 +``` + +## Edge Styles + +### Standard directional + +``` +edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;elbow=vertical;startArrow=none;endFill=1;strokeColor=#545B64;rounded=0 +``` + +### Bidirectional + +``` +edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;elbow=vertical;startArrow=block;startFill=1;endFill=1;strokeColor=#545B64;rounded=0 +``` + +### Dashed (async/optional) + +``` +edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;elbow=vertical;startArrow=none;endFill=1;strokeColor=#545B64;rounded=0;dashed=1 +``` + +### Open arrow (data flow) + +``` +edgeStyle=orthogonalEdgeStyle;html=1;endArrow=open;elbow=vertical;startArrow=none;endFill=0;strokeColor=#545B64;rounded=0 +``` + +## Useful Style Properties + +| Property | Values | Use for | +| ----------------------------------------- | --------- | --------------------------------------------- | +| `rounded=1` | 0/1 | Rounded corners | +| `fillColor`/`strokeColor`/`fontColor` | Hex color | Background / border / text color | +| `shape=cylinder3` / `ellipse` / `rhombus` | keyword | Database cylinders / circles / diamonds | +| `container=1` | 0/1 | Enable container behavior | +| `pointerEvents=0` | 0/1 | Prevent container capturing child connections | +| `exitX`/`exitY`/`entryX`/`entryY` | 0-1 | Edge exit/entry points on shapes | +| `jettySize=auto` | auto/px | Port spacing on orthogonal edges | diff --git a/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/layout-guidelines.md b/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/layout-guidelines.md new file mode 100644 index 00000000..28bdb4a9 --- /dev/null +++ b/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/layout-guidelines.md @@ -0,0 +1,99 @@ +# Layout Guidelines + +Spacing, edge routing, overlap prevention, and placement rules for AWS architecture diagrams. + +## Spacing and Overlap Prevention + +- 180px horizontal / 120px vertical gaps between 120px service group containers +- Group padding: 30px all sides; children start at y=40, x=20 minimum +- ~20px label height below each 48x48 icon; 60px gap between vertical tiers +- Edge labels MUST NOT overlap icons or text — use `y` offset to shift; 30px clearance from arrow endpoints to italic text +- Standalone services 200px+ from top-left corner of Region/VPC groups +- Align all positions to grid multiples of 10 + +## Complex Diagram Scaling (13+ services) + +For diagrams with 13+ services, increase spacing: + +- Horizontal: 220px; Vertical: 160px; Page: `pageWidth=1600;pageHeight=1200` minimum +- Route long-distance edges around clusters using explicit waypoints (``) +- Arrows MUST NOT cross through service containers — use waypoints to route around them +- For non-adjacent service connections, ALWAYS add explicit waypoints to route around intervening containers + +## Edge Routing + +Study `example-event-driven.drawio` and `example-complex-platform.drawio` for correct edge routing patterns. + +**Basic rules:** + +- Use `edgeStyle=orthogonalEdgeStyle` for right-angle connectors +- For simple adjacent connections, let draw.io auto-route — do NOT set entry/exit points +- Leave 20px straight segment before target and after source for arrowheads +- Edges leave PERPENDICULAR to the container face and route OUTWARD — first segment MUST move AWAY from the container (exit bottom → go DOWN, exit right → go RIGHT) + +**Multiple edges from one service** (CRITICAL): + +- When a service has 2+ outgoing edges, each edge MUST exit from a DIFFERENT side or a different point on the same side +- Example: Lambda -> AgentCore (`exitX=0.5;exitY=1` bottom), Lambda -> Step Functions (`exitX=1;exitY=0.5` right), Lambda -> EventBridge (`exitX=1;exitY=0.75` right-lower) +- When 2+ edges enter the same target from the same direction, offset entry points: `entryX=0.25;entryY=0` and `entryX=0.5;entryY=0` (not both at 0.5) + +**Waypoints for non-adjacent routing** (CRITICAL): + +- When an edge must route AROUND intervening containers, add explicit waypoints using `` inside the edge's `` +- Create clean L-shaped (2 waypoints) or U-shaped (3 waypoints) paths +- Route waypoints through clear lanes between container rows/columns +- Example: To route from Lambda (right side) around to DynamoDB (below), exit right then create a vertical lane: `exit=(1,0.25)` -> waypoint at (x_far_right, y_lambda) -> waypoint at (x_far_right, y_dynamo) -> enters DynamoDB from right +- See the edge patterns in `example-event-driven.drawio` for real examples with 2-3 waypoints per edge + +## Handling Overlaps + +**Always add `labelBackgroundColor=none`** to every edge label. NEVER use `labelBackgroundColor=#ffffff` — it breaks dark mode adaptive contrast. + +Only reroute an edge when the overlap is severe and the reroute is simple and clean. Label zone footprints: 78px icons = ~103px tall (with label), 48px icons = ~68px tall, group labels = 30px at top-left. + +For parallel edges sharing a corridor, offset by 20px using explicit waypoints and spread connections across different anchor points. + +## Layout Patterns + +- **Top-to-bottom (tiered)**: Best for VPC architectures with user -> LB -> compute -> DB flow +- **Left-to-right (pipeline)**: Best for data pipelines and CI/CD +- **Column-based (reference architecture)**: Best for complex multi-service platforms with labeled columns + +## Step Badges and Legend + +For complex diagrams (7+ services or multiple branching paths): + +**On-diagram badges**: Teal `#007CBD` 28x28 rounded rectangles near arrow source ends. Place at **source end** (NOT midpoint). Offset 20px above/left. Min 10px clearance from icons/labels. + +**Right sidebar legend**: Panel at `x = diagram_right_edge + 40`, `y=30`. Teal badges (40x38) + bold title + bullet descriptions. All step text MUST use `color: light-dark(...)` for dark mode. Increase `mxGraphModel dx` to accommodate. + +**Legend height MUST match diagram**: `legend-outer` height from `y=30` to AWS Cloud bottom + 20px. MUST NOT cover diagram elements. See `xml-templates-structure.md` for XML, `style-guide.md` for rules. + +**Auxiliary/monitoring services**: ONLY CloudWatch, CloudTrail, X-Ray, and IAM are auxiliary. No step numbers, no edges. Place inside a dashed, unfilled rectangle (`rounded=0;fillColor=none;dashed=1;verticalAlign=top`) labeled "Auxiliary Services". Must be INSIDE AWS Cloud, in a free corner, not covered by legend. Use correct tint colors (CloudWatch/X-Ray: `#FCE4EC`/`#E7157B`, IAM: `#FFEBEE`/`#DD344C`) — NOT gray. Add italic legend note BELOW step descriptions, ABOVE Line Styles box. **All other services are primary** and MUST have edges and step numbers. + +**Decision points**: Maximum 1-2 per diagram. Use `fontStyle=2` (italic) for `[condition]` text on edge labels. Dashed arrows ONLY for failure/fallback paths. + +## Service Placement + +| Service | Correct Container | +| ------------------------------------------------ | -------------------------- | +| ALB, NAT Gateway, Bastion | Public subnet | +| EC2, ECS/Fargate, Lambda (VPC), RDS, ElastiCache | Private subnet | +| Transit Gateway, VPN Gateway | VPC level (not in subnet) | +| Route 53, CloudFront, S3, IAM, CloudWatch | Outside VPC | +| Users, On-premises | Outside AWS Cloud boundary | + +**External actor coordinates**: External actors MUST have coordinates that place them visually OUTSIDE the AWS Cloud group rectangle — at least 40px from the boundary. + +## Layout Sizing Reference + +| Element | Width | Height | +| ---------------------- | --------- | -------- | +| Service icon | 78 | 78 | +| Small resource icon | 48 | 48 | +| Text label | varies | 20 | +| VPC group (typical) | 800-1200 | 500-800 | +| Subnet group (typical) | 350-550 | 400-700 | +| AZ group (typical) | 380-580 | 420-720 | +| Region group (typical) | 900-1400 | 600-900 | +| AWS Cloud group | 1000-1500 | 700-1000 | diff --git a/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/post-processing.md b/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/post-processing.md new file mode 100644 index 00000000..1e4c9efe --- /dev/null +++ b/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/post-processing.md @@ -0,0 +1,15 @@ +# Post-Processing Pipeline + +Deterministic fixers run automatically via the `validate-drawio.sh` PostToolUse hook, in this order: + +1. **fix_nesting.py** — Sets Region `container=0`, re-parents children to root +2. **fix_icon_colors.py** — Corrects service icon fillColor to match category +3. **fix_step_badges.py** — Nudges overlapping step badges apart +4. **fix_placement.py** — Moves external actors below the title block (y >= 140) +5. **fix_legend_size.py** — Resizes legend panel to match diagram height + +All scripts are in `scripts/lib/`. The pipeline is orchestrated by `scripts/lib/post_process_drawio.py`, which chains them in sequence. No manual invocation is needed when using the PostToolUse hook. + +The PostToolUse hook fires on all Edit/Write operations but exits immediately (<10ms) for non-.drawio files. + +**Dependency**: The pipeline requires `defusedxml` (`pip3 install defusedxml>=0.7.1`). If missing, the hook skips validation and shows an install prompt. diff --git a/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/style-guide.md b/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/style-guide.md new file mode 100644 index 00000000..242a13e9 --- /dev/null +++ b/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/style-guide.md @@ -0,0 +1,100 @@ +# AWS Reference Architecture Style Guide + +Style rules and visual standards. For XML code blocks, see `xml-templates-structure.md` and `xml-templates-examples.md`. + +**Primary visual references**: `example-multi-region-active-active.drawio`, `example-event-driven.drawio`, `example-microservices.drawio`, and `example-saas-backend.drawio` demonstrate all patterns below. + +## Title + Subtitle Block + +Every diagram MUST have a title group: title (30px bold), subtitle (16px), orange separator (`strokeColor=#FF9900`). Title = architecture name, subtitle = what it does. Width MUST span the full diagram. See `xml-templates-structure.md` for XML. + +## Dark/Light Adaptive Contrast + +| Element | Property | Value | +| ---------------------- | ------------- | ------------------------------------------------- | +| AWS Cloud fills | `fillColor` | `light-dark(#232F3E0D,#232F3E0D)` | +| Region fills | `fillColor` | `light-dark(#0C7B7D0D,#0C7B7D0D)` | +| VPC fills | `fillColor` | `light-dark(#8C4FFF0D,#8C4FFF0D)` | +| Public subnet fills | `fillColor` | `light-dark(#2488140D,#2488140D)` | +| Private subnet fills | `fillColor` | `light-dark(#147EBA0D,#147EBA0D)` | +| Users container stroke | `strokeColor` | `light-dark(#666666,#D4D4D4)` | +| Legend background | `fillColor` | `light-dark(#EDF3FF,#305363)` | +| Legend step text | inline CSS | `color: light-dark(rgb(0,0,0), rgb(255,255,255))` | +| Line Styles box | `fillColor` | `light-dark(#F5F5F5,#29393B)` | + +**Hex+alpha**: `#0C7B7D0D` = teal at ~5% opacity (last two hex digits are alpha). Add `fillStyle=auto;` when using `light-dark()` fills. + +## Users Container + +Users and external actors MUST be wrapped in a visible container for dark mode. See `xml-templates-structure.md` for XML. + +- Container `fillColor=#f5f5f5` provides contrast in dark mode +- `strokeColor=light-dark(#666666,#D4D4D4)` adapts to mode +- Icon `value=""` (empty) since the container carries the label +- Shadow enabled for visual depth + +## Right Sidebar Step Legend + +Legend panel positioned to the RIGHT of the main diagram. See `xml-templates-structure.md` for XML. + +- Position: `x = diagram_right_edge + 40`, same `y` as title group +- Panel width: ~650px, adaptive background `light-dark(#EDF3FF,#305363)` +- Step entries: teal badge (40x38) at left + text block (width ~548) at x=52 +- Each step `` MUST include `color: light-dark(rgb(0,0,0), rgb(255,255,255))` +- Badge style: `strokeColor=default;strokeWidth=2;shadow=1;glass=0` +- Stack entries vertically with ~10px gap + +## Step Number Badges (On-Diagram) + +Small teal `#007CBD` badges (28x28, fontSize=16) placed near arrows. See `xml-templates-structure.md` for XML. + +- Place near arrow source end, offset 20px above/left +- Minimum clearance: 10px from any icon, container edge, or edge label +- Style: `strokeColor=default;strokeWidth=2;shadow=1;glass=0` + +## Service Group Containers + +### Category Tint Colors + +| Category | fillColor (tint) | strokeColor | Example Services | +| -------------------- | ---------------- | ----------- | ------------------------------------- | +| Compute | `#FFF2E8` | `#ED7100` | Lambda, EC2, ECS, Fargate | +| Database | `#F5E6F7` | `#C925D1` | DynamoDB, RDS, Aurora | +| Analytics/Networking | `#EDE7F6` | `#8C4FFF` | API Gateway, VPC, CloudFront, Kinesis | +| Storage | `#E8F5E9` | `#3F8624` | S3, EBS, EFS | +| App Integration | `#FCE4EC` | `#E7157B` | EventBridge, SQS, SNS, Step Functions | +| AI/ML | `#E0F2F1` | `#01A88D` | SageMaker, Bedrock | +| Security | `#FFEBEE` | `#DD344C` | IAM, Cognito, WAF, KMS | +| IoT | `#E8F5E9` | `#1A9C37` | IoT Core, Greengrass, IoT Analytics | +| General/Auxiliary | `#F5F5F5` | `#666666` | CloudWatch, auxiliary services | + +Container 120x120, icon 48x48 at (x=36, y=30). See `xml-templates-structure.md` for XML. + +## Group Shapes with Adaptive Fills + +Region: `light-dark(#0C7B7D0D,#0C7B7D0D)` / `#00A4A6`. VPC: `light-dark(#8C4FFF0D,#8C4FFF0D)` / `#8C4FFF`. Public subnet: `light-dark(#2488140D,#2488140D)` / `#248814`. Private subnet: `light-dark(#147EBA0D,#147EBA0D)` / `#147EBA`. Add `fillStyle=auto;` when using `light-dark()` fills. + +## Font Standard + +**Default mode**: `fontFamily=Helvetica`. **Sketch mode**: `fontFamily=Comic Sans MS`. Do NOT use Amazon Ember or other fonts. + +Font size hierarchy: + +- Title: 30px bold | Subtitle: 16px | Group titles: 14px bold | Container labels: 12px bold | Service labels: 10px | Edge labels: 11px | Legend badge: 22px bold (legend) / 16px bold (on-diagram) | Legend text: 14px + +## Sketch Mode + +Add `sketch=1;curveFitting=1;jiggle=2` to all non-icon elements. Keep `sketch=0` on service icons. See `example-sketch.drawio`. + +| Element | Default | Sketch | +| ----------------- | ------------------- | ----------------------------------------------------------------------------- | +| Font | Helvetica | Comic Sans MS | +| Container fills | static tint | `light-dark(LIGHT_TINT,#000000)` | +| Icon label text | `fontColor=#232F3E` | `` | +| Users container | `fillColor=#f5f5f5` | `fillColor=light-dark(#F5F5F5,#000000);fontColor=light-dark(#333333,#FAFAFA)` | +| Badge number text | `fontColor=#FFFFFF` | `` | +| Legend badge | `fontColor=#FFFFFF` | `fontColor=light-dark(#000000,#121212)` | + +## AgentCore Icons + +Use `resIcon=mxgraph.aws4.bedrock_agentcore` (NOT `mxgraph.aws4.bedrock`) for Amazon Bedrock AgentCore services. See `example-agentcore.drawio`. diff --git a/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/xml-rules.md b/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/xml-rules.md new file mode 100644 index 00000000..936f7080 --- /dev/null +++ b/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/xml-rules.md @@ -0,0 +1,89 @@ +# XML Generation Rules + +Structural patterns and constraints for AWS architecture diagram XML. + +## AWS4 Shape Styles + +ALWAYS use the `mxgraph.aws4.*` namespace. Reference `aws4-shapes-services.md` and `aws4-shapes-resources.md` for valid shape names. + +There are two style patterns — the difference matters for rendering: + +**Service icon (resourceIcon)** — Main AWS services. Renders the colored square icon. The `points` array gives 16 connection anchors: + +``` +sketch=0;points=[[0,0,0],[0.25,0,0],[0.5,0,0],[0.75,0,0],[1,0,0],[0,1,0],[0.25,1,0],[0.5,1,0],[0.75,1,0],[1,1,0],[0,0.25,0],[0,0.5,0],[0,0.75,0],[1,0.25,0],[1,0.5,0],[1,0.75,0]];outlineConnect=0;fontColor=#16191F;fillColor={CATEGORY_COLOR};strokeColor=#ffffff;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;fontSize=12;fontStyle=0;aspect=fixed;shape=mxgraph.aws4.resourceIcon;resIcon=mxgraph.aws4.{shape_name} +``` + +**Sub-resource icon** — Service sub-components (glue_crawlers, ecs_task, etc.). Smaller flat icons, 48x48: + +``` +sketch=0;outlineConnect=0;fontColor=#16191F;gradientColor=none;fillColor={CATEGORY_COLOR};strokeColor=none;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;fontSize=12;fontStyle=0;aspect=fixed;pointerEvents=1;shape=mxgraph.aws4.{shape_name} +``` + +## Adding Context to Labels + +Add descriptive sub-text using italic HTML: + +```xml +value="AWS Lambda<div><i>compress queries</i></div>" +``` + +## Label Placement (CRITICAL) + +- **Container `value`** = functional category label (e.g., "DNS", "Compute", "Database", "Auth") — NOT the service name +- **Icon `value`** = service name + optional italic sub-label with `verticalLabelPosition=bottom;verticalAlign=top` +- NEVER put the service name on the container. NEVER put the category label on the icon. + +## Edge Labels + +Edge labels are separate child cells attached to an edge, NOT an attribute on the edge itself. Use `connectable="0"` and `edgeLabel` style with `relative="1"` geometry: + +```xml + + + + + + + + +``` + +The `x` value controls position along the edge (-1 = source, 0 = midpoint, 1 = target). The `y` value offsets perpendicular to the edge. + +## Edges + +- **Always connect edges to service icons**, not to container/group shapes. Target the icon cell ID. +- Use `exitX`/`exitY` and `entryX`/`entryY` (0-1) to control connection sides. Spread connections across different sides. +- **Leave room for arrowheads**: At least 20px straight segment before target and after source. +- Add explicit **waypoints** (``) when edges would overlap. +- Align all nodes to a grid (multiples of 10). + +## Groups and Containers + +- Set `parent="containerId"` on children; children use **relative coordinates** +- Add `container=1;pointerEvents=0;` to group styles — **EXCEPT Region groups which MUST use `container=0`** +- **Region groups are decoration-only**: Services positioned visually inside the region rectangle still have `parent="aws-cloud"` or `parent="1"` with absolute coordinates. This prevents nesting depth from breaking edge auto-routing. +- Full group style strings: `group-styles.md` + +## When to Use Containers vs Flat Layout + +**Prefer flat layouts for most diagrams.** Place all service icons as direct children of the AWS Cloud group with text cells for section labels. This produces the cleanest edge routing. + +**Only use nested containers for real infrastructure boundaries:** VPC, subnets, AZs, regions, security groups, Step Functions workflows, ECS clusters. + +**Do NOT use swimlane containers just to visually group columns** (e.g., "Authentication", "Data Layer"). This causes cross-container edge routing problems and coordinate confusion. Instead use a text cell label above each column: + +```xml + + + +``` + +## External Actors + +Users/clients MUST be in a visible container (`fillColor=#f5f5f5`) with adaptive stroke. Icon `value=""`, label on the container. Edges connect to the container, not the icon. NEVER use `shape=actor`. See `xml-templates-structure.md`. + +**Placement rule**: External actors MUST be placed BELOW the title block (y >= 140). Vertically align with the top of the AWS Cloud group so the edge to the first service runs horizontally. + +**Clear path rule**: Do NOT place any service container between external actors and their first target. Place auth/security services BELOW or ABOVE the main entry flow, not in line with it. diff --git a/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/xml-templates-examples.md b/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/xml-templates-examples.md new file mode 100644 index 00000000..b4166a21 --- /dev/null +++ b/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/xml-templates-examples.md @@ -0,0 +1,75 @@ +# XML Templates — Examples + +Edge, container, and annotation XML snippets. For core structure (title, users, legend), see `xml-templates-structure.md`. + +## Edge with Label + +```xml + + + + + + + + +``` + +## Edge with Explicit Waypoints + +```xml + + + + + + + + +``` + +## Decision Point Annotations + +```xml + + + + + + + + + + + +``` + +## Swimlane Container + +```xml + + + + + + +``` + +## Invisible Group + +```xml + + + + + + +``` + +## Region with Adaptive Fill + +```xml + + + +``` diff --git a/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/xml-templates-structure.md b/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/xml-templates-structure.md new file mode 100644 index 00000000..0b41f098 --- /dev/null +++ b/plugins/deploy-on-aws/skills/aws-architecture-diagram/references/xml-templates-structure.md @@ -0,0 +1,98 @@ +# XML Templates — Core Structure + +Ready-to-use XML code blocks for diagram scaffolding. For edge and container examples, see `xml-templates-examples.md`. + +## Title + Subtitle Block + +```xml + + + + + + + + + + + + + + + +``` + +## Users Container + +```xml + + + + + + + + + +``` + +## Service Group Container + +Container `value` = functional category label (e.g., "DNS", "Compute", "Database"). Icon `value` = service name + optional italic sub-label. NEVER put the service name on the container. + +```xml + + + + + + + + + +``` + +## Legend Panel + Step Entry + +```xml + + + + + + + + + + + + + + + + + + + + + + + + +``` + +## On-Diagram Step Badge + +```xml + + + +``` + +## Line Styles Box + +```xml + + + +```