Files
peasead-elastic-container/elastic-container.py
Andrew Pease 9b48e37035 Remove bash script reference from docstring
- No longer a 'port' - this is now the primary implementation
- Remove reference to elastic-container.sh bash script
2025-12-01 14:42:02 -06:00

422 lines
14 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Elastic Container - Python Edition
A 100% containerized Elastic Stack with Elasticsearch, Kibana, Fleet,
and the Detection Engine all pre-configured and ready to use.
"""
import sys
import time
import click
from lib.config import Config
from lib.docker_manager import DockerManager
from lib.kibana_manager import KibanaManager
from lib.utils import get_host_ip, check_prerequisites
# ASCII art banner
BANNER = """
╔═══════════════════════════════════════════════════════════════════╗
║ ║
║ ELASTIC CONTAINER - Python Edition ║
║ ║
║ Containerized Elastic Stack with Detection Engine & Fleet ║
║ ║
╚═══════════════════════════════════════════════════════════════════╝
"""
@click.group(invoke_without_command=True)
@click.option('-v', '--verbose', is_flag=True, help='Enable verbose output')
@click.pass_context
def cli(ctx, verbose):
"""Elastic Container - Manage your containerized Elastic Stack"""
# Store verbose flag in context for subcommands
ctx.ensure_object(dict)
ctx.obj['verbose'] = verbose
# If no command provided, show help
if ctx.invoked_subcommand is None:
click.echo(ctx.get_help())
@cli.command()
@click.pass_context
def stage(ctx):
"""Download all necessary Docker images to local storage"""
verbose = ctx.obj['verbose']
try:
# Initialize components
config = Config()
docker_manager = DockerManager(config, verbose=verbose)
# Check Docker availability
if not docker_manager.check_docker_available():
sys.exit(1)
# Pull images
if docker_manager.stage():
sys.exit(0)
else:
sys.exit(1)
except KeyboardInterrupt:
click.echo("\n\nOperation cancelled by user.")
sys.exit(130)
except Exception as e:
click.secho(f"\n✗ Error: {e}", fg="red", bold=True)
if verbose:
import traceback
click.echo(traceback.format_exc())
sys.exit(1)
@cli.command()
@click.option('--force-setup', is_flag=True, help='Force full setup even if containers exist')
@click.pass_context
def start(ctx, force_setup):
"""Create container network and start all stack containers"""
verbose = ctx.obj['verbose']
try:
if verbose:
click.echo(BANNER)
# Check prerequisites
click.secho("Checking prerequisites...", fg="cyan")
if not check_prerequisites(verbose=verbose):
sys.exit(1)
# Load configuration
config = Config()
# Validate passwords have been changed
config.validate_passwords()
# Initialize managers
docker_manager = DockerManager(config, verbose=verbose)
# Check Docker availability
if not docker_manager.check_docker_available():
sys.exit(1)
# Check if containers already exist
containers_exist, containers_running = docker_manager.check_containers_exist()
if containers_exist and not force_setup:
if containers_running:
click.echo()
click.secho("✓ Containers are already running!", fg="green", bold=True)
click.echo()
click.secho(f"Browse to {config.local_kibana_url}", fg="cyan", bold=True)
click.echo()
if verbose:
click.echo("Use 'python elastic-container.py status' to check container status")
click.echo("Use 'python elastic-container.py --force-setup start' to reconfigure")
sys.exit(0)
else:
click.echo()
click.secho("🔄 Containers exist but are stopped. Starting them...", fg="cyan", bold=True)
click.echo()
# Just start existing containers (quick)
if not docker_manager.start():
sys.exit(1)
click.echo()
click.secho("=" * 70, fg="green", bold=True)
click.secho("✓ Containers started successfully!", fg="green", bold=True)
click.secho("=" * 70, fg="green", bold=True)
click.echo()
click.secho(f"Browse to {config.local_kibana_url}", fg="cyan", bold=True)
click.echo()
if verbose:
click.secho("Login Credentials:", fg="yellow", bold=True)
click.echo(f" Username: {config.elastic_username}")
click.echo(f" Password: {config.elastic_password}")
click.echo()
click.secho("Note:", fg="yellow")
click.echo(" • Existing configuration preserved")
click.echo(" • Use --force-setup flag to reconfigure if needed")
click.echo()
sys.exit(0)
# First-time setup or forced setup
if force_setup:
click.echo()
click.secho("🔧 Force setup requested - running full configuration...", fg="yellow")
# Get host IP for Fleet configuration
host_ip = get_host_ip()
if verbose:
click.secho(f"Host IP: {host_ip}", fg="cyan")
# Start containers
if not docker_manager.start():
sys.exit(1)
# Initialize Kibana manager
kibana_manager = KibanaManager(config, docker_manager, host_ip, verbose=verbose)
# Configure Detection Engine and rules
if not kibana_manager.configure_detection_engine():
click.secho("\n⚠ Warning: Detection Engine configuration had issues", fg="yellow")
click.echo("The stack is running but some features may not be configured.")
# Wait for Fleet Server to be ready
click.echo()
click.secho("Waiting 40 seconds for Fleet Server setup...", fg="cyan")
click.echo()
time.sleep(40)
# Configure Fleet
click.echo("Populating Fleet Settings...")
if not kibana_manager.setup_fleet():
click.secho("\n⚠ Warning: Fleet configuration had issues", fg="yellow")
click.echo("The stack is running but Fleet may not be fully configured.")
# Success message
click.echo()
click.secho("=" * 70, fg="green", bold=True)
click.secho("✓ Elastic Stack started successfully", fg="green", bold=True)
click.secho("=" * 70, fg="green", bold=True)
click.echo()
click.secho(f"Browse to {config.local_kibana_url}", fg="cyan", bold=True)
click.echo()
if verbose:
click.secho("Login Credentials:", fg="yellow", bold=True)
click.echo(f" Username: {config.elastic_username}")
click.echo(f" Password: {config.elastic_password}")
click.echo()
click.secho("Note:", fg="yellow")
click.echo(" • You may see a browser warning due to self-signed certificates")
click.echo(" • Type 'thisisnotsafe' or click proceed to continue to login")
click.echo()
sys.exit(0)
except KeyboardInterrupt:
click.echo("\n\nOperation cancelled by user.")
sys.exit(130)
except Exception as e:
click.secho(f"\n✗ Error: {e}", fg="red", bold=True)
if verbose:
import traceback
click.echo(traceback.format_exc())
sys.exit(1)
@cli.command()
@click.pass_context
def stop(ctx):
"""Stop running containers without removing them"""
verbose = ctx.obj['verbose']
try:
config = Config()
docker_manager = DockerManager(config, verbose=verbose)
if not docker_manager.check_docker_available():
sys.exit(1)
# Check if containers exist
containers_exist, containers_running = docker_manager.check_containers_exist()
if not containers_exist:
click.echo()
click.secho("✗ No containers found!", fg="red", bold=True)
click.echo()
click.echo("Run 'python elastic-container.py start' to create containers first.")
click.echo()
sys.exit(1)
if not containers_running:
click.echo()
click.secho("✓ Containers are already stopped", fg="green")
click.echo()
sys.exit(0)
if docker_manager.stop():
sys.exit(0)
else:
sys.exit(1)
except KeyboardInterrupt:
click.echo("\n\nOperation cancelled by user.")
sys.exit(130)
except Exception as e:
click.secho(f"\n✗ Error: {e}", fg="red", bold=True)
if verbose:
import traceback
click.echo(traceback.format_exc())
sys.exit(1)
@cli.command()
@click.pass_context
def destroy(ctx):
"""Stop and remove containers, networks, and volumes"""
verbose = ctx.obj['verbose']
try:
config = Config()
docker_manager = DockerManager(config, verbose=verbose)
if not docker_manager.check_docker_available():
sys.exit(1)
if docker_manager.destroy():
sys.exit(0)
else:
sys.exit(1)
except KeyboardInterrupt:
click.echo("\n\nOperation cancelled by user.")
sys.exit(130)
except Exception as e:
click.secho(f"\n✗ Error: {e}", fg="red", bold=True)
if verbose:
import traceback
click.echo(traceback.format_exc())
sys.exit(1)
@cli.command()
@click.pass_context
def restart(ctx):
"""Restart all Elastic Stack containers"""
verbose = ctx.obj['verbose']
try:
config = Config()
docker_manager = DockerManager(config, verbose=verbose)
if not docker_manager.check_docker_available():
sys.exit(1)
# Check if containers exist
containers_exist, containers_running = docker_manager.check_containers_exist()
if not containers_exist:
click.echo()
click.secho("✗ No containers found!", fg="red", bold=True)
click.echo()
click.echo("Run 'python elastic-container.py start' to create and start containers first.")
click.echo()
sys.exit(1)
if not containers_running:
click.echo()
click.secho("⚠ Containers exist but are not running", fg="yellow")
click.echo("Starting them instead of restarting...")
click.echo()
if docker_manager.start():
click.secho("✓ Containers started successfully!", fg="green")
sys.exit(0)
else:
sys.exit(1)
# Containers exist and are running - restart them
if docker_manager.restart():
sys.exit(0)
else:
sys.exit(1)
except KeyboardInterrupt:
click.echo("\n\nOperation cancelled by user.")
sys.exit(130)
except Exception as e:
click.secho(f"\n✗ Error: {e}", fg="red", bold=True)
if verbose:
import traceback
click.echo(traceback.format_exc())
sys.exit(1)
@cli.command()
@click.pass_context
def status(ctx):
"""Check the status of all stack containers"""
verbose = ctx.obj['verbose']
try:
config = Config()
docker_manager = DockerManager(config, verbose=verbose)
if not docker_manager.check_docker_available():
sys.exit(1)
if docker_manager.status():
sys.exit(0)
else:
sys.exit(1)
except KeyboardInterrupt:
click.echo("\n\nOperation cancelled by user.")
sys.exit(130)
except Exception as e:
click.secho(f"\n✗ Error: {e}", fg="red", bold=True)
if verbose:
import traceback
click.echo(traceback.format_exc())
sys.exit(1)
@cli.command()
@click.pass_context
def clear(ctx):
"""Clear all documents in logs and metrics data streams"""
verbose = ctx.obj['verbose']
try:
config = Config()
docker_manager = DockerManager(config, verbose=verbose)
if not docker_manager.check_docker_available():
sys.exit(1)
# Get host IP
host_ip = get_host_ip()
if docker_manager.clear_documents(host_ip):
sys.exit(0)
else:
sys.exit(1)
except KeyboardInterrupt:
click.echo("\n\nOperation cancelled by user.")
sys.exit(130)
except Exception as e:
click.secho(f"\n✗ Error: {e}", fg="red", bold=True)
if verbose:
import traceback
click.echo(traceback.format_exc())
sys.exit(1)
@cli.command('help')
@click.pass_context
def help_command(ctx):
"""Show this help message"""
# Get the parent context (the group) to show its help
click.echo(ctx.parent.get_help())
def main():
"""Main entry point"""
cli(obj={})
if __name__ == '__main__':
main()