using System; using System.Management.Automation; using PSMinIO.Core; using PSMinIO.Core.S3; using PSMinIO.Utils; namespace PSMinIO.Cmdlets { /// /// Base class for all MinIO PowerShell cmdlets /// Provides common functionality including connection management, logging, and client access /// public abstract class MinIOBaseCmdlet : PSCmdlet { private MinIOConnection? _connection; /// /// MinIO connection to use for operations. Can be provided via parameter or retrieved from session. /// [Parameter(ValueFromPipeline = true)] [Alias("Connection")] public MinIOConnection? MinIOConnection { get; set; } /// /// Name of session variable containing the MinIO connection (default: MinIOConnection) /// [Parameter] [ValidateNotNullOrEmpty] public string SessionVariable { get; set; } = "MinIOConnection"; /// /// Gets the MinIO connection instance /// protected MinIOConnection Connection { get { if (_connection == null) { _connection = GetConnection(); if (_connection == null) { ThrowTerminatingError(new ErrorRecord( new InvalidOperationException("No MinIO connection available. Use Connect-MinIO to establish a connection, or provide a connection via the -MinIOConnection parameter."), "NoConnection", ErrorCategory.ConnectionError, null)); } if (!_connection!.IsValid) { ThrowTerminatingError(new ErrorRecord( new InvalidOperationException($"MinIO connection is not valid. Status: {_connection.Status}"), "InvalidConnection", ErrorCategory.ConnectionError, _connection)); } } return _connection!; } } /// /// Gets the MinIO S3 client instance /// protected MinIOS3Client S3Client => Connection.S3Client; /// /// Gets the connection from parameter or session variable /// /// MinIO connection or null if not found private MinIOConnection? GetConnection() { // First, try the parameter if (MinIOConnection != null) { return MinIOConnection; } // Then try the session variable try { var sessionConnection = SessionState.PSVariable.GetValue(SessionVariable); if (sessionConnection is MinIOConnection connection) { return connection; } } catch { // Ignore errors when accessing session variables } return null; } /// /// Executes an operation with consistent error handling and logging /// /// Name of the operation for logging /// Operation to execute /// Optional operation details for logging protected void ExecuteOperation(string operationName, Action operation, string? details = null) { var startTime = DateTime.UtcNow; MinIOLogger.LogOperationStart(this, operationName, details); try { operation(); var duration = DateTime.UtcNow - startTime; MinIOLogger.LogOperationComplete(this, operationName, duration, details); } catch (Exception ex) { MinIOLogger.LogOperationFailure(this, operationName, ex, details); // Determine appropriate error category var category = GetErrorCategory(ex); ThrowTerminatingError(new ErrorRecord(ex, $"{operationName}Failed", category, null)); } } /// /// Executes an operation with return value and consistent error handling and logging /// /// Return type /// Name of the operation for logging /// Operation to execute /// Optional operation details for logging /// Operation result protected T ExecuteOperation(string operationName, Func operation, string? details = null) { var startTime = DateTime.UtcNow; MinIOLogger.LogOperationStart(this, operationName, details); try { var result = operation(); var duration = DateTime.UtcNow - startTime; MinIOLogger.LogOperationComplete(this, operationName, duration, details); return result; } catch (Exception ex) { MinIOLogger.LogOperationFailure(this, operationName, ex, details); // Determine appropriate error category var category = GetErrorCategory(ex); ThrowTerminatingError(new ErrorRecord(ex, $"{operationName}Failed", category, null)); // This line will never be reached, but is required for compilation throw; } } /// /// Determines the appropriate PowerShell error category for an exception /// /// Exception to categorize /// Appropriate error category protected ErrorCategory GetErrorCategory(Exception exception) { return exception switch { ArgumentException or ArgumentNullException => ErrorCategory.InvalidArgument, UnauthorizedAccessException => ErrorCategory.PermissionDenied, System.Net.Http.HttpRequestException => ErrorCategory.ConnectionError, TimeoutException => ErrorCategory.OperationTimeout, InvalidOperationException => ErrorCategory.InvalidOperation, System.IO.FileNotFoundException => ErrorCategory.ObjectNotFound, System.IO.DirectoryNotFoundException => ErrorCategory.ObjectNotFound, System.IO.IOException => ErrorCategory.WriteError, NotSupportedException => ErrorCategory.NotImplemented, _ => ErrorCategory.NotSpecified }; } /// /// Validates that a bucket name is valid according to S3 naming rules /// /// Bucket name to validate /// Parameter name for error reporting protected void ValidateBucketName(string bucketName, string parameterName = "BucketName") { if (string.IsNullOrWhiteSpace(bucketName)) { ThrowTerminatingError(new ErrorRecord( new ArgumentException($"Bucket name cannot be null or empty", parameterName), "InvalidBucketName", ErrorCategory.InvalidArgument, bucketName)); } // Basic S3 bucket name validation if (bucketName.Length < 3 || bucketName.Length > 63) { ThrowTerminatingError(new ErrorRecord( new ArgumentException($"Bucket name must be between 3 and 63 characters long", parameterName), "InvalidBucketName", ErrorCategory.InvalidArgument, bucketName)); } if (!System.Text.RegularExpressions.Regex.IsMatch(bucketName, @"^[a-z0-9][a-z0-9\-]*[a-z0-9]$")) { ThrowTerminatingError(new ErrorRecord( new ArgumentException($"Bucket name contains invalid characters. Must contain only lowercase letters, numbers, and hyphens", parameterName), "InvalidBucketName", ErrorCategory.InvalidArgument, bucketName)); } } /// /// Validates that an object name is valid /// /// Object name to validate /// Parameter name for error reporting protected void ValidateObjectName(string objectName, string parameterName = "ObjectName") { if (string.IsNullOrWhiteSpace(objectName)) { ThrowTerminatingError(new ErrorRecord( new ArgumentException($"Object name cannot be null or empty", parameterName), "InvalidObjectName", ErrorCategory.InvalidArgument, objectName)); } if (objectName.Length > 1024) { ThrowTerminatingError(new ErrorRecord( new ArgumentException($"Object name cannot be longer than 1024 characters", parameterName), "InvalidObjectName", ErrorCategory.InvalidArgument, objectName)); } } /// /// Writes verbose output with consistent formatting /// /// Message to write /// Format arguments protected void WriteVerboseMessage(string message, params object[] args) { MinIOLogger.WriteVerbose(this, message, args); } /// /// Writes warning output with consistent formatting /// /// Warning message to write /// Format arguments protected void WriteWarningMessage(string message, params object[] args) { MinIOLogger.WriteWarning(this, message, args); } /// /// Writes debug output with consistent formatting /// /// Debug message to write /// Format arguments protected void WriteDebugMessage(string message, params object[] args) { MinIOLogger.WriteDebug(this, message, args); } } }