diff --git a/.github/scripts/generate-icon-prompts.py b/.github/scripts/generate-icon-prompts.py new file mode 100644 index 00000000..515aa296 --- /dev/null +++ b/.github/scripts/generate-icon-prompts.py @@ -0,0 +1,236 @@ +#!/usr/bin/env python3 +""" +Generate icon prompts for building blocks missing logo.png files. +Parses README.md frontmatter and creates AI image generation prompts. +""" + +import os +import sys +import yaml +import json +from pathlib import Path + +PLATFORM_COLORS = { + "aws": { + "primary": "#FF9900", + "secondary": "#232F3E", + "accent": "#7FBA00", + "name": "AWS colors: orange (#FF9900), dark blue (#232F3E), and lime green (#7FBA00)" + }, + "azure": { + "primary": "#0078D4", + "secondary": "#00BCF2", + "accent": "#50E6FF", + "name": "Azure colors: blue (#0078D4), cyan (#00BCF2), and light blue (#50E6FF)" + }, + "aks": { + "primary": "#326CE5", + "secondary": "#0078D4", + "accent": "#00BCF2", + "name": "Kubernetes/Azure colors: blue (#326CE5), Azure blue (#0078D4), and cyan (#00BCF2)" + }, + "azuredevops": { + "primary": "#0078D4", + "secondary": "#00BCF2", + "accent": "#005A9E", + "name": "Azure DevOps colors: blue (#0078D4), teal (#00BCF2), and dark blue (#005A9E)" + }, + "gcp": { + "primary": "#4285F4", + "secondary": "#EA4335", + "accent": "#FBBC04", + "name": "Google colors: blue (#4285F4), red (#EA4335), yellow (#FBBC04), and green (#34A853)" + }, + "github": { + "primary": "#6e5494", + "secondary": "#24292e", + "accent": "#8b5cf6", + "name": "GitHub colors: purple (#6e5494), dark gray (#24292e), and bright purple (#8b5cf6)" + }, + "ionos": { + "primary": "#003D7A", + "secondary": "#FF6600", + "accent": "#0096D6", + "name": "IONOS colors: blue (#003D7A), orange (#FF6600), and light blue (#0096D6)" + }, + "kubernetes": { + "primary": "#326CE5", + "secondary": "#00D3E0", + "accent": "#7AB8FF", + "name": "Kubernetes colors: blue (#326CE5), cyan (#00D3E0), and light blue (#7AB8FF)" + }, + "sapbtp": { + "primary": "#0070AD", + "secondary": "#F0AB00", + "accent": "#0078D4", + "name": "SAP colors: blue (#0070AD), gold (#F0AB00), and light blue (#0078D4)" + }, + "stackit": { + "primary": "#00A859", + "secondary": "#007A3D", + "accent": "#7FBA00", + "name": "STACKIT colors: green (#00A859), dark green (#007A3D), and lime (#7FBA00)" + } +} + + +def parse_readme_frontmatter(readme_path): + """Extract YAML frontmatter from README.md""" + with open(readme_path, 'r', encoding='utf-8') as f: + content = f.read() + + if not content.startswith('---'): + return None + + # Extract frontmatter between --- delimiters + parts = content.split('---', 2) + if len(parts) < 3: + return None + + try: + frontmatter = yaml.safe_load(parts[1]) + return frontmatter + except yaml.YAMLError: + return None + + +def get_platform_from_frontmatter(frontmatter): + """Get the primary platform from supportedPlatforms list""" + platforms = frontmatter.get('supportedPlatforms', []) + if not platforms: + return None + return platforms[0] # Use first platform + + +def generate_icon_prompt(name, platform, description): + """Generate an AI image generation prompt for an icon""" + platform_colors = PLATFORM_COLORS.get(platform) + + if not platform_colors: + # Fallback to generic bright colors + color_scheme = "bright, vibrant colors" + else: + color_scheme = platform_colors["name"] + + # Clean up description + clean_description = description.strip().replace('\n', ' ') + + # Generate AI prompt + ai_prompt = f"""Create a professional flat design icon for the meshcloud Building Block ecosystem. + +Purpose: {clean_description} + +Visual Style: +- Plain white background (#FFFFFF) for easy removal in post-processing +- Background will be converted to transparent (see post-processing steps) +- Use {color_scheme} as accent colors +- Maximum 2-3 colors total +- Simple geometric shapes with clean lines +- Flat design (no gradients, shadows, or 3D effects) +- Minimalist, modern appearance + +Composition: +- Square centered layout (NOT horizontal) +- Icon fills the entire canvas edge-to-edge (100% of area) +- No padding or margins around the icon +- Symmetrical arrangement +- Platform-appropriate symbol for {platform.upper()} (e.g., cloud, container, database, server, etc.) + +Style: Enterprise professional, instantly recognizable at small sizes, similar to app icons or logos. +Dimensions: 800x800 pixels""" + + # Generate post-processing instructions + post_processing = """**Step 1: Remove white background with GIMP (free)** + +a) Open image in GIMP +b) Right-click layer → "Add Alpha Channel" +c) Tools → "Select by Color" (Shift+O) +d) Click white background +e) Press Delete key +f) File → Export As → logo.png +g) Set Compression level to 9 → Export + +**Step 2: Resize to 800x800 pixels if needed** + +- GIMP: Image → Scale Image → 800x800px +- Or use any image editor + +**Step 3: Compress with pngquant (free command line tool)** + +- Install: `brew install pngquant` (Mac) or `apt install pngquant` (Linux) +- Run: `pngquant --quality=20-30 logo.png --ext .png --force` +- This reduces file size by 60-80% while maintaining quality + +**Target specs:** 800x800px PNG with transparent background, under 100KB""" + + return { + 'ai_prompt': ai_prompt, + 'post_processing': post_processing + } + +def find_missing_logos(modules_dir): + """Find all buildingblock directories missing logo.png""" + missing = [] + + for root, dirs, files in os.walk(modules_dir): + if 'buildingblock' in root: + buildingblock_path = Path(root) + readme_path = buildingblock_path / 'README.md' + logo_path = buildingblock_path / 'logo.png' + + if readme_path.exists() and not logo_path.exists(): + frontmatter = parse_readme_frontmatter(readme_path) + if frontmatter: + platform = get_platform_from_frontmatter(frontmatter) + name = frontmatter.get('name', 'Unknown') + description = frontmatter.get('description', '') + + # Get relative path from modules directory + rel_path = buildingblock_path.relative_to(modules_dir) + + missing.append({ + 'path': str(rel_path), + 'name': name, + 'platform': platform, + 'description': description, + 'readme_path': str(readme_path), + 'logo_path': str(logo_path) + }) + + return missing + +def main(): + # Get modules directory + repo_root = Path(__file__).parent.parent.parent + modules_dir = repo_root / 'modules' + + if not modules_dir.exists(): + print(f"ERROR: Modules directory not found: {modules_dir}", file=sys.stderr) + sys.exit(1) + + # Find missing logos + missing_logos = find_missing_logos(modules_dir) + + # Generate prompts for each missing logo + results = [] + for item in missing_logos: + prompt_data = generate_icon_prompt( + item['name'], + item['platform'] or 'generic', + item['description'] + ) + + results.append({ + 'name': item['name'], + 'platform': item['platform'], + 'path': item['path'], + 'logo_path': item['logo_path'], + 'ai_prompt': prompt_data['ai_prompt'], + 'post_processing': prompt_data['post_processing'] + }) + + # Output as JSON for GitHub Action to consume + print(json.dumps(results, indent=2)) + +if __name__ == '__main__': + main() diff --git a/.github/workflows/icon-prompts.yml b/.github/workflows/icon-prompts.yml new file mode 100644 index 00000000..fccf3943 --- /dev/null +++ b/.github/workflows/icon-prompts.yml @@ -0,0 +1,104 @@ +name: Generate Icon Prompts for Missing Logos + +on: + pull_request: + types: [opened, synchronize, reopened] + paths: + - 'modules/**/buildingblock/README.md' + +permissions: + pull-requests: write + contents: read + +jobs: + check-missing-logos: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install dependencies + run: | + pip install pyyaml + + - name: Find missing logos and generate prompts + id: generate + run: | + python .github/scripts/generate-icon-prompts.py > prompts.json + COUNT=$(jq 'length' prompts.json) + echo "has_missing=$([ "$COUNT" -gt 0 ] && echo 'true' || echo 'false')" >> $GITHUB_OUTPUT + echo "count=$COUNT" >> $GITHUB_OUTPUT + + - name: Read prompts + if: steps.generate.outputs.has_missing == 'true' + id: read_prompts + run: | + PROMPTS=$(cat prompts.json) + echo "prompts<> $GITHUB_OUTPUT + echo "$PROMPTS" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Generate PR comment + if: steps.generate.outputs.has_missing == 'true' + id: format_comment + uses: actions/github-script@v7 + env: + PROMPTS_JSON: ${{ steps.read_prompts.outputs.prompts }} + with: + result-encoding: string + script: | + const prompts = JSON.parse(process.env.PROMPTS_JSON); + + if (prompts.length === 0) { + return ''; + } + + let comment = '## 🎨 Missing Building Block Icons\n\n'; + comment += `Found **${prompts.length}** building block(s) without \`logo.png\` files.\n\n`; + comment += 'Copy the **AI Prompts** below and use them with your favorite AI image generator (Gemini, DALL-E, Midjourney, Stable Diffusion, etc.).\n\n'; + comment += 'Then follow the **Post-Processing Steps** to prepare the icons for upload.\n\n'; + comment += '---\n\n'; + + for (const item of prompts) { + comment += `### ${item.name}\n\n`; + comment += `**Platform:** \`${item.platform}\`\n\n`; + comment += `**Path:** \`${item.logo_path}\`\n\n`; + comment += '#### AI Prompt (copy this to image generator)\n\n'; + comment += '```\n'; + comment += item.ai_prompt; + comment += '\n```\n\n'; + comment += '#### Post-Processing Instructions\n\n'; + comment += item.post_processing; + comment += '\n\n'; + comment += '---\n\n'; + } + + return comment; + + - name: Find existing comment + if: steps.generate.outputs.has_missing == 'true' + uses: peter-evans/find-comment@v3 + id: find_comment + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: 'github-actions[bot]' + body-includes: '🎨 Missing Building Block Icons' + + - name: Create or update comment + if: steps.generate.outputs.has_missing == 'true' + uses: peter-evans/create-or-update-comment@v4 + with: + comment-id: ${{ steps.find_comment.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + body: ${{ steps.format_comment.outputs.result }} + edit-mode: replace + + - name: Success message + if: steps.generate.outputs.has_missing == 'false' + run: | + echo "✅ All building blocks have logo.png files!" diff --git a/modules/aks/postgresql/buildingblock/logo.png b/modules/aks/postgresql/buildingblock/logo.png index b360079f..b7b245e0 100644 Binary files a/modules/aks/postgresql/buildingblock/logo.png and b/modules/aks/postgresql/buildingblock/logo.png differ diff --git a/modules/aks/starterkit-azuredevops/buildingblock/APP_TEAM_README.md b/modules/aks/starterkit-azuredevops/buildingblock/APP_TEAM_README.md new file mode 100644 index 00000000..b86e55dd --- /dev/null +++ b/modules/aks/starterkit-azuredevops/buildingblock/APP_TEAM_README.md @@ -0,0 +1,514 @@ +# Azure Kubernetes Service (AKS) Starter Kit - Azure DevOps + +## What is it? + +The **AKS Azure DevOps Starter Kit** provides application teams with a pre-configured Kubernetes environment integrated with Azure DevOps. It automates the creation of essential infrastructure, including an Azure DevOps project, Git repository, CI/CD pipelines, and secure Azure service connections using passwordless authentication. + +## When to use it? + +This building block is ideal for teams that: + +- Want to deploy applications on Kubernetes without manual infrastructure setup +- Use Azure DevOps for version control and CI/CD workflows +- Need separate development and production environments with different deployment processes +- Prefer secure, passwordless authentication with Workload Identity Federation (OIDC) +- Want branch-based deployment automation (main → dev, release → prod) + +## Usage Examples + +1. **Deploying a microservice**: A developer creates a complete CI/CD pipeline for a new microservice with separate dev/prod pipelines and namespaces +2. **Team onboarding**: New teams get immediate access to a fully configured Azure DevOps project with AKS integration and role-based access +3. **Multi-stage deployment**: Deploy to dev on every commit to main, deploy to prod on release branch merges with additional safeguards + +## Resources Created + +This building block automates the creation of the following resources: + +- **Azure DevOps Project**: A dedicated project for your application with role-based access control + - **Git Repository**: Initialized with Dockerfile, Kubernetes manifests, and pipeline definitions + - **Dev Pipeline**: Builds and deploys from `main` branch to development environment + - **Prod Pipeline**: Builds and deploys from `release` branch to production environment + +- **Development Project**: You, as the creator, will have Project Admin access + - **AKS Namespace**: A dedicated Kubernetes namespace for development + - **Azure Service Connection**: Connects the pipeline to Azure (auto-authorized for faster iteration) + +- **Production Project**: You, as the creator, will have Project Admin access + - **AKS Namespace**: A dedicated Kubernetes namespace for production + - **Azure Service Connection**: Connects the pipeline to Azure (manual authorization for security) + +## Shared Responsibilities + +| Responsibility | Platform Team | Application Team | +| -------------------------------------------- | ------------- | ---------------- | +| Provision and manage AKS cluster | ✅ | ❌ | +| Create Azure DevOps project | ✅ | ❌ | +| Set up CI/CD pipelines | ✅ | ❌ | +| Configure service connections | ✅ | ❌ | +| Build and scan Docker images | ✅ | ❌ | +| Manage Kubernetes namespaces (dev/prod) | ✅ | ❌ | +| Manage resources inside namespaces | ❌ | ✅ | +| Develop and maintain application source code | ❌ | ✅ | +| Maintain pipeline YAML files | ❌ | ✅ | +| Merge to release branch for prod deployments | ❌ | ✅ | + +--- + +## Getting Started + +### 1. Access Your Azure DevOps Project + +After the starter kit is deployed, you'll receive a summary with links to your Azure DevOps project. Navigate to: + +``` +https://dev.azure.com// +``` + +### 2. Clone Your Repository + +Clone the Git repository to your local machine: + +```bash +git clone https://dev.azure.com///_git/ +cd +``` + +### 3. Understand the Branch Strategy + +- **main branch**: Development work happens here + - Commits trigger the **Dev Pipeline** + - Deploys automatically to the **dev AKS namespace** + +- **release branch**: Production releases happen here + - Commits trigger the **Prod Pipeline** + - Deploys to the **prod AKS namespace** + - Requires manual service connection authorization on first run + +### 4. Make Your First Deployment + +#### Deploy to Development + +```bash +# Make changes to your code +git add . +git commit -m "My first change" +git push origin main +``` + +The Dev Pipeline will automatically: +1. Build your Docker image +2. Run security scans +3. Deploy to your dev AKS namespace +4. Make it available at `https://-dev.likvid-k8s.msh.host` + +#### Deploy to Production + +```bash +# First, ensure your main branch is stable +git checkout release +git merge main +git push origin release +``` + +The Prod Pipeline will: +1. Pause for service connection authorization (first run only) +2. Build your Docker image +3. Run security scans +4. Deploy to your prod AKS namespace +5. Make it available at `https://.likvid-k8s.msh.host` + +**First Run Authorization**: When the prod pipeline runs for the first time, it will pause and ask you to authorize the service connection. Click **View** → **Permit** to continue. + +--- + +## Repository Structure + +Your repository includes the following files: + +``` +├── azure-pipelines-dev.yml # Dev pipeline definition +├── azure-pipelines-prod.yml # Prod pipeline definition +├── Dockerfile # Container image build instructions +├── k8s/ +│ ├── deployment.yaml # Kubernetes deployment manifest +│ ├── service.yaml # Kubernetes service manifest +│ └── ingress.yaml # Kubernetes ingress manifest +└── src/ + └── [your application code] +``` + +--- + +## Pipeline Variables + +Both pipelines have the following variables pre-configured: + +| Variable | Description | Example | +|----------|-------------|---------| +| `AKS_NAMESPACE` | Your dedicated Kubernetes namespace | `myapp-dev-abc123` | +| `ENVIRONMENT` | Environment name (development or production) | `development` | +| `SERVICE_CONNECTION` | Azure service connection name | `Azure-AKS-Dev` | +| `DOMAIN_NAME` | Application subdomain | `myapp-dev` | + +You can reference these in your pipeline YAML: + +```yaml +- script: | + echo "Deploying to $(AKS_NAMESPACE)" + echo "Environment: $(ENVIRONMENT)" +``` + +--- + +## Customizing Your Pipelines + +### Modify Pipeline Steps + +Edit `azure-pipelines-dev.yml` or `azure-pipelines-prod.yml` to customize the build and deployment process: + +```yaml +trigger: + branches: + include: + - main # or 'release' for prod + +pool: + vmImage: 'ubuntu-latest' + +variables: + - name: DOCKER_REGISTRY + value: 'myregistry.azurecr.io' + +steps: + - task: Docker@2 + displayName: 'Build Docker Image' + inputs: + command: build + dockerfile: 'Dockerfile' + tags: | + $(Build.BuildId) + latest + + - task: AzureCLI@2 + displayName: 'Deploy to AKS' + inputs: + azureSubscription: $(SERVICE_CONNECTION) + scriptType: 'bash' + scriptLocation: 'inlineScript' + inlineScript: | + kubectl apply -f k8s/ --namespace=$(AKS_NAMESPACE) +``` + +### Add Environment Secrets + +Store sensitive values in Azure Key Vault and reference them in your pipeline: + +1. Go to **Pipelines** → **Library** +2. Create a **Variable Group** linked to Azure Key Vault +3. Reference in pipeline: + +```yaml +variables: + - group: my-secrets + +steps: + - script: | + echo "Database connection: $(DB_CONNECTION_STRING)" +``` + +--- + +## Monitoring Your Deployments + +### View Pipeline Runs + +Navigate to **Pipelines** in your Azure DevOps project: + +- **Dev Pipeline**: Shows all deployments to development +- **Prod Pipeline**: Shows all deployments to production + +Each run shows: +- Build logs +- Test results +- Deployment status +- Security scan results + +### Access Your Applications + +Once deployed, your applications are available at: + +- **Dev**: `https://-dev.likvid-k8s.msh.host` +- **Prod**: `https://.likvid-k8s.msh.host` + +### View AKS Namespace Resources + +Access your Kubernetes namespaces via meshStack: + +1. Navigate to your **Dev Project** or **Prod Project** +2. Click on the **AKS Tenant** +3. Use `kubectl` or the Azure portal to inspect resources + +--- + +## Security Features + +### Workload Identity Federation (Passwordless Authentication) + +Your service connections use **Workload Identity Federation (OIDC)** for authentication: + +✅ **No secrets to manage** - authentication uses short-lived tokens +✅ **Automatic token rotation** - tokens expire quickly and are refreshed automatically +✅ **Zero maintenance** - no manual credential rotation needed +✅ **Better security** - no long-lived credentials that can leak + +### Branch Policies + +The main branch has policies enforced: + +- **Minimum reviewers**: At least 1 reviewer required for PRs +- **Work item linking**: Encourages linking code changes to work items +- **No direct commits**: All changes go through pull requests + +### Manual Authorization for Production + +Production deployments require explicit authorization: + +- First time a pipeline runs, you must approve the service connection +- Prevents accidental or unauthorized deployments to production +- Can be authorized permanently for trusted pipelines + +--- + +## Team Collaboration + +### Inviting Team Members + +Add team members via meshStack: + +1. Navigate to your **Dev Project** or **Prod Project** +2. Go to **Access Management** → **Role Mapping** +3. Invite users with appropriate roles: + - **Project Admin**: Full control over the project + - **Project User**: Can view and manage resources + - **Project Reader**: Read-only access + +### Azure DevOps Roles + +Team members are automatically assigned Azure DevOps roles based on their meshStack roles: + +| meshStack Role | Azure DevOps Role | +|----------------|------------------| +| Workspace Owner | Project Administrator | +| Workspace Manager | Contributor | +| Workspace Member | Reader | + +--- + +## Best Practices + +### Branch Strategy + +**Recommended Workflow**: + +1. Create feature branches from `main` +2. Develop and test locally +3. Open PR to `main` for code review +4. Merge to `main` → triggers dev deployment +5. Test in dev environment +6. When ready for release, merge `main` to `release` → triggers prod deployment + +**Example**: + +```bash +# Create feature branch +git checkout -b feature/new-api +# ... make changes ... +git commit -m "Add new API endpoint" +git push origin feature/new-api + +# Open PR to main (via Azure DevOps UI) +# After merge, dev deployment happens automatically + +# When ready for production +git checkout release +git merge main +git push origin release +# Prod deployment happens after authorization +``` + +### Service Connection Authorization + +**Development**: +- Auto-authorized for convenience +- Faster iteration and testing + +**Production**: +- Manual authorization required on first use +- Adds security checkpoint +- Can be permanently authorized for specific pipelines after initial approval + +### Resource Management + +**Inside Your Namespace**: +- ✅ Deploy applications +- ✅ Create services, config maps, secrets +- ✅ Manage resource quotas and limits + +**Outside Your Namespace**: +- ❌ Cannot modify cluster-wide resources +- ❌ Cannot access other teams' namespaces +- ❌ Cannot change network policies + +### Cost Optimization + +- Use resource requests and limits in Kubernetes manifests +- Clean up unused resources regularly +- Monitor resource usage via meshStack project tags + +--- + +## Troubleshooting + +### Pipeline Fails: "Service connection not found" + +**Cause**: Service connection name mismatch or not authorized + +**Solution**: +1. Verify service connection name in pipeline YAML matches exactly (case-sensitive) +2. Check if manual authorization is required (go to pipeline run and click "Permit") +3. Ensure service connection exists in **Project Settings** → **Service connections** + +### Pipeline Fails: "Insufficient permissions" + +**Cause**: Service principal lacks required permissions + +**Solution**: Contact the Platform Team to verify service principal role assignment. Required roles: +- **Contributor**: For resource deployment +- **Reader**: For read-only operations + +### Service Connection Shows as Invalid + +**Cause**: Service principal or federated credential configuration issue + +**Solution**: Contact the Platform Team to verify: +- Service principal exists and is active +- Federated identity credential is configured correctly +- Azure AD application is properly set up + +### Cannot Access AKS Namespace + +**Cause**: Missing Kubernetes RBAC permissions + +**Solution**: +1. Verify you have Project Admin or Project User role in meshStack +2. Ensure the tenant (AKS namespace) is fully provisioned +3. Check with Platform Team if custom RBAC policies are in place + +### Application Not Accessible + +**Cause**: Ingress or service misconfiguration + +**Solution**: +1. Check ingress manifest for correct hostname +2. Verify service is targeting correct pods (label selectors) +3. Ensure pods are running: `kubectl get pods -n ` +4. Check ingress controller logs + +### First Production Deployment Stuck + +**Cause**: Service connection requires manual authorization + +**Solution**: This is expected behavior. Go to the pipeline run and: +1. Click **View** next to the authorization request +2. Click **Permit** to authorize the service connection +3. Pipeline will continue automatically + +--- + +## Advanced Configuration + +### Adding Stages and Environments + +Modify your pipeline to add approval gates: + +```yaml +stages: + - stage: Build + jobs: + - job: BuildJob + steps: + - script: docker build -t myapp . + + - stage: DeployDev + dependsOn: Build + jobs: + - deployment: DeployDevJob + environment: development + strategy: + runOnce: + deploy: + steps: + - script: kubectl apply -f k8s/ + + - stage: DeployProd + dependsOn: DeployDev + jobs: + - deployment: DeployProdJob + environment: production # Requires manual approval + strategy: + runOnce: + deploy: + steps: + - script: kubectl apply -f k8s/ +``` + +### Integrating with Azure Container Registry + +Update your pipeline to use ACR: + +```yaml +variables: + - name: ACR_NAME + value: 'myregistry.azurecr.io' + +steps: + - task: Docker@2 + displayName: 'Build and Push to ACR' + inputs: + command: buildAndPush + repository: 'myapp' + containerRegistry: 'Azure-ACR-Connection' + tags: | + $(Build.BuildId) +``` + +### Running Tests Before Deployment + +Add test stages to your pipeline: + +```yaml +steps: + - script: | + npm install + npm test + displayName: 'Run Unit Tests' + + - task: PublishTestResults@2 + inputs: + testResultsFormat: 'JUnit' + testResultsFiles: '**/test-results.xml' +``` + +--- + +## Related Documentation + +- [Azure DevOps Pipelines](https://learn.microsoft.com/en-us/azure/devops/pipelines/) +- [Workload Identity Federation](https://learn.microsoft.com/en-us/azure/devops/pipelines/library/connect-to-azure) +- [AKS Best Practices](https://learn.microsoft.com/en-us/azure/aks/best-practices) +- [Kubernetes Documentation](https://kubernetes.io/docs/home/) +- [meshStack Documentation](https://docs.meshcloud.io/) + +--- + +🎉 **Happy coding!** Your AKS Azure DevOps environment is ready for production workloads. diff --git a/modules/aks/starterkit-azuredevops/buildingblock/README.md b/modules/aks/starterkit-azuredevops/buildingblock/README.md new file mode 100644 index 00000000..6aff76ec --- /dev/null +++ b/modules/aks/starterkit-azuredevops/buildingblock/README.md @@ -0,0 +1,250 @@ +--- +name: AKS Starter Kit - Azure DevOps +supportedPlatforms: + - aks +description: Provides a complete AKS development environment with Azure DevOps project, Git repository, CI/CD pipelines, and separate dev/prod namespaces with passwordless authentication. +--- + +# AKS Starter Kit - Azure DevOps + +This building block creates a complete AKS application development environment integrated with Azure DevOps, including: + +- **Azure DevOps Project**: Dedicated project with role-based access control +- **Git Repository**: Initialized with application templates and deployment manifests +- **Dev Environment**: meshStack project + AKS namespace + service connection + pipeline +- **Prod Environment**: meshStack project + AKS namespace + service connection + pipeline +- **CI/CD Pipelines**: Automated deployments triggered by branch commits +- **Service Connections**: Passwordless authentication using Workload Identity Federation + +## Architecture + +``` +Azure DevOps Project +├── Git Repository +├── Dev Pipeline (main branch) → Dev AKS Namespace +└── Prod Pipeline (release branch) → Prod AKS Namespace +``` + +## Resources Created + +### Azure DevOps Resources +- **Project**: Container for all Azure DevOps resources +- **Repository**: Git repository with application code and manifests +- **Pipelines**: Two pipelines (dev and prod) with separate triggers + +### meshStack Resources +- **Dev Project + Tenant**: Development AKS namespace +- **Prod Project + Tenant**: Production AKS namespace +- **Service Connections**: Azure service connections for both environments +- **User Bindings**: Creator assigned as Project Admin on both projects + +## Deployment Flow + +### Development +1. Developer commits to `main` branch +2. Dev pipeline triggers automatically +3. Builds container image +4. Scans with security tools +5. Deploys to dev AKS namespace + +### Production +1. Developer creates PR from `main` to `release` +2. PR review and merge +3. Prod pipeline triggers +4. Same build and scan process +5. Requires manual authorization (first run) +6. Deploys to prod AKS namespace + +## Security Features + +- **Workload Identity Federation**: Passwordless authentication between Azure DevOps and Azure +- **Branch Policies**: Enforced code reviews on main branch +- **Manual Authorization**: Production deployments require explicit approval +- **Least Privilege**: Service principals have minimal required permissions +- **Separate Environments**: Isolated dev and prod namespaces + +## Prerequisites + +The following must be configured before using this building block: + +1. **Azure DevOps Organization**: Active organization with PAT token stored in Key Vault +2. **Service Principals**: Pre-created for dev and prod environments with appropriate role assignments +3. **AKS Landing Zones**: Configured landing zones for dev and prod +4. **Building Block Definitions**: Azure DevOps project, repository, pipeline, and service connection definitions must exist + +## Variables + +### Required Variables + +- `workspace_identifier`: meshStack workspace +- `name`: Name for projects, repository, and namespaces +- `full_platform_identifier`: AKS platform identifier +- `landing_zone_dev_identifier`: Dev AKS landing zone +- `landing_zone_prod_identifier`: Prod AKS landing zone +- `azdevops_*_definition_*_uuid`: UUIDs for all Azure DevOps building block definitions +- `azdevops_organization_name`: Azure DevOps organization name +- `creator`: Creator user information for RBAC +- `dev_azure_subscription_id`: Azure subscription for dev +- `dev_service_principal_id`: Service principal for dev +- `dev_application_object_id`: Azure AD app object ID for dev +- `prod_azure_subscription_id`: Azure subscription for prod +- `prod_service_principal_id`: Service principal for prod +- `prod_application_object_id`: Azure AD app object ID for prod +- `azure_tenant_id`: Azure AD tenant ID + +### Optional Variables + +- `project_tags_yaml`: YAML configuration for project tags (default: empty) +- `repository_init_type`: Repository initialization (Clean or Import, default: Clean) +- `enable_branch_policies`: Enable branch policies (default: true) +- `minimum_reviewers`: Minimum PR reviewers (default: 1) + +## Outputs + +- `dev_link`: URL to dev environment application +- `prod_link`: URL to prod environment application +- `azdevops_project_url`: Azure DevOps project URL +- `azdevops_repository_url`: Git repository URL +- `summary`: Detailed summary with next steps and resource links + +## Integration with Other Building Blocks + +This starter kit orchestrates multiple building blocks: + +1. **Azure DevOps Project** (`azuredevops/project`) +2. **Azure DevOps Repository** (`azuredevops/repository`) +3. **Azure DevOps Pipeline** (2x - `azuredevops/pipeline`) +4. **Azure DevOps Service Connection** (2x - `azuredevops/service-connection-subscription`) + +Parent-child relationships ensure proper dependency ordering and resource cleanup. + +## Best Practices + +### Naming Convention +The `name` variable is normalized to create consistent identifiers: +- Special characters removed +- Converted to lowercase +- Spaces/hyphens normalized +- Random suffix added to ensure uniqueness + +### Branch Strategy +- `main`: Development work, auto-deploys to dev +- `release`: Production releases, auto-deploys to prod +- Feature branches: Create from main, merge back to main + +### Service Connections +- Dev: Auto-authorized for faster development iteration +- Prod: Manual authorization for enhanced security + +### Project Tags +Use `project_tags_yaml` to apply consistent tagging across dev and prod projects for cost allocation and governance. + +## Troubleshooting + +### Pipeline Authorization Required +**Symptom**: Prod pipeline pauses asking for resource authorization + +**Solution**: This is expected on first run. Click "View" → "Permit" to authorize the service connection. + +### Service Connection Invalid +**Symptom**: Pipeline fails with authentication error + +**Solution**: Verify service principals exist and have correct role assignments. Contact platform team. + +### Repository Not Initialized +**Symptom**: Empty repository created + +**Solution**: Verify `repository_init_type` is set to "Clean" or "Import". For Import, ensure template repository is accessible. + +## Maintenance + +### Updating Pipeline YAML +Pipelines reference `azure-pipelines-dev.yml` and `azure-pipelines-prod.yml` in the repository. Modify these files to customize the CI/CD process. + +### Adding New Environments +To add staging or other environments: +1. Create new meshStack project and tenant +2. Add service connection building block +3. Add pipeline building block with appropriate branch trigger + +### Service Principal Rotation +Service principals use Workload Identity Federation (OIDC). No credential rotation is required - authentication is passwordless. + +## Related Documentation + +- [Azure DevOps Pipelines](https://learn.microsoft.com/en-us/azure/devops/pipelines/) +- [Workload Identity Federation](https://learn.microsoft.com/en-us/azure/devops/pipelines/library/connect-to-azure) +- [AKS Best Practices](https://learn.microsoft.com/en-us/azure/aks/best-practices) +- [meshStack Building Blocks](https://docs.meshcloud.io/docs/meshstack.building-blocks.html) + + +## Requirements + +| Name | Version | +|------|---------| +| [meshstack](#requirement\_meshstack) | 0.15.0 | +| [random](#requirement\_random) | ~> 3.6 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [meshstack_building_block_v2.azdevops_project](https://registry.terraform.io/providers/meshcloud/meshstack/0.15.0/docs/resources/building_block_v2) | resource | +| [meshstack_building_block_v2.pipeline_dev](https://registry.terraform.io/providers/meshcloud/meshstack/0.15.0/docs/resources/building_block_v2) | resource | +| [meshstack_building_block_v2.pipeline_prod](https://registry.terraform.io/providers/meshcloud/meshstack/0.15.0/docs/resources/building_block_v2) | resource | +| [meshstack_building_block_v2.repository](https://registry.terraform.io/providers/meshcloud/meshstack/0.15.0/docs/resources/building_block_v2) | resource | +| [meshstack_building_block_v2.service_connection_dev](https://registry.terraform.io/providers/meshcloud/meshstack/0.15.0/docs/resources/building_block_v2) | resource | +| [meshstack_building_block_v2.service_connection_prod](https://registry.terraform.io/providers/meshcloud/meshstack/0.15.0/docs/resources/building_block_v2) | resource | +| [meshstack_project.dev](https://registry.terraform.io/providers/meshcloud/meshstack/0.15.0/docs/resources/project) | resource | +| [meshstack_project.prod](https://registry.terraform.io/providers/meshcloud/meshstack/0.15.0/docs/resources/project) | resource | +| [meshstack_project_user_binding.creator_dev_admin](https://registry.terraform.io/providers/meshcloud/meshstack/0.15.0/docs/resources/project_user_binding) | resource | +| [meshstack_project_user_binding.creator_prod_admin](https://registry.terraform.io/providers/meshcloud/meshstack/0.15.0/docs/resources/project_user_binding) | resource | +| [meshstack_tenant_v4.dev](https://registry.terraform.io/providers/meshcloud/meshstack/0.15.0/docs/resources/tenant_v4) | resource | +| [meshstack_tenant_v4.prod](https://registry.terraform.io/providers/meshcloud/meshstack/0.15.0/docs/resources/tenant_v4) | resource | +| [random_id.suffix](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [azdevops\_organization\_name](#input\_azdevops\_organization\_name) | Azure DevOps organization name. Used only for display purposes. | `string` | n/a | yes | +| [azdevops\_pipeline\_definition\_uuid](#input\_azdevops\_pipeline\_definition\_uuid) | UUID of the Azure DevOps pipeline building block definition. | `string` | n/a | yes | +| [azdevops\_pipeline\_definition\_version\_uuid](#input\_azdevops\_pipeline\_definition\_version\_uuid) | UUID of the Azure DevOps pipeline building block definition version. | `string` | n/a | yes | +| [azdevops\_project\_definition\_uuid](#input\_azdevops\_project\_definition\_uuid) | UUID of the Azure DevOps project building block definition. | `string` | n/a | yes | +| [azdevops\_project\_definition\_version\_uuid](#input\_azdevops\_project\_definition\_version\_uuid) | UUID of the Azure DevOps project building block definition version. | `string` | n/a | yes | +| [azdevops\_repository\_definition\_uuid](#input\_azdevops\_repository\_definition\_uuid) | UUID of the Azure DevOps repository building block definition. | `string` | n/a | yes | +| [azdevops\_repository\_definition\_version\_uuid](#input\_azdevops\_repository\_definition\_version\_uuid) | UUID of the Azure DevOps repository building block definition version. | `string` | n/a | yes | +| [azdevops\_service\_connection\_definition\_uuid](#input\_azdevops\_service\_connection\_definition\_uuid) | UUID of the Azure DevOps service connection building block definition. | `string` | n/a | yes | +| [azdevops\_service\_connection\_definition\_version\_uuid](#input\_azdevops\_service\_connection\_definition\_version\_uuid) | UUID of the Azure DevOps service connection building block definition version. | `string` | n/a | yes | +| [azure\_tenant\_id](#input\_azure\_tenant\_id) | Azure AD tenant ID | `string` | n/a | yes | +| [creator](#input\_creator) | Information about the creator of the resources who will be assigned Project Admin role |
object({
type = string
identifier = string
displayName = string
username = optional(string)
email = optional(string)
euid = optional(string)
})
| n/a | yes | +| [dev\_application\_object\_id](#input\_dev\_application\_object\_id) | Azure AD application object ID for the development service principal | `string` | n/a | yes | +| [dev\_azure\_subscription\_id](#input\_dev\_azure\_subscription\_id) | Azure subscription ID for the development environment | `string` | n/a | yes | +| [dev\_service\_principal\_id](#input\_dev\_service\_principal\_id) | Service principal client ID for the development environment | `string` | n/a | yes | +| [enable\_branch\_policies](#input\_enable\_branch\_policies) | Enable branch policies for the main branch (minimum reviewers, work item linking) | `bool` | `true` | no | +| [full\_platform\_identifier](#input\_full\_platform\_identifier) | Full platform identifier of the AKS Namespace platform. | `string` | n/a | yes | +| [landing\_zone\_dev\_identifier](#input\_landing\_zone\_dev\_identifier) | AKS Landing zone identifier for the development tenant. | `string` | n/a | yes | +| [landing\_zone\_prod\_identifier](#input\_landing\_zone\_prod\_identifier) | AKS Landing zone identifier for the production tenant. | `string` | n/a | yes | +| [minimum\_reviewers](#input\_minimum\_reviewers) | Minimum number of reviewers required for pull requests | `number` | `1` | no | +| [name](#input\_name) | This name will be used for the created projects, app subdomain, Azure DevOps project and repository. | `string` | n/a | yes | +| [prod\_application\_object\_id](#input\_prod\_application\_object\_id) | Azure AD application object ID for the production service principal | `string` | n/a | yes | +| [prod\_azure\_subscription\_id](#input\_prod\_azure\_subscription\_id) | Azure subscription ID for the production environment | `string` | n/a | yes | +| [prod\_service\_principal\_id](#input\_prod\_service\_principal\_id) | Service principal client ID for the production environment | `string` | n/a | yes | +| [project\_tags\_yaml](#input\_project\_tags\_yaml) | YAML configuration for project tags that will be applied to dev and prod projects. Expected structure:
yaml
dev:
key1:
- "value1"
- "value2"
key2:
- "value3"
prod:
key1:
- "value4"
key2:
- "value5"
- "value6"
| `string` | `"dev: {}\nprod: {}\n"` | no | +| [repository\_init\_type](#input\_repository\_init\_type) | Repository initialization type (Clean or Import) | `string` | `"Clean"` | no | +| [workspace\_identifier](#input\_workspace\_identifier) | meshStack workspace identifier | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [azdevops\_project\_url](#output\_azdevops\_project\_url) | URL of the created Azure DevOps project | +| [azdevops\_repository\_url](#output\_azdevops\_repository\_url) | URL of the created Azure DevOps repository | +| [dev\_link](#output\_dev\_link) | Link to the dev environment application | +| [prod\_link](#output\_prod\_link) | Link to the prod environment application | +| [summary](#output\_summary) | Summary with next steps and insights into created resources | + \ No newline at end of file diff --git a/modules/aks/starterkit-azuredevops/buildingblock/aks-azuredevops-starterkit.tftest.hcl b/modules/aks/starterkit-azuredevops/buildingblock/aks-azuredevops-starterkit.tftest.hcl new file mode 100644 index 00000000..19536fcf --- /dev/null +++ b/modules/aks/starterkit-azuredevops/buildingblock/aks-azuredevops-starterkit.tftest.hcl @@ -0,0 +1,281 @@ +variables { + workspace_identifier = "likvid-workspace" + name = "MyApp" + full_platform_identifier = "aks.eu-de-central" + landing_zone_dev_identifier = "aks-dev-lz" + landing_zone_prod_identifier = "aks-prod-lz" + azdevops_project_definition_version_uuid = "00000000-0000-0000-0000-000000000001" + azdevops_project_definition_uuid = "00000000-0000-0000-0000-000000000002" + azdevops_repository_definition_version_uuid = "00000000-0000-0000-0000-000000000003" + azdevops_repository_definition_uuid = "00000000-0000-0000-0000-000000000004" + azdevops_pipeline_definition_version_uuid = "00000000-0000-0000-0000-000000000005" + azdevops_pipeline_definition_uuid = "00000000-0000-0000-0000-000000000006" + azdevops_service_connection_definition_version_uuid = "00000000-0000-0000-0000-000000000007" + azdevops_service_connection_definition_uuid = "00000000-0000-0000-0000-000000000008" + azdevops_organization_name = "likvid-bank" + dev_azure_subscription_id = "11111111-1111-1111-1111-111111111111" + dev_service_principal_id = "22222222-2222-2222-2222-222222222222" + dev_application_object_id = "33333333-3333-3333-3333-333333333333" + prod_azure_subscription_id = "44444444-4444-4444-4444-444444444444" + prod_service_principal_id = "55555555-5555-5555-5555-555555555555" + prod_application_object_id = "66666666-6666-6666-6666-666666666666" + azure_tenant_id = "77777777-7777-7777-7777-777777777777" + + creator = { + type = "User" + identifier = "likvid-tom-user" + displayName = "Tom Livkid" + username = "likvid-tom@meshcloud.io" + email = "likvid-tom@meshcloud.io" + euid = "likvid-tom@meshcloud.io" + } +} + +run "valid_starter_kit_creation" { + command = plan + + assert { + condition = meshstack_project.dev.metadata.name == "myapp-dev" + error_message = "Dev project name should be normalized and suffixed with -dev" + } + + assert { + condition = meshstack_project.prod.metadata.name == "myapp-prod" + error_message = "Prod project name should be normalized and suffixed with -prod" + } + + assert { + condition = meshstack_tenant_v4.dev.spec.platform_identifier == var.full_platform_identifier + error_message = "Dev tenant should use correct platform identifier" + } + + assert { + condition = meshstack_tenant_v4.prod.spec.platform_identifier == var.full_platform_identifier + error_message = "Prod tenant should use correct platform identifier" + } + + assert { + condition = meshstack_building_block_v2.azdevops_project.spec.target_ref.kind == "meshWorkspace" + error_message = "Azure DevOps project should target workspace" + } + + assert { + condition = length(meshstack_building_block_v2.repository.spec.parent_building_blocks) == 1 + error_message = "Repository should have Azure DevOps project as parent" + } + + assert { + condition = length(meshstack_building_block_v2.service_connection_dev.spec.parent_building_blocks) == 1 + error_message = "Dev service connection should have Azure DevOps project as parent" + } + + assert { + condition = length(meshstack_building_block_v2.service_connection_prod.spec.parent_building_blocks) == 1 + error_message = "Prod service connection should have Azure DevOps project as parent" + } + + assert { + condition = length(meshstack_building_block_v2.pipeline_dev.spec.parent_building_blocks) == 2 + error_message = "Dev pipeline should have repository and service connection as parents" + } + + assert { + condition = length(meshstack_building_block_v2.pipeline_prod.spec.parent_building_blocks) == 2 + error_message = "Prod pipeline should have repository and service connection as parents" + } + + assert { + condition = meshstack_building_block_v2.service_connection_dev.spec.inputs.authorize_all_pipelines.value_bool == true + error_message = "Dev service connection should auto-authorize pipelines" + } + + assert { + condition = meshstack_building_block_v2.service_connection_prod.spec.inputs.authorize_all_pipelines.value_bool == false + error_message = "Prod service connection should require manual authorization" + } +} + +run "creator_assigned_as_project_admin" { + command = plan + + variables { + creator = { + type = "User" + identifier = "likvid-daniela-user" + displayName = "Daniela Livkid" + username = "likvid-daniela@meshcloud.io" + email = "likvid-daniela@meshcloud.io" + euid = "likvid-daniela@meshcloud.io" + } + } + + assert { + condition = length(meshstack_project_user_binding.creator_dev_admin) == 1 + error_message = "Creator should be assigned as Project Admin on dev project" + } + + assert { + condition = length(meshstack_project_user_binding.creator_prod_admin) == 1 + error_message = "Creator should be assigned as Project Admin on prod project" + } + + assert { + condition = meshstack_project_user_binding.creator_dev_admin[0].role_ref.name == "Project Admin" + error_message = "Creator should have Project Admin role on dev project" + } + + assert { + condition = meshstack_project_user_binding.creator_prod_admin[0].role_ref.name == "Project Admin" + error_message = "Creator should have Project Admin role on prod project" + } + + assert { + condition = meshstack_project_user_binding.creator_dev_admin[0].subject.name == "likvid-daniela@meshcloud.io" + error_message = "Creator username should be correctly assigned" + } +} + +run "non_user_creator_skips_bindings" { + command = plan + + variables { + creator = { + type = "ServiceAccount" + identifier = "system-account" + displayName = "System Account" + } + } + + assert { + condition = length(meshstack_project_user_binding.creator_dev_admin) == 0 + error_message = "Non-user creator should not have user bindings created" + } + + assert { + condition = length(meshstack_project_user_binding.creator_prod_admin) == 0 + error_message = "Non-user creator should not have user bindings created" + } +} + +run "custom_project_tags" { + command = plan + + variables { + project_tags_yaml = <= 0 && var.minimum_reviewers <= 10 + error_message = "minimum_reviewers must be between 0 and 10" + } +} + +variable "dev_azure_subscription_id" { + type = string + description = "Azure subscription ID for the development environment" +} + +variable "dev_service_principal_id" { + type = string + description = "Service principal client ID for the development environment" +} + +variable "dev_application_object_id" { + type = string + description = "Azure AD application object ID for the development service principal" +} + +variable "prod_azure_subscription_id" { + type = string + description = "Azure subscription ID for the production environment" +} + +variable "prod_service_principal_id" { + type = string + description = "Service principal client ID for the production environment" +} + +variable "prod_application_object_id" { + type = string + description = "Azure AD application object ID for the production service principal" +} + +variable "azure_tenant_id" { + type = string + description = "Azure AD tenant ID" +} diff --git a/modules/aks/starterkit-azuredevops/buildingblock/versions.tf b/modules/aks/starterkit-azuredevops/buildingblock/versions.tf new file mode 100644 index 00000000..4f5f052c --- /dev/null +++ b/modules/aks/starterkit-azuredevops/buildingblock/versions.tf @@ -0,0 +1,12 @@ +terraform { + required_providers { + meshstack = { + source = "meshcloud/meshstack" + version = "0.15.0" + } + random = { + source = "hashicorp/random" + version = "~> 3.6" + } + } +} diff --git a/modules/aks/starterkit/backplane/README.md b/modules/aks/starterkit-github/backplane/README.md similarity index 100% rename from modules/aks/starterkit/backplane/README.md rename to modules/aks/starterkit-github/backplane/README.md diff --git a/modules/aks/starterkit/backplane/permissions.png b/modules/aks/starterkit-github/backplane/permissions.png similarity index 100% rename from modules/aks/starterkit/backplane/permissions.png rename to modules/aks/starterkit-github/backplane/permissions.png diff --git a/modules/aks/starterkit/buildingblock/APP_TEAM_README.md b/modules/aks/starterkit-github/buildingblock/APP_TEAM_README.md similarity index 100% rename from modules/aks/starterkit/buildingblock/APP_TEAM_README.md rename to modules/aks/starterkit-github/buildingblock/APP_TEAM_README.md diff --git a/modules/aks/starterkit/buildingblock/README.md b/modules/aks/starterkit-github/buildingblock/README.md similarity index 100% rename from modules/aks/starterkit/buildingblock/README.md rename to modules/aks/starterkit-github/buildingblock/README.md diff --git a/modules/aks/starterkit-github/buildingblock/logo.png b/modules/aks/starterkit-github/buildingblock/logo.png new file mode 100644 index 00000000..d58f8ddb Binary files /dev/null and b/modules/aks/starterkit-github/buildingblock/logo.png differ diff --git a/modules/aks/starterkit/buildingblock/main.tf b/modules/aks/starterkit-github/buildingblock/main.tf similarity index 100% rename from modules/aks/starterkit/buildingblock/main.tf rename to modules/aks/starterkit-github/buildingblock/main.tf diff --git a/modules/aks/starterkit/buildingblock/outputs.tf b/modules/aks/starterkit-github/buildingblock/outputs.tf similarity index 100% rename from modules/aks/starterkit/buildingblock/outputs.tf rename to modules/aks/starterkit-github/buildingblock/outputs.tf diff --git a/modules/aks/starterkit-github/buildingblock/provider.tf b/modules/aks/starterkit-github/buildingblock/provider.tf new file mode 100644 index 00000000..c89e3966 --- /dev/null +++ b/modules/aks/starterkit-github/buildingblock/provider.tf @@ -0,0 +1 @@ +provider "meshstack" {} diff --git a/modules/aks/starterkit/buildingblock/variables.tf b/modules/aks/starterkit-github/buildingblock/variables.tf similarity index 100% rename from modules/aks/starterkit/buildingblock/variables.tf rename to modules/aks/starterkit-github/buildingblock/variables.tf diff --git a/modules/aks/starterkit/buildingblock/versions.tf b/modules/aks/starterkit-github/buildingblock/versions.tf similarity index 100% rename from modules/aks/starterkit/buildingblock/versions.tf rename to modules/aks/starterkit-github/buildingblock/versions.tf diff --git a/modules/aks/starterkit/buildingblock/logo.png b/modules/aks/starterkit/buildingblock/logo.png deleted file mode 100644 index af8490f9..00000000 Binary files a/modules/aks/starterkit/buildingblock/logo.png and /dev/null differ diff --git a/modules/aws/agentic-coding-sandbox/buildingblock/logo.png b/modules/aws/agentic-coding-sandbox/buildingblock/logo.png index 23536d50..54083ef2 100644 Binary files a/modules/aws/agentic-coding-sandbox/buildingblock/logo.png and b/modules/aws/agentic-coding-sandbox/buildingblock/logo.png differ diff --git a/modules/aws/budget-alert/buildingblock/logo.png b/modules/aws/budget-alert/buildingblock/logo.png index f33d9c5b..0bad3370 100644 Binary files a/modules/aws/budget-alert/buildingblock/logo.png and b/modules/aws/budget-alert/buildingblock/logo.png differ diff --git a/modules/aws/opt-in-region/buildingblock/logo.png b/modules/aws/opt-in-region/buildingblock/logo.png index 5551c545..b52ec8cb 100644 Binary files a/modules/aws/opt-in-region/buildingblock/logo.png and b/modules/aws/opt-in-region/buildingblock/logo.png differ diff --git a/modules/azure/azure-virtual-machine-starterkit/buildingblock/logo.png b/modules/azure/azure-virtual-machine-starterkit/buildingblock/logo.png new file mode 100644 index 00000000..73deda66 Binary files /dev/null and b/modules/azure/azure-virtual-machine-starterkit/buildingblock/logo.png differ diff --git a/modules/gcp/budget-alert/buildingblock/logo.png b/modules/gcp/budget-alert/buildingblock/logo.png index daa3961f..5378f253 100644 Binary files a/modules/gcp/budget-alert/buildingblock/logo.png and b/modules/gcp/budget-alert/buildingblock/logo.png differ