Skip to content

Commit

Permalink
Merge pull request #4 from quixio/feature/recoverfrompendingupgrade
Browse files Browse the repository at this point in the history
Recover process implemented for #64430
  • Loading branch information
joseEnrique authored Jan 29, 2025
2 parents 8a3dade + 899b7bf commit c240eae
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 33 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,6 @@ venv.bak/
# Celery stuff
celerybeat-schedule
celerybeat
tmp/
tmp/

.DS_Store
27 changes: 26 additions & 1 deletion plugin.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: "quix-manager"
version: "0.1.1"
version: "0.2.0"
usage: "helm quix-manager update --release-name quixplatform-manager --repo quixcontainerregistry.azurecr.io/helm/quixplatform-manager:1.5.4"
description: |-
A helm plugin to install quix.
Expand All @@ -10,6 +10,31 @@ hooks:
install: |
#!/bin/bash
set -e
echo "Updating the plugin ...."
# Check for Python 3
if command -v python3 &>/dev/null; then
PYTHON_CMD=python3
elif command -v python &>/dev/null; then
PYTHON_CMD=python
else
echo "Python 3 is required for this plugin. Please install Python 3."
exit 1
fi
# Verify that the Python command is version 3
if ! $PYTHON_CMD -c 'import sys; exit(0) if sys.version_info[0] == 3 else exit(1)'; then
echo "Detected Python version is not 3. Please use Python 3."
exit 1
fi
# Create a virtual environment in the plugin directory
$PYTHON_CMD -m venv "$HELM_PLUGIN_DIR/venv"
# Activate the virtual environment and install pip requirements
"$HELM_PLUGIN_DIR/venv/bin/pip" install -r "$HELM_PLUGIN_DIR/src/requirements.txt"
update: |
#!/bin/bash
set -e
# Check for Python 3
if command -v python3 &>/dev/null; then
Expand Down
54 changes: 29 additions & 25 deletions quix_install_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,32 @@ def generate_configmap(logs, configmap_name='quix-manager-log-configmap', namesp
}
return configmap

def setup_logging(verbose: bool):
# Define the log format
log_format = '# %(asctime)s - %(name)s - %(levelname)s - %(message)s'
# Create an in-memory stream to capture logs
log_stream = io.StringIO()

# Configure logger for both stdout and in-memory logging
logger = logging.getLogger('quix-manager')
logger.propagate = False # Prevent log messages from propagating to the root logger

if not logger.hasHandlers():
# Handler for logging to stdout
console_handler = logging.StreamHandler()
console_handler.setFormatter(logging.Formatter(log_format))
logger.addHandler(console_handler)

# Handler for capturing logs in memory
memory_handler = logging.StreamHandler(log_stream)
memory_handler.setFormatter(logging.Formatter(log_format))
logger.addHandler(memory_handler)

# Set log level based on verbosity
logger.setLevel(logging.DEBUG if verbose else logging.INFO)

return logger, log_stream



