vaulttool 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
vaulttool/__init__.py ADDED
@@ -0,0 +1,91 @@
1
+ """VaultTool - Secure file encryption for secrets and configuration files.
2
+
3
+ This package provides secure encryption and management of sensitive files using
4
+ AES-256-CBC encryption with HMAC authentication.
5
+ """
6
+
7
+ import logging
8
+ import sys
9
+ from typing import Optional
10
+
11
+ __version__ = "2.0.0"
12
+
13
+ # Default logger for the package
14
+ _logger: Optional[logging.Logger] = None
15
+
16
+
17
+ def get_logger(name: str = "vaulttool") -> logging.Logger:
18
+ """Get or create the VaultTool logger.
19
+
20
+ Args:
21
+ name: Logger name (default: "vaulttool")
22
+
23
+ Returns:
24
+ Configured logger instance
25
+ """
26
+ return logging.getLogger(name)
27
+
28
+
29
+ def setup_logging(
30
+ level: int = logging.INFO,
31
+ format_string: Optional[str] = None,
32
+ include_timestamp: bool = True,
33
+ ) -> logging.Logger:
34
+ """Configure logging for VaultTool.
35
+
36
+ Sets up the root vaulttool logger with appropriate handlers and formatters.
37
+ This should be called once at application startup, typically from the CLI.
38
+
39
+ Args:
40
+ level: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
41
+ format_string: Custom format string. If None, uses default format.
42
+ include_timestamp: Whether to include timestamps in log messages
43
+
44
+ Returns:
45
+ Configured logger instance
46
+
47
+ Example:
48
+ >>> from vaulttool import setup_logging
49
+ >>> import logging
50
+ >>> logger = setup_logging(level=logging.DEBUG)
51
+ >>> logger.info("VaultTool initialized")
52
+ """
53
+ logger = logging.getLogger("vaulttool")
54
+ logger.setLevel(level)
55
+
56
+ # Remove existing handlers to avoid duplicates
57
+ logger.handlers.clear()
58
+
59
+ # Create console handler
60
+ console_handler = logging.StreamHandler(sys.stdout)
61
+ console_handler.setLevel(level)
62
+
63
+ # Create formatter
64
+ if format_string is None:
65
+ if include_timestamp:
66
+ format_string = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
67
+ else:
68
+ # Simpler format without timestamp (better for CLI usage)
69
+ format_string = "%(levelname)s: %(message)s"
70
+
71
+ formatter = logging.Formatter(format_string, datefmt="%Y-%m-%d %H:%M:%S")
72
+ console_handler.setFormatter(formatter)
73
+
74
+ # Add handler to logger
75
+ logger.addHandler(console_handler)
76
+
77
+ # Prevent propagation to root logger to avoid duplicate messages
78
+ logger.propagate = False
79
+
80
+ global _logger
81
+ _logger = logger
82
+
83
+ return logger
84
+
85
+
86
+ # For backward compatibility and convenience
87
+ __all__ = [
88
+ "__version__",
89
+ "get_logger",
90
+ "setup_logging",
91
+ ]
vaulttool/__main__.py ADDED
@@ -0,0 +1,5 @@
1
+
2
+ from .cli import app
3
+
4
+ if __name__ == "__main__":
5
+ app()
vaulttool/cli.py ADDED
@@ -0,0 +1,365 @@
1
+
2
+ """Command-line interface for VaultTool.
3
+
4
+ This module provides the command-line interface for VaultTool using Typer.
5
+ It exposes the main VaultTool functionality through a set of subcommands
6
+ for encrypting, decrypting, and managing vault files.
7
+
8
+ Available commands:
9
+ - encrypt: Encrypt source files to vault files
10
+ - refresh: Decrypt vault files to restore source files
11
+ - remove: Delete all vault files
12
+ - check-ignore: Validate .gitignore entries for source files
13
+ - version: Display VaultTool version information
14
+ - gen-vaulttool: Generate example configuration file
15
+ """
16
+
17
+ import logging
18
+ import sys
19
+ import typer
20
+ from . import setup_logging, get_logger
21
+ from .core import VaultTool
22
+
23
+ # Create app with proper help text
24
+ app = typer.Typer(
25
+ help="""Secure file encryption for secrets and configuration files.
26
+
27
+ Encrypts sensitive files using AES-256-CBC and manages their encrypted counterparts.
28
+
29
+ Key Options:
30
+ encrypt --force Re-encrypt all files (ignores checksums)
31
+ refresh --no-force Only restore missing files
32
+
33
+ Config: .vaulttool.yml (current dir) or ~/.vaulttool/.vaulttool.yml
34
+
35
+ Examples:
36
+ vaulttool gen-vaulttool > .vaulttool.yml # Generate config file
37
+ vaulttool encrypt # Encrypt changed files only
38
+ vaulttool refresh # Restore all source files
39
+ vaulttool remove # Delete all vault files
40
+
41
+ vaulttool --verbose encrypt # Show detailed debug logs
42
+ vaulttool --quiet refresh # Show errors only
43
+ """
44
+ )
45
+
46
+ # Global options for logging control
47
+ verbose_option = typer.Option(False, "--verbose", "-v", help="Enable verbose debug logging")
48
+ quiet_option = typer.Option(False, "--quiet", "-q", help="Show only errors (suppress info/warning)")
49
+
50
+
51
+ def _setup_cli_logging(verbose: bool = False, quiet: bool = False) -> logging.Logger:
52
+ """Configure logging for CLI based on verbosity flags.
53
+
54
+ Args:
55
+ verbose: Enable DEBUG level logging
56
+ quiet: Show only ERROR and CRITICAL messages
57
+
58
+ Returns:
59
+ Configured logger instance
60
+ """
61
+ if verbose and quiet:
62
+ typer.echo("Warning: --verbose and --quiet are mutually exclusive. Using --verbose.", err=True)
63
+ quiet = False
64
+
65
+ if verbose:
66
+ level = logging.DEBUG
67
+ elif quiet:
68
+ level = logging.ERROR
69
+ else:
70
+ level = logging.INFO
71
+
72
+ return setup_logging(level=level, include_timestamp=False)
73
+
74
+
75
+ def _get_version() -> str:
76
+ """Get the version of vaulttool package."""
77
+ logger = get_logger(__name__)
78
+
79
+ try:
80
+ from importlib.metadata import version
81
+ pkg_version = version("vaulttool")
82
+ logger.debug(f"Retrieved version from importlib.metadata: {pkg_version}")
83
+ return pkg_version
84
+ except ImportError:
85
+ # importlib.metadata not available in older Python versions
86
+ logger.debug("importlib.metadata not available, trying fallback")
87
+ except Exception as e:
88
+ # Package not found or other metadata error
89
+ logger.warning(f"Failed to get version from metadata: {e}")
90
+
91
+ # Fallback - try to read from pyproject.toml if available
92
+ try:
93
+ from pathlib import Path
94
+
95
+ # Look for pyproject.toml in parent directories
96
+ current_dir = Path(__file__).parent
97
+ logger.debug(f"Looking for pyproject.toml starting from {current_dir}")
98
+
99
+ for level in range(3): # Check up to 3 levels up
100
+ pyproject_path = current_dir / "pyproject.toml"
101
+ if pyproject_path.exists():
102
+ logger.debug(f"Found pyproject.toml at {pyproject_path}")
103
+ with open(pyproject_path, "r") as f:
104
+ content = f.read()
105
+ # Simple parsing for version
106
+ for line in content.split('\n'):
107
+ if line.strip().startswith('version = "'):
108
+ fallback_version = line.split('"')[1]
109
+ logger.debug(f"Extracted version from pyproject.toml: {fallback_version}")
110
+ return fallback_version
111
+ current_dir = current_dir.parent
112
+ logger.debug(f"Level {level + 1}: No pyproject.toml found, checking parent")
113
+
114
+ except (IOError, OSError) as e:
115
+ logger.warning(f"Failed to read pyproject.toml: {e}")
116
+ except Exception as e:
117
+ logger.warning(f"Unexpected error reading pyproject.toml: {e}")
118
+
119
+ logger.debug("Using development version fallback")
120
+ return "unknown (development version)"
121
+
122
+
123
+ @app.command("version")
124
+ def version_cmd(
125
+ verbose: bool = verbose_option,
126
+ quiet: bool = quiet_option,
127
+ ):
128
+ """Display VaultTool version information.
129
+
130
+ Shows the currently installed version of VaultTool along with
131
+ basic package information.
132
+
133
+ Args:
134
+ verbose: Enable verbose debug logging
135
+ quiet: Show only errors
136
+ """
137
+ _setup_cli_logging(verbose, quiet)
138
+ pkg_version = _get_version()
139
+ typer.echo(f"VaultTool version {pkg_version}")
140
+
141
+
142
+ @app.command("gen-vaulttool")
143
+ def gen_config():
144
+ """Generate an example .vaulttool.yml configuration file.
145
+
146
+ Displays a formatted example configuration file that can be saved as
147
+ .vaulttool.yml to configure VaultTool for your project. The configuration
148
+ includes all available options with comments explaining their purpose.
149
+
150
+ Example:
151
+ vaulttool gen-vaulttool > .vaulttool.yml
152
+ """
153
+ example_config = """---
154
+ # .vaulttool.yml - VaultTool Configuration File
155
+ #
156
+ # This configuration file defines how VaultTool handles file encryption.
157
+ # Save this as .vaulttool.yml in your project root directory.
158
+ #
159
+ # Configuration file search order:
160
+ # 1. ./.vaulttool.yml (current directory)
161
+ # 2. ~/.vaulttool/.vaulttool.yml (user home)
162
+ # 3. /etc/vaulttool/config.yml (system-wide)
163
+
164
+ vaulttool:
165
+ # Directories to search for files to encrypt
166
+ # Defaults to current directory if empty
167
+ include_directories:
168
+ - "."
169
+
170
+ # Directories to exclude from encryption
171
+ exclude_directories:
172
+ - "__pycache__"
173
+ - ".git"
174
+ - ".pytest_cache"
175
+ - ".venv"
176
+ - "dist"
177
+ - "node_modules"
178
+
179
+ # File patterns to include for encryption
180
+ include_patterns:
181
+ - "*.conf" # Config files
182
+ - "*.env" # Environment files
183
+ - "*.ini" # Configuration files
184
+ - "*.json" # JSON config files
185
+ - "*.yaml" # YAML config files
186
+ - "*.yml" # YAML config files
187
+
188
+ # File patterns to exclude from encryption
189
+ exclude_patterns:
190
+ - "*.log" # Log files
191
+ - "*.tmp" # Temporary files
192
+ - "*.vault" # Existing vault files
193
+ - "*example*" # Example files
194
+ - "*sample*" # Sample files
195
+
196
+ # Encryption options
197
+ options:
198
+ # Suffix added to encrypted files (e.g., config.env -> config.env.vault)
199
+ suffix: ".vault"
200
+
201
+ # Full Path to encryption key file
202
+ key_file: "/home/USERNAME/.vaulttool/vault.key"
203
+ """
204
+ typer.echo(example_config.strip())
205
+
206
+
207
+ @app.command()
208
+ def remove(
209
+ verbose: bool = verbose_option,
210
+ quiet: bool = quiet_option,
211
+ ):
212
+ """Remove all vault files matching the configured suffix.
213
+
214
+ This command will permanently delete all .vault files found in the configured
215
+ include directories that match the suffix pattern. This operation cannot be undone.
216
+
217
+ Args:
218
+ verbose: Enable verbose debug logging
219
+ quiet: Show only errors
220
+
221
+ Raises:
222
+ Exit code 1 if any operations failed.
223
+ """
224
+ _setup_cli_logging(verbose, quiet)
225
+
226
+ try:
227
+ vt = VaultTool()
228
+ result = vt.remove_task()
229
+
230
+ # Display summary
231
+ if not quiet:
232
+ typer.echo("\n" + "="*60)
233
+ typer.echo("Remove Summary:")
234
+ typer.echo(f" Total: {result['total']}")
235
+ typer.echo(f" Removed: {result['removed']}")
236
+ typer.echo(f" Failed: {result['failed']}")
237
+ typer.echo("="*60)
238
+
239
+ if result['failed'] > 0:
240
+ sys.exit(1)
241
+
242
+ except Exception as e:
243
+ typer.echo(f"ERROR: {e}", err=True)
244
+ sys.exit(1)
245
+
246
+
247
+ @app.command()
248
+ def encrypt(
249
+ force: bool = typer.Option(False, "--force", help="Re-encrypt and overwrite existing .vault files"),
250
+ verbose: bool = verbose_option,
251
+ quiet: bool = quiet_option,
252
+ ):
253
+ """Encrypt files as configured.
254
+
255
+ Encrypts all source files matching the configured patterns into vault files.
256
+ By default, only encrypts files that have changed (different HMAC) or
257
+ don't have existing vault files.
258
+
259
+ Args:
260
+ force: If True, re-encrypt and overwrite existing .vault files even if
261
+ the source file hasn't changed.
262
+ verbose: Enable verbose debug logging
263
+ quiet: Show only errors
264
+
265
+ Raises:
266
+ Exit code 1 if any operations failed.
267
+ """
268
+ _setup_cli_logging(verbose, quiet)
269
+
270
+ try:
271
+ vt = VaultTool()
272
+ result = vt.encrypt_task(force=force)
273
+
274
+ # Display summary
275
+ if not quiet:
276
+ typer.echo("\n" + "="*60)
277
+ typer.echo("Encrypt Summary:")
278
+ typer.echo(f" Total: {result['total']}")
279
+ typer.echo(f" Created: {result['created']}")
280
+ typer.echo(f" Updated: {result['updated']}")
281
+ typer.echo(f" Skipped: {result['skipped']}")
282
+ typer.echo(f" Failed: {result['failed']}")
283
+ typer.echo("="*60)
284
+
285
+ if result['failed'] > 0:
286
+ sys.exit(1)
287
+
288
+ except Exception as e:
289
+ typer.echo(f"ERROR: {e}", err=True)
290
+ sys.exit(1)
291
+
292
+
293
+ @app.command()
294
+ def refresh(
295
+ force: bool = typer.Option(
296
+ True,
297
+ "--force/--no-force",
298
+ help="Overwrite plaintext files from existing .vault files",
299
+ ),
300
+ verbose: bool = verbose_option,
301
+ quiet: bool = quiet_option,
302
+ ):
303
+ """Restore/refresh plaintext files from .vault files.
304
+
305
+ Decrypts vault files to restore their corresponding source files.
306
+ By default, overwrites existing source files (force=True).
307
+
308
+ Args:
309
+ force: If True (default), overwrite existing plaintext files.
310
+ If False, only restore missing files.
311
+ verbose: Enable verbose debug logging
312
+ quiet: Show only errors
313
+
314
+ Raises:
315
+ Exit code 1 if any operations failed.
316
+ """
317
+ _setup_cli_logging(verbose, quiet)
318
+
319
+ try:
320
+ vt = VaultTool()
321
+ result = vt.refresh_task(force=force)
322
+
323
+ # Display summary
324
+ if not quiet:
325
+ typer.echo("\n" + "="*60)
326
+ typer.echo("Refresh Summary:")
327
+ typer.echo(f" Total: {result['total']}")
328
+ typer.echo(f" Succeeded: {result['succeeded']}")
329
+ typer.echo(f" Failed: {result['failed']}")
330
+ typer.echo(f" Skipped: {result['skipped']}")
331
+ typer.echo("="*60)
332
+
333
+ if result['failed'] > 0:
334
+ sys.exit(1)
335
+
336
+ except Exception as e:
337
+ typer.echo(f"ERROR: {e}", err=True)
338
+ sys.exit(1)
339
+
340
+
341
+ @app.command()
342
+ def check_ignore(
343
+ verbose: bool = verbose_option,
344
+ quiet: bool = quiet_option,
345
+ ):
346
+ """Check that all plaintext files are ignored by Git.
347
+
348
+ Validates that all source files matching the configured patterns are
349
+ properly added to .gitignore to prevent accidental commits of sensitive data.
350
+
351
+ Args:
352
+ verbose: Enable verbose debug logging
353
+ quiet: Show only errors
354
+
355
+ Returns:
356
+ Exit code 0 if operation completed.
357
+ """
358
+ _setup_cli_logging(verbose, quiet)
359
+
360
+ try:
361
+ vt = VaultTool()
362
+ vt.check_ignore_task()
363
+ except Exception as e:
364
+ typer.echo(f"ERROR: {e}", err=True)
365
+ sys.exit(1)