diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..6667f09 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,25 @@ +name: Container Environment CI + +on: [push] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Install Docker Compose + run: | + mkdir -p ~/.docker/cli-plugins/ + curl -SL https://github.com/docker/compose/releases/download/v2.23.3/docker-compose-linux-x86_64 -o ~/.docker/cli-plugins/docker-compose + chmod +x ~/.docker/cli-plugins/docker-compose + + - name: Build and start services + run: docker compose -f docker-compose-test.yml build + + - name: Clean up + if: always() + run: docker compose -f docker-compose-test.yml down diff --git a/.github/workflows/container-ci.yml b/.github/workflows/container-ci.yml new file mode 100644 index 0000000..fcbd0f2 --- /dev/null +++ b/.github/workflows/container-ci.yml @@ -0,0 +1,25 @@ +name: Container CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build and test container + run: | + docker compose -f docker-compose.yml -f docker-compose-test.yml build + docker compose -f docker-compose.yml -f docker-compose-test.yml up --abort-on-container-exit + + - name: Clean up + if: always() + run: docker compose -f docker-compose.yml -f docker-compose-test.yml down diff --git a/.gitignore b/.gitignore index 42961d0..bccc680 100644 --- a/.gitignore +++ b/.gitignore @@ -36,4 +36,7 @@ k8sgpt_*.deb # Docs microk8s_setup_and_ai_interaction_command_line_instructions.md -/helm-chart/README.md \ No newline at end of file +/helm-chart/README.md + +# Models +models/*.gguf diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e097391 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,48 @@ +# Stage 1: Builder +FROM rust:1.84-slim-bookworm as builder + +# Install build dependencies +RUN apt-get update && apt-get install -y \ + pkg-config \ + libssl-dev \ + && rm -rf /var/lib/apt/lists/* + +# Create a new empty shell project +WORKDIR /usr/src/app +COPY Cargo.toml Cargo.lock ./ + +# Build dependencies - this is the caching Docker layer +RUN mkdir src && \ + echo "fn main() {}" > src/main.rs && \ + cargo build --release && \ + rm -rf src + +# Build actual source code +COPY src ./src +RUN touch src/main.rs && cargo build --release + +# Stage 2: Runtime +FROM debian:bookworm-slim + +# Install runtime dependencies +RUN apt-get update && apt-get install -y \ + ca-certificates \ + libssl3 \ + && rm -rf /var/lib/apt/lists/* \ + && groupadd -r appuser && useradd -r -g appuser appuser \ + && mkdir -p /home/appuser/.kube \ + && chown -R appuser:appuser /home/appuser \ + && chmod 700 /home/appuser/.kube + + # Copy the binary from builder +COPY --from=builder /usr/src/app/target/release/kube_app /usr/local/bin/ + +# Set proper permissions +RUN chown appuser:appuser /usr/local/bin/kube_app + +# Use non-root user +USER appuser + +# Run the binary +CMD ["kube_app"] + diff --git a/docker-compose-test.yml b/docker-compose-test.yml new file mode 100644 index 0000000..58f649b --- /dev/null +++ b/docker-compose-test.yml @@ -0,0 +1,56 @@ +version: "3.8" + +services: + # localai: + # extends: + # file: docker-compose.yml + # service: localai + # environment: + # - LOCALAI_DEBUG=true + # - TEST_MODE=true + # healthcheck: + # test: ["CMD", "curl", "-f", "http://localhost:8080/health"] + # interval: 10s + # timeout: 5s + # retries: 3 + # start_period: 20s + + # k8sgpt: + # extends: + # file: docker-compose.yml + # service: k8sgpt + # environment: + # - K8SGPT_TEST_MODE=true + # - K8SGPT_LOG_LEVEL=debug + # healthcheck: + # test: ["CMD", "k8sgpt version"] + # interval: 10s + # timeout: 5s + # retries: 3 + + app: + extends: + file: docker-compose.yml + service: app + environment: + - RUST_LOG=debug + - TEST_ENV=true + - MOCK_K8S_API=true + - KUBECONFIG=/home/appuser/.kube/config + volumes: + - ./test/mock-kubeconfig:/home/appuser/.kube/config:ro + healthcheck: + test: ["CMD", "/app/health_check"] + interval: 10s + timeout: 5s + retries: 3 + +volumes: + test-models: + driver: local + test-output: + driver: local + +networks: + test-network: + driver: bridge diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..7a5cbb7 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,9 @@ +version: '3.8' + +services: + app: + build: + context: . + dockerfile: Dockerfile + volumes: + - ${KUBECONFIG:-~/.kube/config}:/home/appuser/.kube/config:ro diff --git a/scripts/health_check.zsh b/scripts/health_check.zsh new file mode 100755 index 0000000..3da3f3b --- /dev/null +++ b/scripts/health_check.zsh @@ -0,0 +1,112 @@ +#!/usr/bin/env zsh + +# Enable error handling +setopt ERR_EXIT PIPE_FAIL NO_UNSET WARN_CREATE_GLOBAL + +# Define colors for output +typeset -A colors +colors=( +'reset' $'\033[0m' +'red' $'\033[31m' +'green' $'\033[32m' +'yellow' $'\033[33m' +'blue' $'\033[34m' +) + +# Constants +typeset -r MAX_RETRIES=3 +typeset -r TIMEOUT=10 +typeset -r RETRY_DELAY=5 + +# Helper functions +function print_status() { +local message=$1 +local status=$2 +if [[ $status -eq 0 ]]; then + print "${colors[green]}✓ ${message}${colors[reset]}" +else + print "${colors[red]}✗ ${message}${colors[reset]}" + return 1 +fi +} + +function check_command() { +local cmd=$1 +which $cmd &>/dev/null || { + print "${colors[red]}Error: $cmd not found${colors[reset]}" + return 1 +} +} + +function check_localai() { +local retries=0 +while (( retries < MAX_RETRIES )); do + if curl -s --max-time $TIMEOUT "http://localhost:8080/health" | grep -q "ok"; then + print_status "LocalAI service is healthy" 0 + return 0 + fi + ((retries++)) + print "${colors[yellow]}Retrying LocalAI health check ($retries/$MAX_RETRIES)${colors[reset]}" + sleep $RETRY_DELAY +done +print_status "LocalAI service is not responding" 1 +return 1 +} + +function check_k8sgpt() { +local retries=0 +while (( retries < MAX_RETRIES )); do + if k8sgpt version &>/dev/null; then + print_status "K8sGPT service is healthy" 0 + return 0 + fi + ((retries++)) + print "${colors[yellow]}Retrying K8sGPT health check ($retries/$MAX_RETRIES)${colors[reset]}" + sleep $RETRY_DELAY +done +print_status "K8sGPT service is not responding" 1 +return 1 +} + +function check_kube_app() { +local retries=0 +while (( retries < MAX_RETRIES )); do + if curl -s --max-time $TIMEOUT "http://localhost:3000/health" | grep -q "ok"; then + print_status "Kube application is healthy" 0 + return 0 + fi + ((retries++)) + print "${colors[yellow]}Retrying Kube application health check ($retries/$MAX_RETRIES)${colors[reset]}" + sleep $RETRY_DELAY +done +print_status "Kube application is not responding" 1 +return 1 +} + +# Main +function main() { +print "${colors[blue]}Starting health checks...${colors[reset]}" + +# Check required commands +check_command "curl" || return 1 +check_command "k8sgpt" || return 1 + +# Initialize status +local -i status=0 + +# Run health checks +check_localai || status=1 +check_k8sgpt || status=1 +check_kube_app || status=1 + +if [[ $status -eq 0 ]]; then + print "\n${colors[green]}All services are healthy!${colors[reset]}" + return 0 +else + print "\n${colors[red]}One or more services are unhealthy${colors[reset]}" + return 1 +fi +} + +main "$@" + diff --git a/setup.zsh b/setup.zsh new file mode 100755 index 0000000..b421fd5 --- /dev/null +++ b/setup.zsh @@ -0,0 +1,115 @@ +#!/usr/bin/env zsh + +# Enable zsh-specific error handling +setopt ERR_EXIT +setopt PIPE_FAIL +setopt NO_UNSET + +# Color definitions +typeset -A colors +colors=( + [red]=$'\e[31m' + [green]=$'\e[32m' + [yellow]=$'\e[33m' + [reset]=$'\e[0m' +) + +# Function to print messages with color +print_message() { + local color="${colors[$1]}" + local message="$2" + echo "${color}${message}${colors[reset]}" +} + +# Function to check prerequisites +check_prerequisites() { + local -a required_tools=(docker docker-compose curl) + + for tool in $required_tools; do + if ! command -v $tool &> /dev/null; then + print_message "red" "Error: $tool is not installed" + return 1 + fi + done + + print_message "green" "All prerequisites are satisfied" +} + +# Function to create required directories +setup_directories() { + local -a dirs=(models output .k8sgpt) + + for dir in $dirs; do + if [[ ! -d $dir ]]; then + mkdir -p $dir + print_message "green" "Created directory: $dir" + fi + done +} + +# Function to download model if not present +download_model() { + local model_path="models/llama-3.2-1b-instruct-q4_k_m.gguf" + local model_url="https://huggingface.co/hugging-quants/Llama-3.2-1B-Instruct-Q4_K_M-GGUF/resolve/main/llama-3.2-1b-instruct-q4_k_m.gguf" + + if [[ ! -f $model_path ]]; then + print_message "yellow" "Downloading model..." + curl -L $model_url -o $model_path + if [[ $? -eq 0 ]]; then + print_message "green" "Model downloaded successfully" + else + print_message "red" "Failed to download model" + return 1 + fi + else + print_message "green" "Model already exists" + fi +} + +# Function to run in development mode +run_dev() { + print_message "yellow" "Starting services in development mode..." + docker-compose up -d + print_message "green" "Services started successfully" +} + +# Function to run in test mode +run_test() { + print_message "yellow" "Starting services in test mode..." + docker-compose -f docker-compose.yml -f docker-compose.test.yml up -d + print_message "green" "Test environment started successfully" +} + +# Main function +main() { + local mode=${1:-dev} + + print_message "yellow" "Setting up environment..." + + # Check prerequisites + check_prerequisites || return 1 + + # Setup directories + setup_directories || return 1 + + # Download model if needed + download_model || return 1 + + # Run in specified mode + case $mode in + dev) + run_dev + ;; + test) + run_test + ;; + *) + print_message "red" "Invalid mode. Use 'dev' or 'test'" + return 1 + ;; + esac +} + +# Run main function with provided arguments +main "$@" + diff --git a/src/main.rs b/src/main.rs index 473fb11..fc5f45f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,8 @@ use tokio; #[tokio::main] async fn main() -> Result<(), Box> { // Initialize Kubernetes client - let client = Client::try_default().await?; + let config = kube::Config::infer().await?; + let client = Client::try_from(config)?; // Access the Namespace API let namespaces: Api = Api::all(client.clone());