envdrift 4.2.1__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.
- envdrift/__init__.py +30 -0
- envdrift/_version.py +34 -0
- envdrift/api.py +192 -0
- envdrift/cli.py +42 -0
- envdrift/cli_commands/__init__.py +1 -0
- envdrift/cli_commands/diff.py +91 -0
- envdrift/cli_commands/encryption.py +630 -0
- envdrift/cli_commands/encryption_helpers.py +93 -0
- envdrift/cli_commands/hook.py +75 -0
- envdrift/cli_commands/init_cmd.py +117 -0
- envdrift/cli_commands/partial.py +222 -0
- envdrift/cli_commands/sync.py +1140 -0
- envdrift/cli_commands/validate.py +109 -0
- envdrift/cli_commands/vault.py +376 -0
- envdrift/cli_commands/version.py +15 -0
- envdrift/config.py +489 -0
- envdrift/constants.json +18 -0
- envdrift/core/__init__.py +30 -0
- envdrift/core/diff.py +233 -0
- envdrift/core/encryption.py +400 -0
- envdrift/core/parser.py +260 -0
- envdrift/core/partial_encryption.py +239 -0
- envdrift/core/schema.py +253 -0
- envdrift/core/validator.py +312 -0
- envdrift/encryption/__init__.py +117 -0
- envdrift/encryption/base.py +217 -0
- envdrift/encryption/dotenvx.py +236 -0
- envdrift/encryption/sops.py +458 -0
- envdrift/env_files.py +60 -0
- envdrift/integrations/__init__.py +21 -0
- envdrift/integrations/dotenvx.py +689 -0
- envdrift/integrations/precommit.py +266 -0
- envdrift/integrations/sops.py +85 -0
- envdrift/output/__init__.py +21 -0
- envdrift/output/rich.py +424 -0
- envdrift/py.typed +0 -0
- envdrift/sync/__init__.py +26 -0
- envdrift/sync/config.py +218 -0
- envdrift/sync/engine.py +383 -0
- envdrift/sync/operations.py +138 -0
- envdrift/sync/result.py +99 -0
- envdrift/vault/__init__.py +107 -0
- envdrift/vault/aws.py +282 -0
- envdrift/vault/azure.py +170 -0
- envdrift/vault/base.py +150 -0
- envdrift/vault/gcp.py +210 -0
- envdrift/vault/hashicorp.py +238 -0
- envdrift-4.2.1.dist-info/METADATA +160 -0
- envdrift-4.2.1.dist-info/RECORD +52 -0
- envdrift-4.2.1.dist-info/WHEEL +4 -0
- envdrift-4.2.1.dist-info/entry_points.txt +2 -0
- envdrift-4.2.1.dist-info/licenses/LICENSE +21 -0
envdrift/config.py
ADDED
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
"""Configuration loader for envdrift.toml."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import tomllib
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class SyncMappingConfig:
|
|
13
|
+
"""Sync mapping configuration for vault key synchronization."""
|
|
14
|
+
|
|
15
|
+
secret_name: str
|
|
16
|
+
folder_path: str
|
|
17
|
+
vault_name: str | None = None
|
|
18
|
+
environment: str | None = None # None = derive from profile or default to "production"
|
|
19
|
+
profile: str | None = None # Profile name for filtering (e.g., "local", "prod")
|
|
20
|
+
activate_to: str | None = None # Path to copy decrypted file when profile is activated
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class SyncConfig:
|
|
25
|
+
"""Sync-specific configuration."""
|
|
26
|
+
|
|
27
|
+
mappings: list[SyncMappingConfig] = field(default_factory=list)
|
|
28
|
+
default_vault_name: str | None = None
|
|
29
|
+
env_keys_filename: str = ".env.keys"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class VaultConfig:
|
|
34
|
+
"""Vault-specific configuration."""
|
|
35
|
+
|
|
36
|
+
provider: str = "azure" # azure, aws, hashicorp, gcp
|
|
37
|
+
azure_vault_url: str | None = None
|
|
38
|
+
aws_region: str = "us-east-1"
|
|
39
|
+
hashicorp_url: str | None = None
|
|
40
|
+
gcp_project_id: str | None = None
|
|
41
|
+
mappings: dict[str, str] = field(default_factory=dict)
|
|
42
|
+
sync: SyncConfig = field(default_factory=SyncConfig)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class EncryptionConfig:
|
|
47
|
+
"""Encryption backend settings."""
|
|
48
|
+
|
|
49
|
+
# Encryption backend: dotenvx (default) or sops
|
|
50
|
+
backend: str = "dotenvx"
|
|
51
|
+
|
|
52
|
+
# dotenvx-specific settings
|
|
53
|
+
dotenvx_auto_install: bool = False
|
|
54
|
+
|
|
55
|
+
# SOPS-specific settings
|
|
56
|
+
sops_auto_install: bool = False
|
|
57
|
+
sops_config_file: str | None = None # Path to .sops.yaml
|
|
58
|
+
sops_age_key_file: str | None = None # Path to age key file
|
|
59
|
+
sops_age_recipients: str | None = None # Age public key(s) for encryption
|
|
60
|
+
sops_kms_arn: str | None = None # AWS KMS key ARN
|
|
61
|
+
sops_gcp_kms: str | None = None # GCP KMS resource ID
|
|
62
|
+
sops_azure_kv: str | None = None # Azure Key Vault key URL
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class ValidationConfig:
|
|
67
|
+
"""Validation settings."""
|
|
68
|
+
|
|
69
|
+
check_encryption: bool = True
|
|
70
|
+
strict_extra: bool = True
|
|
71
|
+
secret_patterns: list[str] = field(default_factory=list)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass
|
|
75
|
+
class PrecommitConfig:
|
|
76
|
+
"""Pre-commit hook settings."""
|
|
77
|
+
|
|
78
|
+
files: list[str] = field(default_factory=list)
|
|
79
|
+
schemas: dict[str, str] = field(default_factory=dict)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dataclass
|
|
83
|
+
class PartialEncryptionEnvironmentConfig:
|
|
84
|
+
"""Partial encryption configuration for a single environment."""
|
|
85
|
+
|
|
86
|
+
name: str
|
|
87
|
+
clear_file: str
|
|
88
|
+
secret_file: str
|
|
89
|
+
combined_file: str
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dataclass
|
|
93
|
+
class PartialEncryptionConfig:
|
|
94
|
+
"""Partial encryption settings."""
|
|
95
|
+
|
|
96
|
+
enabled: bool = False
|
|
97
|
+
environments: list[PartialEncryptionEnvironmentConfig] = field(default_factory=list)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@dataclass
|
|
101
|
+
class EnvdriftConfig:
|
|
102
|
+
"""Complete envdrift configuration."""
|
|
103
|
+
|
|
104
|
+
# Core settings
|
|
105
|
+
schema: str | None = None
|
|
106
|
+
environments: list[str] = field(
|
|
107
|
+
default_factory=lambda: ["development", "staging", "production"]
|
|
108
|
+
)
|
|
109
|
+
env_file_pattern: str = ".env.{environment}"
|
|
110
|
+
|
|
111
|
+
# Sub-configs
|
|
112
|
+
validation: ValidationConfig = field(default_factory=ValidationConfig)
|
|
113
|
+
vault: VaultConfig = field(default_factory=VaultConfig)
|
|
114
|
+
encryption: EncryptionConfig = field(default_factory=EncryptionConfig)
|
|
115
|
+
precommit: PrecommitConfig = field(default_factory=PrecommitConfig)
|
|
116
|
+
partial_encryption: PartialEncryptionConfig = field(default_factory=PartialEncryptionConfig)
|
|
117
|
+
|
|
118
|
+
# Raw config for access to custom fields
|
|
119
|
+
raw: dict[str, Any] = field(default_factory=dict)
|
|
120
|
+
|
|
121
|
+
@classmethod
|
|
122
|
+
def from_dict(cls, data: dict[str, Any]) -> EnvdriftConfig:
|
|
123
|
+
"""
|
|
124
|
+
Builds an EnvdriftConfig from a configuration dictionary.
|
|
125
|
+
|
|
126
|
+
Parses top-level sections (expected keys: "envdrift", "validation", "vault", "encryption", "precommit"), applies sensible defaults for missing fields, and returns a populated EnvdriftConfig with the original dictionary stored in `raw`.
|
|
127
|
+
|
|
128
|
+
Parameters:
|
|
129
|
+
data (dict[str, Any]): Parsed TOML/pyproject data containing configuration sections.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
EnvdriftConfig: Configuration object populated from `data`.
|
|
133
|
+
"""
|
|
134
|
+
envdrift_section = data.get("envdrift", {})
|
|
135
|
+
validation_section = data.get("validation", {})
|
|
136
|
+
vault_section = data.get("vault", {})
|
|
137
|
+
encryption_section = data.get("encryption", {})
|
|
138
|
+
precommit_section = data.get("precommit", {})
|
|
139
|
+
|
|
140
|
+
# Build validation config
|
|
141
|
+
validation = ValidationConfig(
|
|
142
|
+
check_encryption=validation_section.get("check_encryption", True),
|
|
143
|
+
strict_extra=validation_section.get("strict_extra", True),
|
|
144
|
+
secret_patterns=validation_section.get("secret_patterns", []),
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# Build sync config from vault.sync section
|
|
148
|
+
sync_section = vault_section.get("sync", {})
|
|
149
|
+
sync_mappings = [
|
|
150
|
+
SyncMappingConfig(
|
|
151
|
+
secret_name=m["secret_name"],
|
|
152
|
+
folder_path=m["folder_path"],
|
|
153
|
+
vault_name=m.get("vault_name"),
|
|
154
|
+
environment=m.get("environment"), # None = derive from profile
|
|
155
|
+
profile=m.get("profile"),
|
|
156
|
+
activate_to=m.get("activate_to"),
|
|
157
|
+
)
|
|
158
|
+
for m in sync_section.get("mappings", [])
|
|
159
|
+
]
|
|
160
|
+
sync_config = SyncConfig(
|
|
161
|
+
mappings=sync_mappings,
|
|
162
|
+
default_vault_name=sync_section.get("default_vault_name"),
|
|
163
|
+
env_keys_filename=sync_section.get("env_keys_filename", ".env.keys"),
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Build vault config
|
|
167
|
+
vault = VaultConfig(
|
|
168
|
+
provider=vault_section.get("provider", "azure"),
|
|
169
|
+
azure_vault_url=vault_section.get("azure", {}).get("vault_url"),
|
|
170
|
+
aws_region=vault_section.get("aws", {}).get("region", "us-east-1"),
|
|
171
|
+
hashicorp_url=vault_section.get("hashicorp", {}).get("url"),
|
|
172
|
+
gcp_project_id=vault_section.get("gcp", {}).get("project_id"),
|
|
173
|
+
mappings=vault_section.get("mappings", {}),
|
|
174
|
+
sync=sync_config,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
# Build precommit config
|
|
178
|
+
precommit = PrecommitConfig(
|
|
179
|
+
files=precommit_section.get("files", []),
|
|
180
|
+
schemas=precommit_section.get("schemas", {}),
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
# Build partial_encryption config
|
|
184
|
+
partial_encryption_section = data.get("partial_encryption", {})
|
|
185
|
+
partial_encryption_envs = [
|
|
186
|
+
PartialEncryptionEnvironmentConfig(
|
|
187
|
+
name=env["name"],
|
|
188
|
+
clear_file=env["clear_file"],
|
|
189
|
+
secret_file=env["secret_file"],
|
|
190
|
+
combined_file=env["combined_file"],
|
|
191
|
+
)
|
|
192
|
+
for env in partial_encryption_section.get("environments", [])
|
|
193
|
+
]
|
|
194
|
+
partial_encryption = PartialEncryptionConfig(
|
|
195
|
+
enabled=partial_encryption_section.get("enabled", False),
|
|
196
|
+
environments=partial_encryption_envs,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
# Build encryption config
|
|
200
|
+
sops_section = encryption_section.get("sops", {})
|
|
201
|
+
dotenvx_section = encryption_section.get("dotenvx", {})
|
|
202
|
+
encryption = EncryptionConfig(
|
|
203
|
+
backend=encryption_section.get("backend", "dotenvx"),
|
|
204
|
+
dotenvx_auto_install=dotenvx_section.get("auto_install", False),
|
|
205
|
+
sops_auto_install=sops_section.get("auto_install", False),
|
|
206
|
+
sops_config_file=sops_section.get("config_file"),
|
|
207
|
+
sops_age_key_file=sops_section.get("age_key_file"),
|
|
208
|
+
sops_age_recipients=sops_section.get("age_recipients"),
|
|
209
|
+
sops_kms_arn=sops_section.get("kms_arn"),
|
|
210
|
+
sops_gcp_kms=sops_section.get("gcp_kms"),
|
|
211
|
+
sops_azure_kv=sops_section.get("azure_kv"),
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
return cls(
|
|
215
|
+
schema=envdrift_section.get("schema"),
|
|
216
|
+
environments=envdrift_section.get(
|
|
217
|
+
"environments", ["development", "staging", "production"]
|
|
218
|
+
),
|
|
219
|
+
env_file_pattern=envdrift_section.get("env_file_pattern", ".env.{environment}"),
|
|
220
|
+
validation=validation,
|
|
221
|
+
vault=vault,
|
|
222
|
+
encryption=encryption,
|
|
223
|
+
precommit=precommit,
|
|
224
|
+
partial_encryption=partial_encryption,
|
|
225
|
+
raw=data,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
class ConfigNotFoundError(Exception):
|
|
230
|
+
"""Configuration file not found."""
|
|
231
|
+
|
|
232
|
+
pass
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def find_config(start_dir: Path | None = None, filename: str = "envdrift.toml") -> Path | None:
|
|
236
|
+
"""
|
|
237
|
+
Locate an envdrift configuration file by searching the given directory and its parents.
|
|
238
|
+
|
|
239
|
+
Searches each directory from start_dir (defaults to the current working directory) up to the filesystem root for a file named by `filename`. If no such file is found, also checks each directory's pyproject.toml for a top-level [tool.envdrift] section and returns that pyproject path when present.
|
|
240
|
+
|
|
241
|
+
Parameters:
|
|
242
|
+
start_dir (Path | None): Directory to start searching from; defaults to the current working directory.
|
|
243
|
+
filename (str): Configuration filename to look for (default "envdrift.toml").
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
Path | None: Path to the first matching configuration file or pyproject.toml containing [tool.envdrift], or `None` if none is found.
|
|
247
|
+
"""
|
|
248
|
+
if start_dir is None:
|
|
249
|
+
start_dir = Path.cwd()
|
|
250
|
+
|
|
251
|
+
current = start_dir.resolve()
|
|
252
|
+
|
|
253
|
+
while current != current.parent:
|
|
254
|
+
config_path = current / filename
|
|
255
|
+
if config_path.exists():
|
|
256
|
+
return config_path
|
|
257
|
+
|
|
258
|
+
# Also check pyproject.toml for [tool.envdrift] section
|
|
259
|
+
pyproject = current / "pyproject.toml"
|
|
260
|
+
if pyproject.exists():
|
|
261
|
+
try:
|
|
262
|
+
with open(pyproject, "rb") as f:
|
|
263
|
+
data = tomllib.load(f)
|
|
264
|
+
if "tool" in data and "envdrift" in data["tool"]:
|
|
265
|
+
return pyproject
|
|
266
|
+
except (OSError, tomllib.TOMLDecodeError):
|
|
267
|
+
# Skip malformed or unreadable pyproject.toml files
|
|
268
|
+
pass
|
|
269
|
+
|
|
270
|
+
current = current.parent
|
|
271
|
+
|
|
272
|
+
return None
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def load_config(path: Path | str | None = None) -> EnvdriftConfig:
|
|
276
|
+
"""Load configuration from envdrift.toml or pyproject.toml.
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
path: Path to config file (auto-detected if None)
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
EnvdriftConfig instance
|
|
283
|
+
|
|
284
|
+
Raises:
|
|
285
|
+
ConfigNotFoundError: If config file not found and path was specified
|
|
286
|
+
"""
|
|
287
|
+
if path is not None:
|
|
288
|
+
path = Path(path)
|
|
289
|
+
if not path.exists():
|
|
290
|
+
raise ConfigNotFoundError(f"Configuration file not found: {path}")
|
|
291
|
+
else:
|
|
292
|
+
path = find_config()
|
|
293
|
+
if path is None:
|
|
294
|
+
# Return default config if no file found
|
|
295
|
+
return EnvdriftConfig()
|
|
296
|
+
|
|
297
|
+
with open(path, "rb") as f:
|
|
298
|
+
data = tomllib.load(f)
|
|
299
|
+
|
|
300
|
+
# Check if this is pyproject.toml with [tool.envdrift]
|
|
301
|
+
if path.name == "pyproject.toml":
|
|
302
|
+
tool_config = data.get("tool", {}).get("envdrift", {})
|
|
303
|
+
if tool_config:
|
|
304
|
+
# Restructure to expected format (copy to avoid mutating original)
|
|
305
|
+
envdrift_section = dict(tool_config)
|
|
306
|
+
data = {"envdrift": envdrift_section}
|
|
307
|
+
if "validation" in envdrift_section:
|
|
308
|
+
data["validation"] = envdrift_section.get("validation")
|
|
309
|
+
del envdrift_section["validation"]
|
|
310
|
+
if "vault" in envdrift_section:
|
|
311
|
+
data["vault"] = envdrift_section.get("vault")
|
|
312
|
+
del envdrift_section["vault"]
|
|
313
|
+
if "encryption" in envdrift_section:
|
|
314
|
+
data["encryption"] = envdrift_section.get("encryption")
|
|
315
|
+
del envdrift_section["encryption"]
|
|
316
|
+
if "precommit" in envdrift_section:
|
|
317
|
+
data["precommit"] = envdrift_section.get("precommit")
|
|
318
|
+
del envdrift_section["precommit"]
|
|
319
|
+
if "partial_encryption" in envdrift_section:
|
|
320
|
+
data["partial_encryption"] = envdrift_section.get("partial_encryption")
|
|
321
|
+
del envdrift_section["partial_encryption"]
|
|
322
|
+
|
|
323
|
+
return EnvdriftConfig.from_dict(data)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def get_env_file_path(config: EnvdriftConfig, environment: str) -> Path:
|
|
327
|
+
"""
|
|
328
|
+
Build the Path to the .env file for the given environment using the configuration's env_file_pattern.
|
|
329
|
+
|
|
330
|
+
Parameters:
|
|
331
|
+
config (EnvdriftConfig): Configuration whose env_file_pattern will be formatted.
|
|
332
|
+
environment (str): Environment name inserted into the pattern (replaces `{environment}`).
|
|
333
|
+
|
|
334
|
+
Returns:
|
|
335
|
+
Path: Path to the computed .env file.
|
|
336
|
+
"""
|
|
337
|
+
filename = config.env_file_pattern.format(environment=environment)
|
|
338
|
+
return Path(filename)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def get_schema_for_environment(config: EnvdriftConfig, environment: str) -> str | None:
|
|
342
|
+
"""
|
|
343
|
+
Resolve the schema path to use for a given environment.
|
|
344
|
+
|
|
345
|
+
Prefers an environment-specific precommit schema when configured; otherwise returns the default schema from the config.
|
|
346
|
+
|
|
347
|
+
Returns:
|
|
348
|
+
The schema path for `environment`, or `None` if no schema is configured.
|
|
349
|
+
"""
|
|
350
|
+
# Check for environment-specific schema
|
|
351
|
+
env_schema = config.precommit.schemas.get(environment)
|
|
352
|
+
if env_schema:
|
|
353
|
+
return env_schema
|
|
354
|
+
|
|
355
|
+
# Fall back to default schema
|
|
356
|
+
return config.schema
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
# Example config file content
|
|
360
|
+
EXAMPLE_CONFIG = """# envdrift.toml - Project configuration
|
|
361
|
+
|
|
362
|
+
[envdrift]
|
|
363
|
+
# Default schema for validation
|
|
364
|
+
schema = "config.settings:ProductionSettings"
|
|
365
|
+
|
|
366
|
+
# Environments to manage
|
|
367
|
+
environments = ["development", "staging", "production"]
|
|
368
|
+
|
|
369
|
+
# Path pattern for env files
|
|
370
|
+
env_file_pattern = ".env.{environment}"
|
|
371
|
+
|
|
372
|
+
[validation]
|
|
373
|
+
# Check encryption by default
|
|
374
|
+
check_encryption = true
|
|
375
|
+
|
|
376
|
+
# Treat extra vars as errors (matches Pydantic extra="forbid")
|
|
377
|
+
strict_extra = true
|
|
378
|
+
|
|
379
|
+
# Additional secret detection patterns
|
|
380
|
+
secret_patterns = [
|
|
381
|
+
"^STRIPE_",
|
|
382
|
+
"^TWILIO_",
|
|
383
|
+
]
|
|
384
|
+
|
|
385
|
+
[encryption]
|
|
386
|
+
# Encryption backend: dotenvx (default) or sops
|
|
387
|
+
backend = "dotenvx"
|
|
388
|
+
|
|
389
|
+
# dotenvx-specific settings
|
|
390
|
+
[encryption.dotenvx]
|
|
391
|
+
auto_install = false
|
|
392
|
+
|
|
393
|
+
# SOPS-specific settings (only used when backend = "sops")
|
|
394
|
+
[encryption.sops]
|
|
395
|
+
auto_install = false
|
|
396
|
+
# config_file = ".sops.yaml" # Path to SOPS configuration
|
|
397
|
+
# age_key_file = "key.txt" # Path to age private key file
|
|
398
|
+
# age_recipients = "age1..." # Age public key(s) for encryption
|
|
399
|
+
# kms_arn = "arn:aws:kms:..." # AWS KMS key ARN
|
|
400
|
+
# gcp_kms = "projects/..." # GCP KMS resource ID
|
|
401
|
+
# azure_kv = "https://..." # Azure Key Vault key URL
|
|
402
|
+
|
|
403
|
+
[vault]
|
|
404
|
+
# Vault provider: azure, aws, hashicorp, gcp
|
|
405
|
+
provider = "azure"
|
|
406
|
+
|
|
407
|
+
[vault.azure]
|
|
408
|
+
vault_url = "https://my-vault.vault.azure.net/"
|
|
409
|
+
|
|
410
|
+
[vault.aws]
|
|
411
|
+
region = "us-east-1"
|
|
412
|
+
|
|
413
|
+
[vault.hashicorp]
|
|
414
|
+
url = "https://vault.example.com:8200"
|
|
415
|
+
|
|
416
|
+
[vault.gcp]
|
|
417
|
+
project_id = "my-gcp-project"
|
|
418
|
+
# token from VAULT_TOKEN env var
|
|
419
|
+
|
|
420
|
+
# Sync configuration for `envdrift sync` command
|
|
421
|
+
[vault.sync]
|
|
422
|
+
default_vault_name = "my-keyvault"
|
|
423
|
+
env_keys_filename = ".env.keys"
|
|
424
|
+
|
|
425
|
+
# Map vault secrets to local service directories
|
|
426
|
+
[[vault.sync.mappings]]
|
|
427
|
+
secret_name = "myapp-dotenvx-key"
|
|
428
|
+
folder_path = "."
|
|
429
|
+
environment = "production"
|
|
430
|
+
|
|
431
|
+
[[vault.sync.mappings]]
|
|
432
|
+
secret_name = "service2-dotenvx-key"
|
|
433
|
+
folder_path = "services/service2"
|
|
434
|
+
vault_name = "other-vault" # Optional: override default vault
|
|
435
|
+
environment = "staging"
|
|
436
|
+
|
|
437
|
+
# Profile mappings - use with `envdrift pull --profile local`
|
|
438
|
+
[[vault.sync.mappings]]
|
|
439
|
+
secret_name = "local-key"
|
|
440
|
+
folder_path = "."
|
|
441
|
+
profile = "local" # Tag for --profile filtering
|
|
442
|
+
activate_to = ".env" # Copy decrypted .env.local to .env
|
|
443
|
+
|
|
444
|
+
[precommit]
|
|
445
|
+
# Files to validate on commit
|
|
446
|
+
files = [
|
|
447
|
+
".env.production",
|
|
448
|
+
".env.staging",
|
|
449
|
+
]
|
|
450
|
+
|
|
451
|
+
# Schema per environment (optional override)
|
|
452
|
+
[precommit.schemas]
|
|
453
|
+
production = "config.settings:ProductionSettings"
|
|
454
|
+
staging = "config.settings:StagingSettings"
|
|
455
|
+
|
|
456
|
+
# Partial encryption configuration (optional)
|
|
457
|
+
[partial_encryption]
|
|
458
|
+
enabled = false
|
|
459
|
+
|
|
460
|
+
# Configure environments for partial encryption
|
|
461
|
+
# [[partial_encryption.environments]]
|
|
462
|
+
# name = "production"
|
|
463
|
+
# clear_file = ".env.production.clear"
|
|
464
|
+
# secret_file = ".env.production.secret"
|
|
465
|
+
# combined_file = ".env.production"
|
|
466
|
+
"""
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def create_example_config(path: Path | None = None) -> Path:
|
|
470
|
+
"""
|
|
471
|
+
Create an example envdrift.toml configuration file at the given path.
|
|
472
|
+
|
|
473
|
+
Parameters:
|
|
474
|
+
path (Path | None): Destination path for the example config. If None, defaults to "./envdrift.toml".
|
|
475
|
+
|
|
476
|
+
Returns:
|
|
477
|
+
Path: The path to the created configuration file.
|
|
478
|
+
|
|
479
|
+
Raises:
|
|
480
|
+
FileExistsError: If a file already exists at the target path.
|
|
481
|
+
"""
|
|
482
|
+
if path is None:
|
|
483
|
+
path = Path("envdrift.toml")
|
|
484
|
+
|
|
485
|
+
if path.exists():
|
|
486
|
+
raise FileExistsError(f"Configuration file already exists: {path}")
|
|
487
|
+
|
|
488
|
+
path.write_text(EXAMPLE_CONFIG)
|
|
489
|
+
return path
|
envdrift/constants.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"dotenvx_version": "1.51.4",
|
|
3
|
+
"download_urls": {
|
|
4
|
+
"darwin_amd64": "https://github.com/dotenvx/dotenvx/releases/download/v{version}/dotenvx-{version}-darwin-amd64.tar.gz",
|
|
5
|
+
"darwin_arm64": "https://github.com/dotenvx/dotenvx/releases/download/v{version}/dotenvx-{version}-darwin-arm64.tar.gz",
|
|
6
|
+
"linux_amd64": "https://github.com/dotenvx/dotenvx/releases/download/v{version}/dotenvx-{version}-linux-amd64.tar.gz",
|
|
7
|
+
"linux_arm64": "https://github.com/dotenvx/dotenvx/releases/download/v{version}/dotenvx-{version}-linux-arm64.tar.gz",
|
|
8
|
+
"windows_amd64": "https://github.com/dotenvx/dotenvx/releases/download/v{version}/dotenvx-{version}-windows-amd64.zip"
|
|
9
|
+
},
|
|
10
|
+
"sops_version": "3.11.0",
|
|
11
|
+
"sops_download_urls": {
|
|
12
|
+
"darwin_amd64": "https://github.com/getsops/sops/releases/download/v{version}/sops-v{version}.darwin.amd64",
|
|
13
|
+
"darwin_arm64": "https://github.com/getsops/sops/releases/download/v{version}/sops-v{version}.darwin.arm64",
|
|
14
|
+
"linux_amd64": "https://github.com/getsops/sops/releases/download/v{version}/sops-v{version}.linux.amd64",
|
|
15
|
+
"linux_arm64": "https://github.com/getsops/sops/releases/download/v{version}/sops-v{version}.linux.arm64",
|
|
16
|
+
"windows_amd64": "https://github.com/getsops/sops/releases/download/v{version}/sops-v{version}.amd64.exe"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Core modules for envdrift."""
|
|
2
|
+
|
|
3
|
+
from envdrift.core.diff import DiffEngine, DiffResult, DiffType, VarDiff
|
|
4
|
+
from envdrift.core.encryption import EncryptionDetector, EncryptionReport
|
|
5
|
+
from envdrift.core.parser import EncryptionStatus, EnvFile, EnvParser, EnvVar
|
|
6
|
+
from envdrift.core.schema import FieldMetadata, SchemaLoader, SchemaMetadata
|
|
7
|
+
from envdrift.core.validator import ValidationResult, Validator
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
# Parser
|
|
11
|
+
"EnvFile",
|
|
12
|
+
"EnvParser",
|
|
13
|
+
"EnvVar",
|
|
14
|
+
"EncryptionStatus",
|
|
15
|
+
# Schema
|
|
16
|
+
"FieldMetadata",
|
|
17
|
+
"SchemaLoader",
|
|
18
|
+
"SchemaMetadata",
|
|
19
|
+
# Validator
|
|
20
|
+
"ValidationResult",
|
|
21
|
+
"Validator",
|
|
22
|
+
# Diff
|
|
23
|
+
"DiffEngine",
|
|
24
|
+
"DiffResult",
|
|
25
|
+
"DiffType",
|
|
26
|
+
"VarDiff",
|
|
27
|
+
# Encryption
|
|
28
|
+
"EncryptionDetector",
|
|
29
|
+
"EncryptionReport",
|
|
30
|
+
]
|