import streamlit as st import json from jsonschema import validate, ValidationError import re import networkx as nx import matplotlib.pyplot as plt import io import graphviz # Mock existing resources (customizable) existing_resources = { "storageAccount1": { "type": "Microsoft.Storage/storageAccounts", "location": "East US", "status": "existing", "properties": { "accountType": "Standard_LRS" } } } # ARM template schema (simplified version) arm_schema = { "type": "object", "properties": { "$schema": {"type": "string"}, "contentVersion": {"type": "string"}, "parameters": {"type": "object"}, "variables": {"type": "object"}, "resources": { "type": "array", "items": { "type": "object", "required": ["type", "name", "location"], "properties": { "type": {"type": "string"}, "name": {"type": "string"}, "location": {"type": "string"}, "properties": {"type": "object"}, "dependsOn": {"type": "array", "items": {"type": "string"}} } } }, "outputs": {"type": "object"} }, "required": ["$schema", "contentVersion", "resources"] } # Function to validate the ARM template against the schema def validate_template(template): try: validate(instance=template, schema=arm_schema) return True, "Template is valid." except ValidationError as e: return False, f"Template validation error: {e.message}" # Function to resolve template expressions (like parameters and variables) def resolve_template_expressions(template): def resolve_expression(match): expr = match.group(1) parts = expr.split('.') if parts[0] == 'parameters': return str(template.get('parameters', {}).get(parts[1], {}).get('defaultValue', '')) elif parts[0] == 'variables': return str(template.get('variables', {}).get(parts[1], '')) return match.group(0) template_str = json.dumps(template) resolved_str = re.sub(r'\[(\w+\.\w+)\]', resolve_expression, template_str) return json.loads(resolved_str) # Function to estimate the cost of resources def estimate_cost(resources): cost_estimates = { "Microsoft.Storage/storageAccounts": 10, "Microsoft.Compute/virtualMachines": 50, "Microsoft.Web/sites": 30, "Microsoft.Sql/servers": 40, } total_cost = 0 for resource in resources: resource_type = resource.get('type') if resource_type in cost_estimates: total_cost += cost_estimates[resource_type] else: total_cost += 5 return total_cost # Function to create a dependency graph of resources def create_dependency_graph(resources): G = nx.DiGraph() for resource in resources: resource_name = resource.get('name') G.add_node(resource_name) dependencies = resource.get('dependsOn', []) for dep in dependencies: G.add_edge(dep, resource_name) return G # Function to plot the dependency graph def plot_dependency_graph(G): plt.figure(figsize=(12, 8)) pos = nx.spring_layout(G) nx.draw(G, pos, with_labels=True, node_color='lightblue', node_size=3000, font_size=8, font_weight='bold') plt.title("Resource Dependency Graph") return plt # Function to lint the ARM template def lint_template(template): lint_results = [] for resource in template.get('resources', []): if 'tags' not in resource: lint_results.append(f"Warning: Resource '{resource.get('name')}' does not have any tags.") for param_name, param_value in template.get('parameters', {}).items(): if 'metadata' not in param_value or 'description' not in param_value['metadata']: lint_results.append(f"Warning: Parameter '{param_name}' does not have a description.") return lint_results # Function to generate a system diagram def generate_system_diagram(resources): dot = graphviz.Digraph() dot.attr(rankdir='TB') for i, resource in enumerate(resources): resource_type = resource['type'].split('/')[-1] node_id = f"R{i}" dot.node(node_id, f"{resource_type}\n{resource['name']}") for resource in resources: if 'dependsOn' in resource: for dependency in resource['dependsOn']: dep_id = next((f"R{j}" for j, r in enumerate(resources) if r['name'] == dependency), None) if dep_id: dot.edge(dep_id, f"R{resources.index(resource)}") return dot # Main function to simulate the deployment def simulate_deployment(arm_template): try: template = json.loads(arm_template) is_valid, validation_message = validate_template(template) if not is_valid: return [], [], [], [], 0, None, [], None, validation_message resolved_template = resolve_template_expressions(template) resources_to_create = [] resources_to_update = [] resources_to_delete = [] resource_details = [] for resource in resolved_template.get('resources', []): resource_name = resource.get('name') resource_type = resource.get('type') location = resource.get('location') properties = resource.get('properties', {}) if resource_name in existing_resources: existing = existing_resources[resource_name] if (existing['type'] == resource_type and existing['location'] == location and existing['properties'] == properties): resources_to_update.append(resource_name) else: resources_to_create.append(resource_name) else: resources_to_create.append(resource_name) resource_details.append({ "name": resource_name, "type": resource_type, "location": location, "properties": properties, "dependsOn": resource.get('dependsOn', []) }) for resource_name in existing_resources: if resource_name not in [r.get('name') for r in resolved_template.get('resources', [])]: resources_to_delete.append(resource_name) estimated_cost = estimate_cost(resource_details) dependency_graph = create_dependency_graph(resolved_template.get('resources', [])) lint_results = lint_template(template) system_diagram = generate_system_diagram(resource_details) return resources_to_create, resources_to_update, resources_to_delete, resource_details, estimated_cost, dependency_graph, lint_results, system_diagram, "Simulation completed successfully." except json.JSONDecodeError: return [], [], [], [], 0, None, [], None, "Invalid JSON format. Please check your ARM template." # Streamlit UI st.title("Comprehensive ARM Template Simulator") st.subheader("Upload your ARM template or paste it below to simulate its deployment.") uploaded_file = st.file_uploader("Choose an ARM template file", type=["json"]) template_input = st.text_area("Or paste ARM Template JSON here:", height=300) def read_file_contents(file): return file.getvalue().decode("utf-8") if st.button("Simulate Template"): if uploaded_file is not None: file_contents = read_file_contents(uploaded_file) template_to_simulate = file_contents elif template_input: template_to_simulate = template_input else: st.error("Please either upload a file or paste an ARM template to simulate.") st.stop() resources_to_create, resources_to_update, resources_to_delete, resource_details, estimated_cost, dependency_graph, lint_results, system_diagram, message = simulate_deployment(template_to_simulate) st.subheader("Simulation Results:") st.write(message) if resources_to_create or resources_to_update or resources_to_delete: st.write("### Resources to be Created:") st.write(resources_to_create or "No new resources will be created.") st.write("### Resources to be Updated:") st.write(resources_to_update or "No resources will be updated.") st.write("### Resources to be Deleted:") st.write(resources_to_delete or "No resources will be deleted.") st.write("### Detailed Resource Information:") for resource in resource_details: st.write(f"**Name:** {resource['name']}") st.write(f"**Type:** {resource['type']}") st.write(f"**Location:** {resource['location']}") st.json(resource['properties']) st.write(f"### Estimated Monthly Cost: ${estimated_cost}") st.write("### Resource Dependency Graph:") if dependency_graph: fig = plot_dependency_graph(dependency_graph) st.pyplot(fig) st.write("### System Diagram:") if system_diagram: st.graphviz_chart(system_diagram) st.write("### Template Lint Results:") for result in lint_results: st.write(result) else: st.write("No changes detected in the simulation.")