Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Supervisor Integration [UAT-58] #68

Merged
merged 11 commits into from
Dec 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ dist/
*_creds.json

# Environment variables file
*env*
.env

# Ignore Prettier configuration overrides for development
.prettierignore
35 changes: 24 additions & 11 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ ENV DEBIAN_FRONTEND=noninteractive \
# Set the shell with pipefail option
SHELL ["/bin/bash", "-o", "pipefail", "-c"]

# Install necessary packages and clean up
# Install necessary packages
RUN apt-get update && \
apt-get install -y --no-install-recommends \
tzdata=2024a-3ubuntu1.1 \
Expand All @@ -35,25 +35,30 @@ RUN apt-get update && \
nano=7.2-2build1 \
vim=2:9.1.0016-1ubuntu7.5 \
python3.12=3.12.3-1ubuntu0.3 \
python3-pip=24.0+dfsg-1ubuntu1.1 && \
ln -fs /usr/share/zoneinfo/$TZ /etc/localtime && \
dpkg-reconfigure --frontend noninteractive tzdata && \
python3-pip=24.0+dfsg-1ubuntu1.1 \
supervisor=4.2.5-1ubuntu0.1 && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

# Configure the timezone
RUN echo $TZ > /etc/timezone && \
rm /etc/localtime && \
ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && \
dpkg-reconfigure -f noninteractive tzdata