if __name__ == "__main__":
Expand All @@ -33,39 +59,17 @@ def generate_configmap(logs, configmap_name='quix-manager-log-configmap', namesp
parser.add_argument('--logs-as-config', action='store_true', help='Write in the stdout a configmap with all logs happened. This is essentially for argocd')


# Set up logging
log_format = '# %(levelname)s: %(message)s'

# Create an in-memory stream to capture logs
log_stream = io.StringIO()

# Configure logger for both stdout and in-memory logging
logger = logging.getLogger('helm_logger')

# Get the args from command
args, _ = parser.parse_known_args()
# Avoid adding duplicate handlers in case the script runs multiple times
if not logger.hasHandlers():
# Handler for logging to stdout
console_handler = logging.StreamHandler()
console_handler.setFormatter(logging.Formatter(log_format))
logger.addHandler(console_handler)

# Handler for capturing logs in memory
memory_handler = logging.StreamHandler(log_stream)
memory_handler.setFormatter(logging.Formatter(log_format))
logger.addHandler(memory_handler)

if args.verbose:
logger.setLevel(logging.DEBUG)
else:
logger.setLevel(logging.INFO)
# Set up logging
logger, log_stream = setup_logging(args.verbose)

logger.info("Starting Helm command execution")
# Log some initial info
helm_manager = HelmManager(args)
helm_manager.run()
if args.logs_as_config:

# Retrieve the logs f rom the in-memory log stream
log_contents = log_stream.getvalue()

Expand Down
65 changes: 59 additions & 6 deletions src/helm_manager.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import os, sys, shutil, subprocess, yaml, tarfile,logging
from argparse import Namespace

logging = logging.getLogger('helm_logger')
logging = logging.getLogger('quix-manager')

class HelmManager:
def __init__(self, args: Namespace = None):
Expand Down Expand Up @@ -37,12 +37,11 @@ def __init__(self, args: Namespace = None):
self.merged_file_path = os.path.join(deployment_dir, f"{self.release_name}merged.yaml")


def _run_helm_with_args(self, helm_args: list, output_file: str = None):
def _run_helm_with_args(self, helm_args: list):
"""
Runs a Helm command with the provided arguments.
:param helm_args: List of arguments for the Helm command.
:param output_file: Optional file to store Helm command output.
"""
command = ['helm'] + helm_args
logging.debug(f"Executing Helm command: {command}")
Expand Down Expand Up @@ -93,7 +92,51 @@ def _check_if_exists(self, release_name: str):
list_args.extend(["--namespace", self.namespace])
result = self._run_helm_with_args(list_args)
return release_name in result.stdout.decode('utf-8')

@staticmethod
def parse_output(output_str):
"""
Parse the output of a Helm command into a dictionary.
"""
result = {}
# Split the output into lines and extract key-value pairs
lines = output_str.strip().split('\n')
for line in lines:
if ':' in line:
key, value = line.split(':', 1)
result[key.strip()] = value.strip()

return result

def _rollback(self, revision: str):
"""
Rollback to the specified revision of a Helm release.
:param revision: Revision number to rollback to.
"""
list_args = ['rollback', self.release_name, revision ]
if self.namespace:
list_args.extend(['--namespace', self.namespace])
self._run_helm_with_args(list_args)

logging.info(f"Rolled back to revision {revision}.")

def _get_release_status(self):
"""
Retrieves the current status of a Helm release.
:param release_name: Name of the Helm release.
:return: Status of the release.
"""
list_args = ['status', self.release_name]
if self.namespace:
list_args.extend(['--namespace', self.namespace])
status_result = self._run_helm_with_args(list_args)
status_output = self.parse_output(status_result.stdout.decode('utf-8'))

logging.info(f"The status of the Helm chart {self.release_name} is: {status_output.get('STATUS')}")
return status_output

def pull_repo(self):
"""
Pulls the Helm chart from the specified repository.
Expand Down Expand Up @@ -140,15 +183,15 @@ def _template_with_merged_values(self):
if self.namespace:
list_args.extend(["--namespace", self.namespace])
result = self._run_helm_with_args(list_args)
print(result.stdout.decode('utf-8'))

def run(self):
"""
Executes the main logic: checks if the release exists, retrieves values, merges YAML files,
and either updates the release or generates a template.
"""
exist_release = self._check_if_exists(release_name=self.release_name)

exist_release = self._check_if_exists(release_name=self.release_name)

if exist_release:
try:
values = self._get_values(release_name=self.release_name)
Expand All @@ -173,7 +216,17 @@ def run(self):
logging.error(f"Error during execution: {e}")
sys.exit(1)
else:
logging.warning(f"Release {self.release_name} does not exist. You need to install it first.")
status = self._get_release_status()
if status.get('STATUS') == 'pending-upgrade':
logging.debug(f"Release {self.release_name} is in pending-upgrade status.")
revision = str(int(status.get('REVISION'))-1)
self._rollback(revision)
logging.debug(f"Release {self.release_name} has been rolled back and running the upgrade.")
self.run()
else:
logging.error(f"Release {self.release_name} does not exist. You need to install it first.")
sys.exit(1)



class FileManager:
Expand Down

0 comments on commit c240eae

Please sign in to comment.