diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 00000000..2ec6a16c --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,23 @@ +FROM mcr.microsoft.com/devcontainers/base:ubuntu-24.04 + +# 1. Define the arguments to accept them from devcontainer.json +ARG HTTP_PROXY +ARG HTTPS_PROXY +ARG NO_PROXY +ARG http_proxy +ARG https_proxy +ARG no_proxy + +# 2. Export them as environment variables +ENV HTTP_PROXY=$HTTP_PROXY +ENV HTTPS_PROXY=$HTTPS_PROXY +ENV NO_PROXY=$NO_PROXY +ENV http_proxy=$http_proxy +ENV https_proxy=$https_proxy +ENV no_proxy=$no_proxy + +# 3. Ensure sudo keeps these variables (crucial for features that use sudo) +RUN echo 'Defaults env_keep += "HTTP_PROXY HTTPS_PROXY NO_PROXY http_proxy https_proxy no_proxy"' >> /etc/sudoers + +# 4. Prime the apt cache with proxy settings active +RUN apt-get update diff --git a/.devcontainer/devcontainer-lock.json b/.devcontainer/devcontainer-lock.json new file mode 100644 index 00000000..dab65ac6 --- /dev/null +++ b/.devcontainer/devcontainer-lock.json @@ -0,0 +1,24 @@ +{ + "features": { + "ghcr.io/devcontainers/features/common-utils:2": { + "version": "2.5.9", + "resolved": "ghcr.io/devcontainers/features/common-utils@sha256:cb0c4d3c276f157eed17935747e364178d75fee17f55c4e129966f64633deb3a", + "integrity": "sha256:cb0c4d3c276f157eed17935747e364178d75fee17f55c4e129966f64633deb3a" + }, + "ghcr.io/devcontainers/features/docker-outside-of-docker:1": { + "version": "1.10.0", + "resolved": "ghcr.io/devcontainers/features/docker-outside-of-docker@sha256:c2c2cf829505ead8e4892c88c31b6594ae94a2bbb209e16e1fac456c1a3a624e", + "integrity": "sha256:c2c2cf829505ead8e4892c88c31b6594ae94a2bbb209e16e1fac456c1a3a624e" + }, + "ghcr.io/devcontainers/features/go:1": { + "version": "1.3.4", + "resolved": "ghcr.io/devcontainers/features/go@sha256:d85e921f91b41340055bb12b325d9d551170ed04b3b832e33530bf42f167c032", + "integrity": "sha256:d85e921f91b41340055bb12b325d9d551170ed04b3b832e33530bf42f167c032" + }, + "ghcr.io/devcontainers/features/node:2": { + "version": "2.1.0", + "resolved": "ghcr.io/devcontainers/features/node@sha256:586c9a6f7dd40bd3ba2cd41e7f2f88dcc31fbe5d1442afcbf07ffbc66b686857", + "integrity": "sha256:586c9a6f7dd40bd3ba2cd41e7f2f88dcc31fbe5d1442afcbf07ffbc66b686857" + } + } +} diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..094efabe --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,64 @@ +{ + "name": "Cloud Deployment Dev Container", + "build": { + "dockerfile": "Dockerfile", + "args": { + "HTTP_PROXY": "${localEnv:HTTP_PROXY:}", + "HTTPS_PROXY": "${localEnv:HTTPS_PROXY:}", + "NO_PROXY": "${localEnv:NO_PROXY:}", + "http_proxy": "${localEnv:http_proxy:}", + "https_proxy": "${localEnv:https_proxy:}", + "no_proxy": "${localEnv:no_proxy:}" + } + }, + "features": { + "ghcr.io/devcontainers/features/docker-outside-of-docker:1": { + "version": "latest", + "enableNonRootDocker": "true", + "moby": "true" + }, + "ghcr.io/devcontainers/features/node:2": { + "version": "lts" + }, + "ghcr.io/devcontainers/features/go:1": { + "version": "latest" + }, + "ghcr.io/devcontainers/features/common-utils:2": { + "installZsh": true, + "configureZshAsDefaultShell": true, + "installOhMyZsh": true, + "upgradePackages": true + } + }, + "containerEnv": { + "LOCAL_WORKSPACE_FOLDER": "${localWorkspaceFolder}", + "HTTP_PROXY": "${localEnv:HTTP_PROXY:}", + "HTTPS_PROXY": "${localEnv:HTTPS_PROXY:}", + "NO_PROXY": "${localEnv:NO_PROXY:}", + "http_proxy": "${localEnv:http_proxy:}", + "https_proxy": "${localEnv:https_proxy:}", + "no_proxy": "${localEnv:no_proxy:}" + }, + "forwardPorts": [ + 4200, + 3000, + 8081, + 8003 + ], + "customizations": { + "vscode": { + "extensions": [ + "ms-azuretools.vscode-docker", + "dbaeumer.vscode-eslint", + "golang.go", + "ms-vscode.cpptools", + "ryu1kn.text-marker", + "shd101wyy.markdown-preview-enhanced", + "shardulm94.trailing-spaces", + "waderyan.gitblame", + "esbenp.prettier-vscode" + ] + } + }, + "postCreateCommand": "sed -i 's/\\r$//' .devcontainer/post-create.sh && /bin/bash .devcontainer/post-create.sh" +} \ No newline at end of file diff --git a/.devcontainer/post-create.sh b/.devcontainer/post-create.sh new file mode 100755 index 00000000..aefd7752 --- /dev/null +++ b/.devcontainer/post-create.sh @@ -0,0 +1,200 @@ +#!/bin/bash +set -e + +echo "Starting DevContainer post-create setup..." + +# Get workspace root (volume mounted from host) +WORKSPACE_ROOT="${PWD}" +TEMPLATE_FILE="$WORKSPACE_ROOT/.env.template" +OUTPUT_FILE="$WORKSPACE_ROOT/.env" +KONG_FILE="$WORKSPACE_ROOT/kong.yaml" +CYPRESS_CONFIG="$WORKSPACE_ROOT/sample-web-ui/cypress.config.ts" +DOCKER_COMPOSE_FILE="$WORKSPACE_ROOT/docker-compose.yml" +README_FILE="$WORKSPACE_ROOT/Readme.md" + +# Function to check if setup is already done +is_setup_complete() { + if [ -f "$OUTPUT_FILE" ] && grep -qE "^MPS_JWT_SECRET=.+$" "$OUTPUT_FILE"; then + return 0 # Setup is complete + fi + return 1 # Setup needed +} + +# Function to generate random 32-character string (cross-platform) +generate_random_string() { + if command -v openssl &> /dev/null; then + openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | cut -c1-32 + elif [[ -c /dev/urandom ]]; then + tr -dc 'a-zA-Z0-9' < /dev/urandom | head -c 32 + elif command -v python3 &> /dev/null; then + python3 -c "import random; import string; print(''.join(random.choices(string.ascii_letters + string.digits, k=32)))" + else + # Fallback: use timestamp + date +%s%N | sha256sum | tr -dc 'a-zA-Z0-9' | head -c 32 + fi +} + +# Function to get the host IP address +get_host_ip() { + if command -v hostname &> /dev/null; then + local ip=$(hostname -I 2>/dev/null | awk '{print $1}') + if [ -n "$ip" ]; then + echo "$ip" + return 0 + fi + fi + echo "localhost" +} + +# Cross-platform sed replacement +sed_replace() { + local file="$1" + local pattern="$2" + local replacement="$3" + + if [[ "$OSTYPE" == "darwin"* ]]; then + sed -i '' "s|$pattern|$replacement|g" "$file" + else + sed -i "s|$pattern|$replacement|g" "$file" + fi +} + +# Function to validate repository +validate_repository() { + if [ -f "$README_FILE" ] && grep -q "Device Management Toolkit (formerly known as Open AMT Cloud Toolkit)" "$README_FILE"; then + echo "✓ Detected: Device Management Toolkit repository" + return 0 + fi + echo "✗ Error: Unrecognized repository" + exit 1 +} + +# Function to generate default values for DMT +generate_defaults() { + DEFAULT_MPS_COMMON_NAME=$(get_host_ip) + DEFAULT_MPS_WEB_ADMIN_USER="hspeoob" + DEFAULT_MPS_WEB_ADMIN_PASSWORD="Apple-1234" + DEFAULT_MPS_JWT_SECRET=$(generate_random_string) + DEFAULT_MPS_JWT_ISSUER=$(generate_random_string) + DEFAULT_POSTGRES_PASSWORD="Apple-1234" + DEFAULT_VAULT_TOKEN=$(generate_random_string) +} + +# Function to populate .env file +populate_env_file() { + echo "Creating and populating .env file..." + cp "$TEMPLATE_FILE" "$OUTPUT_FILE" + + sed_replace "$OUTPUT_FILE" "^MPS_COMMON_NAME=.*" "MPS_COMMON_NAME=$DEFAULT_MPS_COMMON_NAME" + sed_replace "$OUTPUT_FILE" "^MPS_WEB_ADMIN_USER=.*" "MPS_WEB_ADMIN_USER=$DEFAULT_MPS_WEB_ADMIN_USER" + sed_replace "$OUTPUT_FILE" "^MPS_WEB_ADMIN_PASSWORD=.*" "MPS_WEB_ADMIN_PASSWORD=$DEFAULT_MPS_WEB_ADMIN_PASSWORD" + sed_replace "$OUTPUT_FILE" "^MPS_JWT_SECRET=.*" "MPS_JWT_SECRET=$DEFAULT_MPS_JWT_SECRET" + sed_replace "$OUTPUT_FILE" "^MPS_JWT_ISSUER=.*" "MPS_JWT_ISSUER=$DEFAULT_MPS_JWT_ISSUER" + sed_replace "$OUTPUT_FILE" "^POSTGRES_PASSWORD=.*" "POSTGRES_PASSWORD=$DEFAULT_POSTGRES_PASSWORD" + sed_replace "$OUTPUT_FILE" "^VAULT_TOKEN=.*" "VAULT_TOKEN=$DEFAULT_VAULT_TOKEN" + + echo "✓ .env file has been created and populated" +} + +# Function to update kong.yaml +update_kong_config() { + if [ -f "$KONG_FILE" ]; then + echo "Updating kong.yaml with JWT secrets..." + ACTUAL_JWT_SECRET=$(grep "^MPS_JWT_SECRET=" "$OUTPUT_FILE" | cut -d'=' -f2) + ACTUAL_JWT_ISSUER=$(grep "^MPS_JWT_ISSUER=" "$OUTPUT_FILE" | cut -d'=' -f2) + + sed_replace "$KONG_FILE" "key: [a-zA-Z0-9]* #sample key" "key: $ACTUAL_JWT_ISSUER #sample key" + sed_replace "$KONG_FILE" "secret:.*" "secret: \"$ACTUAL_JWT_SECRET\"" + echo "✓ kong.yaml has been updated" + else + echo "⚠ Warning: kong.yaml not found" + fi +} + +# Function to update cypress.config.ts +update_cypress_config() { + if [ -f "$CYPRESS_CONFIG" ]; then + echo "Updating cypress.config.ts with credentials..." + sed_replace "$CYPRESS_CONFIG" "MPS_USERNAME: '.*'" "MPS_USERNAME: '$DEFAULT_MPS_WEB_ADMIN_USER'" + sed_replace "$CYPRESS_CONFIG" "MPS_PASSWORD: '.*'" "MPS_PASSWORD: '$DEFAULT_MPS_WEB_ADMIN_PASSWORD'" + sed_replace "$CYPRESS_CONFIG" "VAULT_TOKEN: '.*'" "VAULT_TOKEN: '$DEFAULT_VAULT_TOKEN'" + echo "✓ cypress.config.ts has been updated" + else + echo "⚠ Warning: cypress.config.ts not found" + fi +} + +# Function to update docker-compose.yml +update_docker_compose() { + if [ -f "$DOCKER_COMPOSE_FILE" ]; then + echo "Updating docker-compose.yml with workspace folder variable..." + sed_replace "$DOCKER_COMPOSE_FILE" "- \\./nginx\\.conf:" "- \${LOCAL_WORKSPACE_FOLDER:-.}/nginx.conf:" + sed_replace "$DOCKER_COMPOSE_FILE" "- \\./data:" "- \${LOCAL_WORKSPACE_FOLDER:-.}/data:" + sed_replace "$DOCKER_COMPOSE_FILE" "- \\./kong\\.yaml:" "- \${LOCAL_WORKSPACE_FOLDER:-.}/kong.yaml:" + sed_replace "$DOCKER_COMPOSE_FILE" "- \\./mosquitto\\.conf:" "- \${LOCAL_WORKSPACE_FOLDER:-.}/mosquitto.conf:" + echo "✓ docker-compose.yml has been updated" + else + echo "⚠ Warning: docker-compose.yml not found" + fi +} + +# Main execution +main() { + validate_repository + + # Check if setup is already done + if is_setup_complete; then + echo "✓ Setup already completed. Skipping configuration..." + else + echo "Configuration setup required..." + echo "==========================================" + echo "Environment Configuration Setup" + echo "==========================================" + + generate_defaults + populate_env_file + update_kong_config + update_cypress_config + update_docker_compose + + echo "==========================================" + echo "Configuration complete!" + echo "==========================================" + fi +} + +# Configure apt proxy if needed +for var in HTTP_PROXY HTTPS_PROXY NO_PROXY http_proxy https_proxy no_proxy; do + eval v="\${$var}" + if [ -z "$v" ]; then unset $var; fi +done + +strip_trailing_slash() { + local url="$1" + echo "$url" | sed 's%[\\/]*$%%' +} + +if [ -n "$HTTP_PROXY" ] || [ -n "$http_proxy" ] || [ -n "$HTTPS_PROXY" ] || [ -n "$https_proxy" ]; then + echo "Configuring apt to use proxy..." + sudo mkdir -p /etc/apt/apt.conf.d + apt_http_proxy="$(strip_trailing_slash "${HTTP_PROXY:-${http_proxy:-}}")" + apt_https_proxy="$(strip_trailing_slash "${HTTPS_PROXY:-${https_proxy:-}}")" + sudo tee /etc/apt/apt.conf.d/99proxy > /dev/null </**" + ] + }, + { + "type": "pwa-node", + "request": "launch", + "name": "Debug RPS (Node.js)", + "program": "${workspaceFolder}/rps/dist/Index.js", + "cwd": "${workspaceFolder}/rps", + "envFile": "${workspaceFolder}/.env", + "sourceMaps": true, + "outFiles": [ + "${workspaceFolder}/rps/dist/**/*.js" + ], + "skipFiles": [ + "/**" + ] + }, + { + "name": "Debug MPS Router (Go)", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}/mps-router/cmd/main.go", + "cwd": "${workspaceFolder}/mps-router", + "envFile": "${workspaceFolder}/.env" + }, + { + "name": "Debug Web UI (Chrome)", + "type": "pwa-chrome", + "request": "launch", + "url": "http://localhost:4200", + "webRoot": "${workspaceFolder}/sample-web-ui" + } + ], + "compounds": [ + { + "name": "Debug All Services", + "configurations": ["Debug MPS (Node.js)", "Debug RPS (Node.js)", "Debug MPS Router (Go)", "Debug Web UI (Chrome)"] + } + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000..a5707bc1 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,73 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Start Infrastructure", + "type": "shell", + "command": "docker-compose up -d db vault kong mosquitto consul", + "presentation": { + "reveal": "always", + "panel": "shared" + }, + "group": "build", + "problemMatcher": [] + }, + { + "label": "Stop Infrastructure", + "type": "shell", + "command": "docker-compose stop db vault kong mosquitto consul", + "presentation": { + "reveal": "always", + "panel": "shared" + }, + "problemMatcher": [] + }, + { + "label": "Restart Infrastructure", + "type": "shell", + "command": "docker-compose restart db vault kong mosquitto consul", + "problemMatcher": [] + }, + { + "label": "Connect DevContainer to Network", + "type": "shell", + "command": "docker network connect $(docker network ls --filter name=openamtnetwork --format '{{.Name}}') $HOSTNAME || echo 'Already connected or network not found'", + "presentation": { + "reveal": "always", + "panel": "shared" + }, + "problemMatcher": [] + }, + { + "label": "Build MPS", + "type": "typescript", + "tsconfig": "mps/tsconfig.build.json", + "group": "build" + }, + { + "label": "Build RPS", + "type": "typescript", + "tsconfig": "rps/tsconfig.build.json", + "group": "build" + }, + { + "label": "Serve WebUI (Angular)", + "type": "shell", + "command": "cd sample-web-ui && npm install && npm start -- --host 0.0.0.0", + "isBackground": true, + "problemMatcher": { + "owner": "typescript", + "pattern": "$tsc", + "background": { + "activeOnStart": true, + "beginsPattern": { + "regexp": "Compiling..." + }, + "endsPattern": { + "regexp": "Compiled successfully" + } + } + } + } + ] +} diff --git a/DEVELOPER_DEVCONTAINER.md b/DEVELOPER_DEVCONTAINER.md new file mode 100644 index 00000000..50c853b3 --- /dev/null +++ b/DEVELOPER_DEVCONTAINER.md @@ -0,0 +1,116 @@ +# Cloud Deployment Developer Guide + +This guide describes how to develop and debug the Open AMT Cloud Toolkit components using the provided VS Code Dev Container environment. + +## Architecture Overview + +The development environment uses a **Hybrid** approach: +1. **Code & Services**: Your application code (MPS, RPS, Router, WebUI) runs *inside* the Dev Container as standard processes. This gives you full IDE support (IntelliSense, Debuggers). +2. **Infrastructure**: External dependencies (Postgres, Vault, Kong, etc.) run as *Docker containers* alongside the Dev Container, using the host's Docker daemon (Docker-outside-of-Docker). + +```mermaid +flowchart TD + %% Styles + classDef host fill:#f5f5f5,stroke:#333,stroke-width:2px,color:#000; + classDef docker fill:#e3f2fd,stroke:#1565c0,stroke-width:2px,color:#000; + classDef devcontainer fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px,stroke-dasharray: 5 5,color:#000; + classDef infra fill:#fff3e0,stroke:#ef6c00,stroke-width:2px,color:#000; + classDef service fill:#ffffff,stroke:#333,stroke-width:1px,color:#000; + classDef database fill:#ffffff,stroke:#333,stroke-width:1px,shape:cylinder,color:#000; + + subgraph Host ["Host Machine (Windows/Linux/Mac)"] + Browser["Web Browser (Chrome/Edge)"]:::service + VSCode["VS Code"]:::service + + subgraph DockerEngine ["Docker Engine"] + DockerDaemon[[Docker Daemon]]:::service + + subgraph DevContainer ["Dev Container (VS Code Remote)"] + direction TB + MPS["MPS Service (Node.js)
:3000"]:::service + RPS["RPS Service (Node.js)
:8081"]:::service + Router["MPS Router (Go)
:8003"]:::service + WebUI["Sample Web UI (Angular)
:4200"]:::service + + Debugger["VS Code Debugger"]:::service + Debugger -.-> MPS + Debugger -.-> RPS + Debugger -.-> Router + Debugger -.-> WebUI + end + + subgraph Infra ["Infrastructure (Docker Compose)"] + DB[("Postgres DB
:5432")]:::database + Vault[("Vault
:8200")]:::database + Kong["Kong Gateway"]:::service + Consul["Consul"]:::service + Mosquitto["Mosquitto MQTT"]:::service + end + + %% Connections + DevContainer -- "Docker Socket" --> DockerDaemon + MPS --> DB + MPS --> Vault + RPS --> DB + RPS --> Vault + WebUI -- "HTTP" --> MPS + + %% Networking + DevContainer <== "Shared Network (openamtnetwork)" ==> Infra + end + end + + VSCode -- "Remote Connection" --> DevContainer + Browser -- "Forwarded Port 4200" --> WebUI + + %% Apply Formatting + class Host host + class DockerEngine docker + class DevContainer devcontainer + class Infra infra +``` + +## Getting Started + +### Prerequisites +* Docker Desktop (or Docker Engine on Linux) +* VS Code with **Dev Containers** extension installed. + +### Initial Setup +1. Open this folder in VS Code. +2. When prompted, click **Reopen in Container**. (Or run command: `Dev Containers: Reopen in Container`). + +## Running the Environment + +### 1. Start Infrastructure +Before running the services, you need the database and other tools up. +* Open the Command Palette (`Ctrl+Shift+P`). +* Run **Tasks: Run Task** -> **Start Infrastructure**. + * *This spins up Postgres, Vault, Kong, etc.* + +### 2. Connect Network +Ensure your Dev Container can talk to the infrastructure containers. +* Run **Tasks: Run Task** -> **Connect DevContainer to Network**. + +### 3. Start Web UI (Optional) +If you are working on the frontend: +* Run **Tasks: Run Task** -> **Serve WebUI (Angular)**. +* Wait for compilation to finish. + +### 4. Debug Services +* Go to the **Run and Debug** view (`Ctrl+Shift+D`). +* Select **"Debug All Services"**. +* Press **F5**. + * This will launch MPS, RPS, and the Router in debug mode. + * If Web UI task is running, it will also launch a Chrome window attached to the frontend. + +## Configuration Files + +* **.devcontainer/devcontainer.json**: Defines the environment (tools, extensions, port forwarding). +* **.vscode/launch.json**: Debugger configurations for Node.js (MPS/RPS), Go (Router), and Chrome (WebUI). +* **.vscode/tasks.json**: Helper scripts to manage Docker containers and build processes. + +## Troubleshooting + +* **Database connection failed**: Ensure you ran the "Start Infrastructure" task and the "Connect DevContainer to Network" task. The services look for a host named `db`. +* **Web UI not accessible**: Make sure the ports are forwarded in the **Ports** view (Ctrl+J -> Ports). Port 4200 should be pointing to localhost:4200.