# Install yq (architecture-aware)
RUN ARCH=$(uname -m) && \
if [ "$ARCH" = "x86_64" ]; then ARCH="amd64"; elif [ "$ARCH" = "aarch64" ]; then ARCH="arm64"; fi && \
curl -sL https://github.com/mikefarah/yq/releases/download/v4.44.3/yq_linux_${ARCH}.tar.gz | tar xz && \
curl -sL https://github.com/mikefarah/yq/releases/download/v4.44.6/yq_linux_${ARCH}.tar.gz | tar xz && \
mv yq_linux_${ARCH} /usr/bin/yq && \
rm -rf /tmp/*

# Install Google Cloud SDK (architecture-aware)
RUN ARCH=$(uname -m) && \
if [ "$ARCH" = "x86_64" ]; then \
curl -sSL "https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-504.0.0-linux-x86_64.tar.gz" -o google-cloud-sdk.tar.gz; \
curl -sSL "https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-504.0.0-linux-x86_64.tar.gz" -o google-cloud-sdk.tar.gz; \
elif [ "$ARCH" = "aarch64" ]; then \
curl -sSL "https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-504.0.0-linux-arm.tar.gz" -o google-cloud-sdk.tar.gz; \
curl -sSL "https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-504.0.0-linux-arm.tar.gz" -o google-cloud-sdk.tar.gz; \
fi && \
tar -xzf google-cloud-sdk.tar.gz && \
./google-cloud-sdk/install.sh -q && \
Expand Down Expand Up @@ -84,11 +89,11 @@ RUN mkdir -p $GNUPGHOME && \
# Install Bitwarden CLI (architecture-aware)
RUN ARCH=$(uname -m) && \
if [ "$ARCH" = "x86_64" ]; then \
curl -Lso /usr/local/bin/bw "https://vault.bitwarden.com/download/linux/amd64/bw"; \
curl -Lso /usr/local/bin/bw "https://vault.bitwarden.com/download/linux/amd64/bw"; \
elif [ "$ARCH" = "aarch64" ]; then \
curl -Lso /usr/local/bin/bw "https://vault.bitwarden.com/download/linux/arm64/bw"; \
curl -Lso /usr/local/bin/bw "https://vault.bitwarden.com/download/linux/arm64/bw"; \
else \
echo "Unsupported architecture: $ARCH" && exit 1; \
echo "Unsupported architecture: $ARCH" && exit 1; \
fi && \
chmod +x /usr/local/bin/bw && \
rm -rf /tmp/* /var/tmp/*
Expand All @@ -97,6 +102,13 @@ RUN ARCH=$(uname -m) && \
RUN groupadd -g ${GID} ${USER} && \
useradd -l -m -u ${UID} -g ${GID} -s /bin/bash ${USER}

# Create the Supervisor log directory and set permissions
RUN mkdir -p /var/log/supervisor && \
chown -R ${USER}:${USER} /var/log/supervisor

# Create a directory for Supervisor runtime files
RUN mkdir -p /var/run/supervisor && chown -R ${USER}:${USER} /var/run/supervisor

# Prepare directories for the user and worker configuration
RUN mkdir -p /etc/worker /home/${USER}/.cd/bin /home/${USER}/.cd/configs && \
touch /home/${USER}/.cd/configs/merged_worker.yml && \
Expand All @@ -109,7 +121,8 @@ RUN mkdir -p /etc/worker /home/${USER}/.cd/bin /home/${USER}/.cd/configs && \
WORKDIR /home/${USER}

# Copy built-in worker.yml to the container
COPY ./src/configs/worker.yml /etc/worker/worker.yml
COPY ./src/configs /etc/worker
COPY ./src/scripts /usr/local/scripts

# Copy the bin, etc, and lib directories
COPY ./etc/home /home/${USER}/etc
Expand Down
26 changes: 26 additions & 0 deletions etc/home/supervisor.default
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[supervisord]
logfile=/var/log/supervisor/supervisord.log
logfile_maxbytes=50MB
logfile_backups=10
loglevel=info
pidfile=/var/run/supervisor/supervisord.pid
nodaemon=false
minfds=1024
minprocs=200

[supervisorctl]
serverurl=unix:///var/run/supervisor/supervisord.sock

[unix_http_server]
file=/var/run/supervisor/supervisord.sock ; path to your socket file

[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

[program:${process_name}]
command=${command}
autostart=${autostart}
autorestart=${autorestart}
stderr_logfile=/var/log/supervisor/${process_name}.err.log
stdout_logfile=/var/log/supervisor/${process_name}.out.log
environment=${envs}
13 changes: 12 additions & 1 deletion lib/environment.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ source_if_exists "$SCRIPT_DIR/auth.sh"
source_if_exists "$SCRIPT_DIR/secrets.sh"
# shellcheck source=./cleanup.sh
source_if_exists "$SCRIPT_DIR/cleanup.sh"
# shellcheck source=./process_manager.sh
source_if_exists "$SCRIPT_DIR/process_manager.sh"
# shellcheck source=./worker_config.sh
source_if_exists "$SCRIPT_DIR/worker_config.sh"

Expand Down Expand Up @@ -86,7 +88,16 @@ configure_environment() {
return 1
fi

log_info "Environment setup completed successfully."
# Perform process manager setup
log_info "Setting up process manager..."
if should_generate_config; then
echo "Preparing Supervisor configurations..."
configure_and_execute_services
else
echo "No services found in $CONFIG_FILE. Skipping Supervisor configuration."
fi

log_info "Secure environment setup completed successfully."
}

# Call the main function
Expand Down
87 changes: 87 additions & 0 deletions lib/process_manager.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#!/bin/bash

# Define paths
CONFIG_FILE="/etc/worker/services.yml"
TEMPLATE_FILE="/home/${USER}/etc/supervisor.default"
FINAL_CONFIG="/home/${USER}/etc/supervisord.conf"

# Function to check for service configurations
should_generate_config() {
local enabled_services_count
# Extract enabled services into JSON format
services_yaml=$(yq e -o=json '.services[] | select(.enabled == true)' "$CONFIG_FILE")
# Count the number of items in the JSON array, trimming any newlines or spaces
enabled_services_count=$(echo "$services_yaml" | jq -c '. | length' | tr -d '\n')

# Check if the configuration file exists and there is at least one enabled service
if [ -f "$CONFIG_FILE" ] && [ "${enabled_services_count:-0}" -gt 0 ]; then
return 0
else
return 1
fi
}

# Helper function to parse and process each service configuration
parse_service_info() {
local service_json="$1"
local name command autostart autorestart environment

name=$(echo "$service_json" | jq -r '.name')
command=$(echo "$service_json" | jq -r '.command')
autostart=$(echo "$service_json" | jq -r '.autostart // "false"')
autorestart=$(echo "$service_json" | jq -r '.autorestart // "false"')
environment=$(echo "$service_json" | jq -r '.environment // [] | join(",")')

sed "s|\${process_name}|$name|g; \
s|\${command}|$command|g; \
s|\${autostart}|$autostart|g; \
s|\${autorestart}|$autorestart|g; \
s|\${envs}|$environment|g" "$TEMPLATE_FILE" >> "$FINAL_CONFIG"
}

# Function to start Supervisor with the generated configuration
start_supervisor() {
echo "Starting Supervisor with the generated configuration..."
supervisord -c "$FINAL_CONFIG"
}

# Function to configure and start the Supervisor
configure_and_execute_services() {
if ! should_generate_config; then
echo "No services found in $CONFIG_FILE. Skipping Supervisor configuration."
return 1
fi

# Copy the base Supervisor configuration.
cp "$TEMPLATE_FILE" "$FINAL_CONFIG"

# Remove the template [program:x] section from FINAL_CONFIG
# shellcheck disable=SC2016
sed -i '/\[program:\${process_name}\]/,/^$/d' "$FINAL_CONFIG"

# Convert enabled services to JSON and process each.
local services_yaml
# Filter only services with enabled: true
services_yaml=$(yq e -o=json '.services[] | select(.enabled == true)' "$CONFIG_FILE" | jq -c .)

if [ -z "$services_yaml" ]; then
echo "Failed to parse services from $CONFIG_FILE or no services defined."
return 1
fi

# Use a temporary file to avoid subshell issues
local services_file
services_file=$(mktemp)

echo "$services_yaml" > "$services_file"
mapfile -t services_array < "$services_file"
rm -f "$services_file"

# Process each service in the array
for service_json in "${services_array[@]}"; do
parse_service_info "$service_json"
done

# After generating the config and processing services, start Supervisor
start_supervisor
}
18 changes: 18 additions & 0 deletions src/configs/services.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
kind: workerService
version: udx.io/worker-v1/service
services:
- name: "app_service"
enabled: "false"
command: "sh /usr/local/scripts/process_example.sh"
autostart: "true"
autorestart: "false"
environment:
- "KEY1=value1"
- "KEY2=value2"
- name: "another_service"
enabled: "false"
command: "sh /usr/local/scripts/process_example.sh"
autostart: "true"
autorestart: "true"
environment: []
13 changes: 13 additions & 0 deletions src/scripts/process_example.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/bash

# Script to run as a supervisor service in a loop

end_time=$(($(date +%s) + 30)) # Calculate end_time as current epoch time plus 30 seconds.

while [ "$(date +%s)" -lt $end_time ]; do
echo "Service is running at $(date)"
sleep 5
done

echo "30 seconds have elapsed. Exiting now."
exit 0 # Clean exit with status 0, indicating success.
63 changes: 63 additions & 0 deletions tests/readme
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Test Suite for UDX Worker

This directory contains a suite of tests designed to validate various functionalities of the UDX Worker environment. Below you'll find descriptions of each test script and how to run them.

## Test Scripts

### Dependency Validation
- **File**: `tests/tasks/10_dependencies.sh`
- **Description**: Verifies the availability of essential commands like `gcloud`, `aws`, `az`, `bw`, `yq`, `jq`.
- **Run Command**:
```shell
bash tests/tasks/10_dependencies.sh
```

### Configuration Validation
- **File**: `tests/tasks/20_config.sh`
- **Description**: Validates the environment configuration comparing to defined worker config. Could be tested with/without user config.
- **Run Command**:
```shell
bash tests/tasks/20_config.sh
```

### Authorization Validation
- **File**: `tests/tasks/30_auth.sh`
- **Description**: Validates the authorization of actors by provided creds to worker container.
- **Run Command**:
```shell
bash tests/tasks/30_auth.sh
```

### Secrets Fetching Validation
- **File**: `tests/tasks/40_secrets.sh`
- **Description**: Verifies the fetching and validation of secrets defined in user config.
- **Run Command**:
```shell
bash tests/tasks/40_secrets.sh
```

## Running All Tests
To run all the tests sequentially, you can use the main test script:

- **File**: `tests/main.sh`
- **Description**: Finds and executes all test scripts in the `tests/tasks` directory.
- **Run Command**:
```shell
bash tests/main.sh
```

## Example Configuration File
To test a user's worker config, configure and mount it into the running container at the path `/home/udx/.cd/configs/worker.yml`. Here is an example:

```yaml
---
kind: workerConfig
version: udx.io/worker-v1/config
config:
env:
AZURE_CLIENT_ID: "12345678-1234-1234-1234-1234567890ab"
AZURE_TENANT_ID: "abcdef12-3456-7890-abcd-ef1234567890"
AZURE_SUBSCRIPTION_ID: "1234abcd-5678-90ef-abcd-12345678abcd"
secrets:
TEST_SECRET: "azure/kv-udx-worker-tooling/test-secret"
```
Loading