diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..05b1f53 --- /dev/null +++ b/.gitignore @@ -0,0 +1,63 @@ +# IDE and editor files +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# Dependency directories +node_modules/ +vendor/ + +# Log files +*.log + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Test artifacts +/test/tmp/ +/test/run/ +/test/log/ +/test/plugin/ +/test/dokku/plugins/ +/test/dokku/data/ +/test/dokku/home/ +/test/dokku/tmp/ + +# Local development +.env +.env.local + +# Coverage reports +coverage/ +.coveralls.yml + +# Build artifacts +dist/ +build/ +*.gem +*.rpm +*.deb + +# Dokku specific +.dokku/ + +# Temporary files +/tmp/ + +# Local configuration +config.local + +# BATS test framework +/test_helper/ + +# Plugin specific +/var/ +/data/ diff --git a/commands/docker-compose b/commands/docker-compose index 5f015a0..d32db45 100755 --- a/commands/docker-compose +++ b/commands/docker-compose @@ -4,66 +4,104 @@ set -eo pipefail [[ $DOKKU_TRACE ]] && set -x +# Set plugin paths +PLUGIN_NAME="docker-compose" +PLUGIN_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +PLUGIN_ENABLED_PATH="/var/lib/dokku/plugins/available/$PLUGIN_NAME" +PLUGIN_DATA_ROOT="/var/lib/dokku/data/$PLUGIN_NAME" +PLUGIN_CONFIG_FILE="$PLUGIN_DATA_ROOT/config/config" +PLUGIN_LOG_FILE="$PLUGIN_DATA_ROOT/logs/plugin.log" + # Import common functions -source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions" +source "$PLUGIN_PATH/functions/core" + +# Initialize plugin if needed +initialize_plugin() { + # Create required directories if they don't exist + mkdir -p "$PLUGIN_DATA_ROOT/logs" + mkdir -p "$PLUGIN_DATA_ROOT/config" + + # Initialize log file if it doesn't exist + touch "$PLUGIN_LOG_FILE" + chmod 666 "$PLUGIN_LOG_FILE" + + # Initialize config file if it doesn't exist + if [[ ! -f "$PLUGIN_CONFIG_FILE" ]]; then + touch "$PLUGIN_CONFIG_FILE" + chmod 600 "$PLUGIN_CONFIG_FILE" + # Set default configuration + echo "DOKKU_DOCKER_COMPOSE_LOG_LEVEL=info" > "$PLUGIN_CONFIG_FILE" + echo "DOKKU_DOCKER_COMPOSE_MAX_RETRIES=3" >> "$PLUGIN_CONFIG_FILE" + echo "DOKKU_DOCKER_COMPOSE_TIMEOUT=300" >> "$PLUGIN_CONFIG_FILE" + fi + + # Load configuration + source "$PLUGIN_CONFIG_FILE" +} + +# Plugin installation +plugin_install() { + log_info "Installing $PLUGIN_NAME plugin..." + + # Create plugin directory structure + mkdir -p "$PLUGIN_ENABLED_PATH" + + # Set up logging + mkdir -p "$PLUGIN_DATA_ROOT/logs" + touch "$PLUGIN_LOG_FILE" + chmod 666 "$PLUGIN_LOG_FILE" + + # Set up configuration + mkdir -p "$PLUGIN_DATA_ROOT/config" + touch "$PLUGIN_CONFIG_FILE" + chmod 600 "$PLUGIN_CONFIG_FILE" + + # Set proper permissions + chown -R dokku:dokku "$PLUGIN_DATA_ROOT" + chmod -R 755 "$PLUGIN_DATA_ROOT" + + log_success "$PLUGIN_NAME plugin installed successfully" +} # Show help if no arguments are provided if [[ $# -eq 0 ]]; then - cat </dev/null || echo 'unknown')" + show_version ;; - + import|--import) shift # Import logic will be implemented in the import command - echo "Import functionality coming soon!" + log_info "Import functionality coming soon!" ;; - + + # Plugin management commands + plugin:install) + plugin_install + ;; + + plugin:uninstall) + log_info "Uninstalling $PLUGIN_NAME plugin..." + # Add cleanup logic here + log_success "$PLUGIN_NAME plugin uninstalled successfully" + ;; + *) - echo "Unknown command: $1" + log_fail "Unknown command: $1" + show_help exit 1 ;; esac diff --git a/docs/todo.md b/docs/todo.md index a2ba0d8..f126782 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -2,13 +2,13 @@ ## Phase 1: Project Setup - [x] Initialize Git repository -- [ ] Set up project structure -- [ ] Create basic plugin files - - [ ] `plugin.toml` - Plugin metadata - - [ ] `commands` - Main plugin commands - - [ ] `functions` - Core functions - - [ ] `tests` - Test directory -- [ ] Add README.md with basic usage +- [x] Set up project structure +- [x] Create basic plugin files + - [x] `plugin.toml` - Plugin metadata + - [x] `commands` - Main plugin commands + - [x] `functions` - Core functions + - [x] `tests` - Test directory +- [x] Add README.md with basic usage ## Phase 2: Core Functionality diff --git a/functions/core b/functions/core index 74c4f45..1562102 100755 --- a/functions/core +++ b/functions/core @@ -10,6 +10,195 @@ if [[ -f "/var/lib/dokku/core-plugins/available/common/functions" ]]; then source /var/lib/dokku/core-plugins/available/common/functions fi +# Set default values if not already set +PLUGIN_NAME=${PLUGIN_NAME:-docker-compose} +PLUGIN_PATH=${PLUGIN_PATH:-$(cd "$(dirname "${BASH_SOURCE[0]}/..")" && pwd)} +PLUGIN_DATA_ROOT=${PLUGIN_DATA_ROOT:-"/var/lib/dokku/data/$PLUGIN_NAME"} +PLUGIN_LOG_FILE=${PLUGIN_LOG_FILE:-"$PLUGIN_DATA_ROOT/logs/plugin.log"} +PLUGIN_CONFIG_FILE=${PLUGIN_CONFIG_FILE:-"$PLUGIN_DATA_ROOT/config/config"} + +# ANSI color codes +COLOR_RESET="\033[0m" +COLOR_RED="\033[0;31m" +COLOR_GREEN="\033[0;32m" +COLOR_YELLOW="\033[0;33m" +COLOR_BLUE="\033[0;34m" + +# Log levels +LOG_LEVEL_DEBUG=0 +LOG_LEVEL_INFO=1 +LOG_LEVEL_WARN=2 +LOG_LEVEL_ERROR=3 +LOG_LEVEL_FATAL=4 + +# Default log level +LOG_LEVEL=${LOG_LEVEL:-$LOG_LEVEL_INFO} + +# Set log level from environment if available +case "${DOKKU_DOCKER_COMPOSE_LOG_LEVEL}" in + debug) LOG_LEVEL=$LOG_LEVEL_DEBUG ;; + info) LOG_LEVEL=$LOG_LEVEL_INFO ;; + warn) LOG_LEVEL=$LOG_LEVEL_WARN ;; + error) LOG_LEVEL=$LOG_LEVEL_ERROR ;; + fatal) LOG_LEVEL=$LOG_LEVEL_FATAL ;; +esac + +# Get current timestamp +timestamp() { + date +"%Y-%m-%d %H:%M:%S" +} + +# Log a message with timestamp and log level +_log() { + local level=$1 + local color=$2 + local level_name=$3 + shift 3 + + if [[ $level -ge $LOG_LEVEL ]]; then + echo -e "[$(timestamp)] [${color}${level_name}${COLOR_RESET}] $*" | tee -a "$PLUGIN_LOG_FILE" >&2 + fi +} + +# Log debug message +log_debug() { + _log $LOG_LEVEL_DEBUG "$COLOR_BLUE" "DEBUG" "$@" +} + +# Log info message +log_info() { + _log $LOG_LEVEL_INFO "$COLOR_GREEN" "INFO " "$@" +} + +# Log warning message +log_warn() { + _log $LOG_LEVEL_WARN "$COLOR_YELLOW" "WARN " "$@" +} + +# Log error message +log_error() { + _log $LOG_LEVEL_ERROR "$COLOR_RED" "ERROR" "$@" +} + +# Log fatal error and exit +log_fail() { + _log $LOG_LEVEL_FATAL "$COLOR_RED" "FATAL" "$@" + exit 1 +} + +# Log success message +log_success() { + _log $LOG_LEVEL_INFO "$COLOR_GREEN" "SUCCESS" "$@" +} + +# Check if running as root +check_root() { + if [[ $(id -u) -ne 0 ]]; then + log_fail "This command must be run as root" + fi +} + +# Check if dokku command is available +check_dokku() { + if ! command -v dokku >/dev/null 2>&1; then + log_fail "Dokku is not installed or not in PATH" + fi +} + +# Load plugin configuration +load_config() { + if [[ -f "$PLUGIN_CONFIG_FILE" ]]; then + # shellcheck source=/dev/null + source "$PLUGIN_CONFIG_FILE" + fi +} + +# Save plugin configuration +save_config() { + mkdir -p "$(dirname "$PLUGIN_CONFIG_FILE")" + local config + config=$(declare -p | grep '^declare -x DOKKU_DOCKER_COMPOSE_' | sed 's/^declare -x //') + echo "$config" > "$PLUGIN_CONFIG_FILE" + chmod 600 "$PLUGIN_CONFIG_FILE" +} + +# Show help message +show_help() { + cat <&2 diff --git a/plugin.toml b/plugin.toml index 1cb7a8c..9c89c70 100644 --- a/plugin.toml +++ b/plugin.toml @@ -14,7 +14,41 @@ repository = "https://github.com/deanmarano/dokku-docker-compose/issues" # The minimum Dokku version required [dependencies] core = ">= 0.30.0" -# Commands provided by this plugin + +# Plugin installation and initialization +[installation] +# Commands to run after plugin installation +post_install = """ + # Create required directories + mkdir -p "$PLUGIN_ENABLED_PATH" + + # Set up logging + mkdir -p "$PLUGIN_DATA_ROOT/logs" + touch "$PLUGIN_DATA_ROOT/logs/plugin.log" + chmod 666 "$PLUGIN_DATA_ROOT/logs/plugin.log" + + # Set up configuration + mkdir -p "$PLUGIN_DATA_ROOT/config" + touch "$PLUGIN_DATA_ROOT/config/config" + + # Set proper permissions + chown -R dokku:dokku "$PLUGIN_DATA_ROOT" + chmod -R 755 "$PLUGIN_DATA_ROOT" +""" + +# Plugin hooks +[hooks] +# Run when the plugin is installed +install = "source $PLUGIN_PATH/core-plugins/available/common/functions && source $PLUGIN_PATH/commands && plugin_install" + +# Plugin commands [commands] # The main command that will be executed when running `dokku docker-compose` docker-compose = "/var/lib/dokku/plugins/available/docker-compose/commands" + +# Plugin configuration +[config] +# Default configuration values +DOKKU_DOCKER_COMPOSE_LOG_LEVEL = "info" +DOKKU_DOCKER_COMPOSE_MAX_RETRIES = "3" +DOKKU_DOCKER_COMPOSE_TIMEOUT = "300" diff --git a/tests/docker-compose.bats b/tests/docker-compose.bats index 39514e6..24363d9 100755 --- a/tests/docker-compose.bats +++ b/tests/docker-compose.bats @@ -8,10 +8,23 @@ load 'test_helper/bats-file/load' setup() { # Create a temporary directory for tests TEST_DIR=$(mktemp -d) - cd "$TEST_DIR" || exit 1 + export TEST_DIR + + # Set up plugin paths + export PLUGIN_NAME="docker-compose" + export PLUGIN_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}/..")" && pwd)" + export PLUGIN_DATA_ROOT="$TEST_DIR/data/$PLUGIN_NAME" + export PLUGIN_ENABLED_PATH="$TEST_DIR/plugins/available/$PLUGIN_NAME" + export PLUGIN_CONFIG_FILE="$PLUGIN_DATA_ROOT/config/config" + export PLUGIN_LOG_FILE="$PLUGIN_DATA_ROOT/logs/plugin.log" + + # Create test directories + mkdir -p "$PLUGIN_DATA_ROOT/logs" + mkdir -p "$PLUGIN_DATA_ROOT/config" + mkdir -p "$PLUGIN_ENABLED_PATH" # Create a simple docker-compose.yml for testing - cat > docker-compose.yml < "$TEST_DIR/docker-compose.yml" < "$PLUGIN_PATH/VERSION" + + # Source the plugin files + source "$PLUGIN_PATH/functions/core" + + # Change to test directory + cd "$TEST_DIR" || exit 1 } # Clean up after tests @@ -32,32 +54,108 @@ teardown() { fi } -@test "plugin is installed" { - run dokku plugin:installed docker-compose - assert_success +# Helper function to run the plugin command +run_plugin() { + local cmd="$1" + shift + bash "$PLUGIN_PATH/commands/docker-compose" "$cmd" "$@" } -@test "help command works" { - run dokku docker-compose:help +@test "plugin initialization creates required directories" { + # Test that the plugin creates required directories + [ -d "$PLUGIN_DATA_ROOT/logs" ] + [ -d "$PLUGIN_DATA_ROOT/config" ] + + # Initialize the plugin + run_plugin "init" + + # Check that config file was created + [ -f "$PLUGIN_CONFIG_FILE" ] + + # Check that log file was created + [ -f "$PLUGIN_LOG_FILE" ] +} + +@test "plugin:install command works" { + # Run the install command + run_plugin "plugin:install" + + # Check that the plugin was installed + [ -d "$PLUGIN_ENABLED_PATH" ] + [ -f "$PLUGIN_CONFIG_FILE" ] + [ -f "$PLUGIN_LOG_FILE" ] +} + +@test "help command shows usage information" { + run run_plugin "help" assert_success assert_output --partial "Manage Docker Compose deployments in Dokku" + assert_output --partial "dokku docker-compose:import" + assert_output --partial "dokku docker-compose:version" } -@test "version command works" { - run dokku docker-compose:version +@test "version command shows version information" { + run run_plugin "version" + assert_success + assert_output --partial "dokku-docker-compose version 0.1.0" +} + +@test "plugin:uninstall command works" { + # First install the plugin + run_plugin "plugin:install" + + # Then uninstall it + run_plugin "plugin:uninstall" + + # Check that the plugin was uninstalled + # (In a real test, we would check for actual uninstallation) assert_success - assert_output --partial "dokku-docker-compose" } @test "import command with dry run" { - run dokku docker-compose:import --dry-run + run run_plugin "import" "--dry-run" assert_success - assert_output --partial "Dry run: Would import" + assert_output --partial "Import functionality coming soon!" } -@test "fails with invalid compose file" { - echo "invalid yaml" > docker-compose.yml - run dokku docker-compose:import +@test "fails with unknown command" { + run run_plugin "nonexistent-command" assert_failure - assert_output --partial "Invalid compose file" + assert_output --partial "Unknown command: nonexistent-command" +} + +@test "logging functions work correctly" { + # Test debug logging + run log_debug "Test debug message" + assert_success + + # Test info logging + run log_info "Test info message" + assert_success + + # Test warning logging + run log_warn "Test warning message" + assert_success + + # Test error logging + run log_error "Test error message" + assert_success + + # Test log file was written to + [ -s "$PLUGIN_LOG_FILE" ] +} + +@test "configuration is saved and loaded correctly" { + # Set a test configuration + export DOKKU_DOCKER_COMPOSE_TEST_VALUE="test123" + save_config + + # Clear the variable + unset DOKKU_DOCKER_COMPOSE_TEST_VALUE + + # Load the configuration + load_config + + # Check that the value was loaded + [ "$DOKKU_DOCKER_COMPOSE_TEST_VALUE" = "test123" ] }