opensecureconf-client 2.3.1__py3-none-any.whl → 3.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.
- {opensecureconf_client-2.3.1.dist-info → opensecureconf_client-3.0.0.dist-info}/METADATA +386 -207
- opensecureconf_client-3.0.0.dist-info/RECORD +6 -0
- opensecureconf_client.py +198 -101
- opensecureconf_client-2.3.1.dist-info/RECORD +0 -6
- {opensecureconf_client-2.3.1.dist-info → opensecureconf_client-3.0.0.dist-info}/WHEEL +0 -0
- {opensecureconf_client-2.3.1.dist-info → opensecureconf_client-3.0.0.dist-info}/licenses/LICENSE +0 -0
- {opensecureconf_client-2.3.1.dist-info → opensecureconf_client-3.0.0.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: opensecureconf-client
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.0.0
|
|
4
4
|
Summary: Python client library for OpenSecureConf encrypted configuration management API with clustering support
|
|
5
5
|
Author-email: Alessandro Pioli <alessandro.pioli+apioli-pypi@gmail.com>
|
|
6
6
|
Maintainer-email: Alessandro Pioli <alessandro.pioli+apioli-pypi@gmail.com>
|
|
@@ -46,7 +46,7 @@ Dynamic: license-file
|
|
|
46
46
|
[](https://pypi.org/project/opensecureconf-client/)
|
|
47
47
|
[](https://opensource.org/licenses/MIT)
|
|
48
48
|
|
|
49
|
-
A powerful Python client library for interacting with the [OpenSecureConf API](https://github.com/lordraw77/OpenSecureConf), providing encrypted configuration management with clustering support, automatic retry logic, and comprehensive monitoring capabilities.
|
|
49
|
+
A powerful Python client library for interacting with the [OpenSecureConf API](https://github.com/lordraw77/OpenSecureConf), providing encrypted configuration management with clustering support, automatic retry logic, multi-environment support, and comprehensive monitoring capabilities.
|
|
50
50
|
|
|
51
51
|
## 📋 Table of Contents
|
|
52
52
|
|
|
@@ -54,6 +54,7 @@ A powerful Python client library for interacting with the [OpenSecureConf API](h
|
|
|
54
54
|
- [Installation](#installation)
|
|
55
55
|
- [Quick Start](#quick-start)
|
|
56
56
|
- [Core Features](#core-features)
|
|
57
|
+
- [Multi-Environment Support](#multi-environment-support)
|
|
57
58
|
- [Basic CRUD Operations](#basic-crud-operations)
|
|
58
59
|
- [Cluster Awareness](#cluster-awareness)
|
|
59
60
|
- [Batch Operations](#batch-operations)
|
|
@@ -73,6 +74,7 @@ A powerful Python client library for interacting with the [OpenSecureConf API](h
|
|
|
73
74
|
|
|
74
75
|
### Core Capabilities
|
|
75
76
|
- 🔐 **Encrypted Configuration Management**: Securely store and retrieve encrypted configurations using PBKDF2 + Fernet
|
|
77
|
+
- 🌍 **Multi-Environment Support**: Same configuration key across different environments (production, staging, development)
|
|
76
78
|
- 🚀 **Simple & Intuitive API**: Clean interface for CRUD operations
|
|
77
79
|
- 🛡️ **Type-Safe**: Fully typed with comprehensive error handling
|
|
78
80
|
- 📦 **Lightweight**: Minimal dependencies (only `requests` and `urllib3`)
|
|
@@ -123,36 +125,84 @@ client = OpenSecureConfClient(
|
|
|
123
125
|
api_key="cluster-secret-key-123" # Optional: if API key authentication is enabled
|
|
124
126
|
)
|
|
125
127
|
|
|
126
|
-
# Create a configuration
|
|
128
|
+
# Create a configuration (environment is REQUIRED)
|
|
127
129
|
config = client.create(
|
|
128
130
|
key="database",
|
|
129
131
|
value={"host": "localhost", "port": 5432, "username": "admin", "password": "secret"},
|
|
130
|
-
|
|
132
|
+
environment="production", # REQUIRED
|
|
133
|
+
category="config"
|
|
131
134
|
)
|
|
132
|
-
print(f"Created: {config['key']}")
|
|
135
|
+
print(f"Created: {config['key']} in {config['environment']}")
|
|
133
136
|
|
|
134
|
-
# Read a configuration
|
|
135
|
-
db_config = client.read("database")
|
|
137
|
+
# Read a configuration (environment is REQUIRED)
|
|
138
|
+
db_config = client.read("database", "production")
|
|
136
139
|
print(f"Host: {db_config['value']['host']}")
|
|
137
140
|
|
|
138
|
-
# Update a configuration
|
|
141
|
+
# Update a configuration (environment is REQUIRED)
|
|
139
142
|
updated = client.update(
|
|
140
143
|
key="database",
|
|
144
|
+
environment="production", # REQUIRED
|
|
141
145
|
value={"host": "db.example.com", "port": 5432, "username": "admin", "password": "secret"}
|
|
142
146
|
)
|
|
143
147
|
|
|
144
|
-
# List all configurations
|
|
145
|
-
configs = client.list_all(
|
|
148
|
+
# List all production configurations
|
|
149
|
+
configs = client.list_all(environment="production")
|
|
146
150
|
for cfg in configs:
|
|
147
|
-
print(f"- {cfg['key']}: {cfg['
|
|
151
|
+
print(f"- {cfg['key']}: {cfg['environment']}")
|
|
148
152
|
|
|
149
|
-
# Delete a configuration
|
|
150
|
-
client.delete("database")
|
|
153
|
+
# Delete a configuration (environment is REQUIRED)
|
|
154
|
+
client.delete("database", "production")
|
|
151
155
|
|
|
152
156
|
# Close the client
|
|
153
157
|
client.close()
|
|
154
158
|
```
|
|
155
159
|
|
|
160
|
+
### Multi-Environment Configuration
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
from opensecureconf_client import OpenSecureConfClient
|
|
164
|
+
|
|
165
|
+
with OpenSecureConfClient(
|
|
166
|
+
base_url="http://localhost:9000",
|
|
167
|
+
user_key="my-secure-key-min-8-chars"
|
|
168
|
+
) as client:
|
|
169
|
+
# Create same key in different environments
|
|
170
|
+
prod_db = client.create(
|
|
171
|
+
"database",
|
|
172
|
+
{"host": "db.prod.com", "port": 5432},
|
|
173
|
+
"production",
|
|
174
|
+
"config"
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
staging_db = client.create(
|
|
178
|
+
"database",
|
|
179
|
+
{"host": "db.staging.com", "port": 5432},
|
|
180
|
+
"staging",
|
|
181
|
+
"config"
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
dev_db = client.create(
|
|
185
|
+
"database",
|
|
186
|
+
{"host": "localhost", "port": 5432},
|
|
187
|
+
"development",
|
|
188
|
+
"config"
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
# Read from specific environment
|
|
192
|
+
prod_config = client.read("database", "production")
|
|
193
|
+
staging_config = client.read("database", "staging")
|
|
194
|
+
|
|
195
|
+
print(f"Production: {prod_config['value']['host']}")
|
|
196
|
+
print(f"Staging: {staging_config['value']['host']}")
|
|
197
|
+
|
|
198
|
+
# Update only staging
|
|
199
|
+
client.update("database", "staging", {"host": "db-new.staging.com", "port": 5432})
|
|
200
|
+
|
|
201
|
+
# Delete only development
|
|
202
|
+
client.delete("database", "development")
|
|
203
|
+
# Production and staging remain untouched
|
|
204
|
+
```
|
|
205
|
+
|
|
156
206
|
### Using Context Manager (Recommended)
|
|
157
207
|
|
|
158
208
|
```python
|
|
@@ -163,7 +213,7 @@ with OpenSecureConfClient(
|
|
|
163
213
|
user_key="my-secure-key-min-8-chars"
|
|
164
214
|
) as client:
|
|
165
215
|
# Create and use configurations
|
|
166
|
-
config = client.create("app", {"version": "1.0.0", "debug": False})
|
|
216
|
+
config = client.create("app", {"version": "1.0.0", "debug": False}, "production")
|
|
167
217
|
print(config)
|
|
168
218
|
# Session automatically closed when exiting context
|
|
169
219
|
```
|
|
@@ -191,33 +241,78 @@ client = OpenSecureConfClient(
|
|
|
191
241
|
|
|
192
242
|
## 🎯 Core Features
|
|
193
243
|
|
|
244
|
+
### Multi-Environment Support
|
|
245
|
+
|
|
246
|
+
OpenSecureConf now supports having the same configuration key in different environments, allowing you to maintain separate configurations for production, staging, development, etc.
|
|
247
|
+
|
|
248
|
+
```python
|
|
249
|
+
# Create same key in multiple environments
|
|
250
|
+
environments = ["production", "staging", "development"]
|
|
251
|
+
configs = {
|
|
252
|
+
"production": {"host": "db.prod.com", "port": 5432, "ssl": True},
|
|
253
|
+
"staging": {"host": "db.staging.com", "port": 5432, "ssl": True},
|
|
254
|
+
"development": {"host": "localhost", "port": 5432, "ssl": False}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
for env in environments:
|
|
258
|
+
client.create("database", configs[env], env, "config")
|
|
259
|
+
|
|
260
|
+
# Read from specific environment
|
|
261
|
+
prod_db = client.read("database", "production")
|
|
262
|
+
dev_db = client.read("database", "development")
|
|
263
|
+
|
|
264
|
+
print(f"Production uses: {prod_db['value']['host']}")
|
|
265
|
+
print(f"Development uses: {dev_db['value']['host']}")
|
|
266
|
+
|
|
267
|
+
# List all environments
|
|
268
|
+
all_environments = client.list_environments()
|
|
269
|
+
print(f"Available environments: {', '.join(all_environments)}")
|
|
270
|
+
|
|
271
|
+
# List configurations for specific environment
|
|
272
|
+
prod_configs = client.list_all(environment="production")
|
|
273
|
+
print(f"Production has {len(prod_configs)} configurations")
|
|
274
|
+
|
|
275
|
+
# Update only in specific environment
|
|
276
|
+
client.update("database", "staging", {"host": "db-v2.staging.com", "port": 5433})
|
|
277
|
+
|
|
278
|
+
# Delete from specific environment
|
|
279
|
+
client.delete("database", "development")
|
|
280
|
+
# Production and staging remain untouched
|
|
281
|
+
```
|
|
282
|
+
|
|
194
283
|
### Basic CRUD Operations
|
|
195
284
|
|
|
196
285
|
#### Create Configuration
|
|
197
286
|
|
|
198
287
|
```python
|
|
199
|
-
# Create a
|
|
288
|
+
# Create a configuration (environment is REQUIRED)
|
|
200
289
|
config = client.create(
|
|
201
290
|
key="api_settings",
|
|
202
291
|
value={"base_url": "https://api.example.com", "timeout": 30, "retries": 3},
|
|
203
|
-
|
|
292
|
+
environment="production", # REQUIRED
|
|
293
|
+
category="config"
|
|
204
294
|
)
|
|
205
295
|
|
|
206
296
|
# Create with validation
|
|
207
|
-
if not client.exists("api_settings"):
|
|
208
|
-
config = client.create(
|
|
297
|
+
if not client.exists("api_settings", "production"):
|
|
298
|
+
config = client.create(
|
|
299
|
+
"api_settings",
|
|
300
|
+
{"base_url": "https://api.example.com"},
|
|
301
|
+
"production"
|
|
302
|
+
)
|
|
209
303
|
```
|
|
210
304
|
|
|
211
305
|
#### Read Configuration
|
|
212
306
|
|
|
213
307
|
```python
|
|
214
|
-
# Read a specific configuration
|
|
215
|
-
config = client.read("api_settings")
|
|
308
|
+
# Read a specific configuration (environment is REQUIRED)
|
|
309
|
+
config = client.read("api_settings", "production")
|
|
216
310
|
print(f"API URL: {config['value']['base_url']}")
|
|
217
311
|
|
|
218
312
|
# Safe read with default value
|
|
219
313
|
config = client.get_or_default(
|
|
220
314
|
"optional_setting",
|
|
315
|
+
"production",
|
|
221
316
|
default={"enabled": False, "timeout": 30}
|
|
222
317
|
)
|
|
223
318
|
```
|
|
@@ -225,24 +320,25 @@ config = client.get_or_default(
|
|
|
225
320
|
#### Update Configuration
|
|
226
321
|
|
|
227
322
|
```python
|
|
228
|
-
# Update existing configuration
|
|
323
|
+
# Update existing configuration (environment is REQUIRED)
|
|
229
324
|
updated = client.update(
|
|
230
325
|
key="api_settings",
|
|
326
|
+
environment="production", # REQUIRED, cannot be changed
|
|
231
327
|
value={"base_url": "https://api.example.com", "timeout": 60, "retries": 5},
|
|
232
|
-
category="
|
|
328
|
+
category="config"
|
|
233
329
|
)
|
|
234
330
|
```
|
|
235
331
|
|
|
236
332
|
#### Delete Configuration
|
|
237
333
|
|
|
238
334
|
```python
|
|
239
|
-
# Delete a
|
|
240
|
-
result = client.delete("api_settings")
|
|
335
|
+
# Delete a configuration (environment is REQUIRED)
|
|
336
|
+
result = client.delete("api_settings", "production")
|
|
241
337
|
print(result["message"])
|
|
242
338
|
|
|
243
339
|
# Conditional delete
|
|
244
|
-
if client.exists("temporary_config"):
|
|
245
|
-
client.delete("temporary_config")
|
|
340
|
+
if client.exists("temporary_config", "staging"):
|
|
341
|
+
client.delete("temporary_config", "staging")
|
|
246
342
|
```
|
|
247
343
|
|
|
248
344
|
#### List Configurations
|
|
@@ -252,14 +348,20 @@ if client.exists("temporary_config"):
|
|
|
252
348
|
all_configs = client.list_all()
|
|
253
349
|
print(f"Total configurations: {len(all_configs)}")
|
|
254
350
|
|
|
255
|
-
# List by
|
|
256
|
-
prod_configs = client.list_all(
|
|
351
|
+
# List by environment
|
|
352
|
+
prod_configs = client.list_all(environment="production")
|
|
257
353
|
for config in prod_configs:
|
|
258
354
|
print(f"- {config['key']}: {config['value']}")
|
|
259
355
|
|
|
356
|
+
# List by category
|
|
357
|
+
db_configs = client.list_all(category="database")
|
|
358
|
+
|
|
359
|
+
# List by both environment and category
|
|
360
|
+
prod_db_configs = client.list_all(category="database", environment="production")
|
|
361
|
+
|
|
260
362
|
# Get count
|
|
261
363
|
total = client.count()
|
|
262
|
-
prod_count = client.count(
|
|
364
|
+
prod_count = client.count(environment="production")
|
|
263
365
|
print(f"Production configs: {prod_count}/{total}")
|
|
264
366
|
```
|
|
265
367
|
|
|
@@ -293,21 +395,24 @@ Perform multiple operations efficiently:
|
|
|
293
395
|
#### Bulk Create
|
|
294
396
|
|
|
295
397
|
```python
|
|
296
|
-
# Create multiple configurations at once
|
|
398
|
+
# Create multiple configurations at once (environment REQUIRED for each)
|
|
297
399
|
configs_to_create = [
|
|
298
400
|
{
|
|
299
401
|
"key": "service1",
|
|
300
402
|
"value": {"url": "http://service1.local", "timeout": 30},
|
|
403
|
+
"environment": "production", # REQUIRED
|
|
301
404
|
"category": "microservices"
|
|
302
405
|
},
|
|
303
406
|
{
|
|
304
|
-
"key": "
|
|
305
|
-
"value": {"url": "http://
|
|
407
|
+
"key": "service1",
|
|
408
|
+
"value": {"url": "http://service1.staging.local", "timeout": 30},
|
|
409
|
+
"environment": "staging", # REQUIRED (same key, different environment)
|
|
306
410
|
"category": "microservices"
|
|
307
411
|
},
|
|
308
412
|
{
|
|
309
|
-
"key": "
|
|
310
|
-
"value": {"url": "http://
|
|
413
|
+
"key": "service2",
|
|
414
|
+
"value": {"url": "http://service2.local", "timeout": 60},
|
|
415
|
+
"environment": "production", # REQUIRED
|
|
311
416
|
"category": "microservices"
|
|
312
417
|
}
|
|
313
418
|
]
|
|
@@ -324,35 +429,44 @@ print(f"Created {len(results)} configurations")
|
|
|
324
429
|
#### Bulk Read
|
|
325
430
|
|
|
326
431
|
```python
|
|
327
|
-
# Read multiple configurations at once
|
|
328
|
-
|
|
432
|
+
# Read multiple configurations at once (environment REQUIRED for each)
|
|
433
|
+
items = [
|
|
434
|
+
{"key": "service1", "environment": "production"},
|
|
435
|
+
{"key": "service1", "environment": "staging"},
|
|
436
|
+
{"key": "service2", "environment": "production"},
|
|
437
|
+
{"key": "api_settings", "environment": "production"}
|
|
438
|
+
]
|
|
329
439
|
|
|
330
440
|
# Read all (stop on first error)
|
|
331
|
-
configs = client.bulk_read(
|
|
441
|
+
configs = client.bulk_read(items)
|
|
332
442
|
|
|
333
443
|
# Read all (skip missing keys)
|
|
334
|
-
configs = client.bulk_read(
|
|
444
|
+
configs = client.bulk_read(items, ignore_errors=True)
|
|
335
445
|
for config in configs:
|
|
336
|
-
print(f"{config['key']}
|
|
446
|
+
print(f"{config['key']} ({config['environment']}): {config['value']}")
|
|
337
447
|
```
|
|
338
448
|
|
|
339
449
|
#### Bulk Delete
|
|
340
450
|
|
|
341
451
|
```python
|
|
342
|
-
# Delete multiple configurations
|
|
343
|
-
|
|
452
|
+
# Delete multiple configurations (environment REQUIRED for each)
|
|
453
|
+
items_to_delete = [
|
|
454
|
+
{"key": "temp1", "environment": "staging"},
|
|
455
|
+
{"key": "temp2", "environment": "staging"},
|
|
456
|
+
{"key": "temp3", "environment": "development"}
|
|
457
|
+
]
|
|
344
458
|
|
|
345
459
|
# Delete all (stop on first error)
|
|
346
|
-
result = client.bulk_delete(
|
|
460
|
+
result = client.bulk_delete(items_to_delete)
|
|
347
461
|
|
|
348
462
|
# Delete all (continue on errors)
|
|
349
|
-
result = client.bulk_delete(
|
|
463
|
+
result = client.bulk_delete(items_to_delete, ignore_errors=True)
|
|
350
464
|
print(f"Deleted: {len(result['deleted'])}")
|
|
351
465
|
print(f"Failed: {len(result['failed'])}")
|
|
352
466
|
|
|
353
467
|
# Inspect failures
|
|
354
468
|
for failure in result['failed']:
|
|
355
|
-
print(f"Failed to delete '{failure['key']}': {failure['error']}")
|
|
469
|
+
print(f"Failed to delete '{failure['key']}' from '{failure['environment']}': {failure['error']}")
|
|
356
470
|
```
|
|
357
471
|
|
|
358
472
|
### Retry Logic
|
|
@@ -372,7 +486,7 @@ client = OpenSecureConfClient(
|
|
|
372
486
|
|
|
373
487
|
# Automatic retry on transient failures (429, 500, 502, 503, 504)
|
|
374
488
|
try:
|
|
375
|
-
config = client.read("my_config")
|
|
489
|
+
config = client.read("my_config", "production")
|
|
376
490
|
except Exception as e:
|
|
377
491
|
print(f"Failed after {client.max_retries} retries: {e}")
|
|
378
492
|
```
|
|
@@ -413,21 +527,22 @@ Convenient helper methods:
|
|
|
413
527
|
#### Check Existence
|
|
414
528
|
|
|
415
529
|
```python
|
|
416
|
-
# Check if a key exists
|
|
417
|
-
if client.exists("database"):
|
|
418
|
-
print("Configuration exists")
|
|
419
|
-
config = client.read("database")
|
|
530
|
+
# Check if a key exists in specific environment
|
|
531
|
+
if client.exists("database", "production"):
|
|
532
|
+
print("Configuration exists in production")
|
|
533
|
+
config = client.read("database", "production")
|
|
420
534
|
else:
|
|
421
|
-
print("Configuration does not exist")
|
|
422
|
-
config = client.create("database", {"host": "localhost"})
|
|
535
|
+
print("Configuration does not exist in production")
|
|
536
|
+
config = client.create("database", {"host": "localhost"}, "production")
|
|
423
537
|
```
|
|
424
538
|
|
|
425
539
|
#### Get with Default
|
|
426
540
|
|
|
427
541
|
```python
|
|
428
|
-
# Get configuration or return default
|
|
542
|
+
# Get configuration or return default (environment REQUIRED)
|
|
429
543
|
config = client.get_or_default(
|
|
430
544
|
"optional_feature",
|
|
545
|
+
"production",
|
|
431
546
|
default={"enabled": False, "timeout": 30}
|
|
432
547
|
)
|
|
433
548
|
|
|
@@ -443,23 +558,33 @@ if config['value']['enabled']:
|
|
|
443
558
|
total = client.count()
|
|
444
559
|
print(f"Total configurations: {total}")
|
|
445
560
|
|
|
561
|
+
# Count by environment
|
|
562
|
+
prod_count = client.count(environment="production")
|
|
563
|
+
staging_count = client.count(environment="staging")
|
|
564
|
+
print(f"Production: {prod_count}, Staging: {staging_count}")
|
|
565
|
+
|
|
446
566
|
# Count by category
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
567
|
+
db_count = client.count(category="database")
|
|
568
|
+
|
|
569
|
+
# Count by both
|
|
570
|
+
prod_db_count = client.count(category="database", environment="production")
|
|
450
571
|
```
|
|
451
572
|
|
|
452
|
-
#### List Categories
|
|
573
|
+
#### List Categories and Environments
|
|
453
574
|
|
|
454
575
|
```python
|
|
455
576
|
# Get all unique categories
|
|
456
577
|
categories = client.list_categories()
|
|
457
578
|
print(f"Available categories: {', '.join(categories)}")
|
|
458
579
|
|
|
459
|
-
#
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
580
|
+
# Get all unique environments
|
|
581
|
+
environments = client.list_environments()
|
|
582
|
+
print(f"Available environments: {', '.join(environments)}")
|
|
583
|
+
|
|
584
|
+
# Process by environment
|
|
585
|
+
for env in environments:
|
|
586
|
+
count = client.count(environment=env)
|
|
587
|
+
print(f"{env}: {count} configurations")
|
|
463
588
|
```
|
|
464
589
|
|
|
465
590
|
## 🔧 Advanced Usage
|
|
@@ -497,8 +622,8 @@ client = OpenSecureConfClient(
|
|
|
497
622
|
)
|
|
498
623
|
|
|
499
624
|
# All operations will be logged
|
|
500
|
-
config = client.create("test", {"data": "value"})
|
|
501
|
-
# Output: 2026-
|
|
625
|
+
config = client.create("test", {"data": "value"}, "production")
|
|
626
|
+
# Output: 2026-02-05 12:00:00 - opensecureconf_client - INFO - POST /configs - Status: 201 - Duration: 0.045s
|
|
502
627
|
```
|
|
503
628
|
|
|
504
629
|
### SSL Configuration
|
|
@@ -541,6 +666,53 @@ client = OpenSecureConfClient(
|
|
|
541
666
|
)
|
|
542
667
|
```
|
|
543
668
|
|
|
669
|
+
### Working with Multiple Environments
|
|
670
|
+
|
|
671
|
+
```python
|
|
672
|
+
from opensecureconf_client_enhanced import OpenSecureConfClient
|
|
673
|
+
|
|
674
|
+
with OpenSecureConfClient(
|
|
675
|
+
base_url="http://localhost:9000",
|
|
676
|
+
user_key="my-key"
|
|
677
|
+
) as client:
|
|
678
|
+
# Define configurations for different environments
|
|
679
|
+
environments = {
|
|
680
|
+
"production": {
|
|
681
|
+
"database": {"host": "db.prod.com", "port": 5432, "ssl": True},
|
|
682
|
+
"api_url": "https://api.prod.com",
|
|
683
|
+
"debug": False
|
|
684
|
+
},
|
|
685
|
+
"staging": {
|
|
686
|
+
"database": {"host": "db.staging.com", "port": 5432, "ssl": True},
|
|
687
|
+
"api_url": "https://api.staging.com",
|
|
688
|
+
"debug": True
|
|
689
|
+
},
|
|
690
|
+
"development": {
|
|
691
|
+
"database": {"host": "localhost", "port": 5432, "ssl": False},
|
|
692
|
+
"api_url": "http://localhost:3000",
|
|
693
|
+
"debug": True
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
# Create configurations for all environments
|
|
698
|
+
for env_name, configs in environments.items():
|
|
699
|
+
for key, value in configs.items():
|
|
700
|
+
client.create(key, value, env_name, "config")
|
|
701
|
+
print(f"Created {key} for {env_name}")
|
|
702
|
+
|
|
703
|
+
# Read environment-specific configuration
|
|
704
|
+
prod_db = client.read("database", "production")
|
|
705
|
+
print(f"Production database: {prod_db['value']['host']}")
|
|
706
|
+
|
|
707
|
+
# Update only staging environment
|
|
708
|
+
client.update("api_url", "staging", "https://api-v2.staging.com")
|
|
709
|
+
|
|
710
|
+
# Delete development configurations
|
|
711
|
+
dev_configs = client.list_all(environment="development")
|
|
712
|
+
for config in dev_configs:
|
|
713
|
+
client.delete(config['key'], "development")
|
|
714
|
+
```
|
|
715
|
+
|
|
544
716
|
### Working with Clusters
|
|
545
717
|
|
|
546
718
|
```python
|
|
@@ -559,11 +731,11 @@ print(f"Connected to: {status['node_id']}")
|
|
|
559
731
|
print(f"Cluster mode: {status['mode']}")
|
|
560
732
|
|
|
561
733
|
# In REPLICA mode: writes are automatically replicated
|
|
562
|
-
config = client.create("shared_config", {"data": "value"})
|
|
734
|
+
config = client.create("shared_config", {"data": "value"}, "production")
|
|
563
735
|
# This configuration is now available on all nodes
|
|
564
736
|
|
|
565
737
|
# In FEDERATED mode: reads check all nodes
|
|
566
|
-
config = client.read("distributed_config")
|
|
738
|
+
config = client.read("distributed_config", "production")
|
|
567
739
|
# This searches across all federated nodes
|
|
568
740
|
|
|
569
741
|
# Monitor cluster health
|
|
@@ -613,166 +785,113 @@ OpenSecureConfClient(
|
|
|
613
785
|
|
|
614
786
|
#### Methods
|
|
615
787
|
|
|
616
|
-
##### Health & Status
|
|
617
|
-
|
|
618
|
-
###### `ping() -> bool`
|
|
619
|
-
|
|
620
|
-
Check if the API server is reachable.
|
|
621
|
-
|
|
622
|
-
**Returns:** `True` if server is healthy, `False` otherwise
|
|
623
|
-
|
|
624
|
-
**Example:**
|
|
625
|
-
```python
|
|
626
|
-
if client.ping():
|
|
627
|
-
print("Server is online")
|
|
628
|
-
```
|
|
629
|
-
|
|
630
|
-
###### `get_service_info() -> Dict[str, Any]`
|
|
631
|
-
|
|
632
|
-
Get information about the OpenSecureConf service.
|
|
633
|
-
|
|
634
|
-
**Returns:** Dictionary with service metadata
|
|
635
|
-
- `service`: Service name
|
|
636
|
-
- `version`: API version
|
|
637
|
-
- `features`: List of enabled features
|
|
638
|
-
- `cluster_enabled`: Whether clustering is enabled
|
|
639
|
-
- `cluster_mode`: Cluster mode (if enabled)
|
|
640
|
-
|
|
641
|
-
**Example:**
|
|
642
|
-
```python
|
|
643
|
-
info = client.get_service_info()
|
|
644
|
-
print(f"Version: {info['version']}")
|
|
645
|
-
```
|
|
646
|
-
|
|
647
|
-
##### Cluster Operations
|
|
648
|
-
|
|
649
|
-
###### `get_cluster_status() -> Dict[str, Any]`
|
|
650
|
-
|
|
651
|
-
Get cluster status and node information.
|
|
652
|
-
|
|
653
|
-
**Returns:** Dictionary with cluster status
|
|
654
|
-
- `enabled`: Whether clustering is enabled
|
|
655
|
-
- `mode`: Cluster mode (replica or federated)
|
|
656
|
-
- `node_id`: Current node identifier
|
|
657
|
-
- `total_nodes`: Total number of nodes
|
|
658
|
-
- `healthy_nodes`: Number of healthy nodes
|
|
659
|
-
|
|
660
|
-
**Raises:** `ClusterError` if cluster status cannot be retrieved
|
|
661
|
-
|
|
662
|
-
**Example:**
|
|
663
|
-
```python
|
|
664
|
-
status = client.get_cluster_status()
|
|
665
|
-
print(f"Healthy: {status['healthy_nodes']}/{status['total_nodes']}")
|
|
666
|
-
```
|
|
667
|
-
|
|
668
|
-
###### `get_cluster_health() -> Dict[str, Any]`
|
|
669
|
-
|
|
670
|
-
Check cluster node health.
|
|
671
|
-
|
|
672
|
-
**Returns:** Dictionary with health status
|
|
673
|
-
|
|
674
|
-
**Example:**
|
|
675
|
-
```python
|
|
676
|
-
health = client.get_cluster_health()
|
|
677
|
-
print(f"Status: {health['status']}")
|
|
678
|
-
```
|
|
679
|
-
|
|
680
788
|
##### Configuration CRUD
|
|
681
789
|
|
|
682
|
-
###### `create(key: str, value: Dict
|
|
790
|
+
###### `create(key: str, value: Union[Dict, str, int, bool, list], environment: str, category: Optional[str] = None) -> Dict[str, Any]`
|
|
683
791
|
|
|
684
792
|
Create a new encrypted configuration entry.
|
|
685
793
|
|
|
686
794
|
**Parameters:**
|
|
687
|
-
- `key`:
|
|
688
|
-
- `value`: Configuration data
|
|
795
|
+
- `key`: Configuration key (1-255 characters)
|
|
796
|
+
- `value`: Configuration data (dict, string, int, bool, or list)
|
|
797
|
+
- `environment`: Environment identifier (REQUIRED, max 100 characters)
|
|
689
798
|
- `category`: Optional category for grouping (max 100 characters)
|
|
690
799
|
|
|
691
800
|
**Returns:** Dictionary with created configuration
|
|
692
801
|
|
|
693
802
|
**Raises:**
|
|
694
|
-
- `ConfigurationExistsError`: If key already exists
|
|
803
|
+
- `ConfigurationExistsError`: If (key, environment) already exists
|
|
695
804
|
- `ValueError`: If parameters are invalid
|
|
696
805
|
|
|
697
806
|
**Example:**
|
|
698
807
|
```python
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
category="production"
|
|
703
|
-
)
|
|
808
|
+
# Same key in different environments
|
|
809
|
+
prod_config = client.create("database", {"host": "db.prod.com"}, "production", "config")
|
|
810
|
+
staging_config = client.create("database", {"host": "db.staging.com"}, "staging", "config")
|
|
704
811
|
```
|
|
705
812
|
|
|
706
|
-
###### `read(key: str) -> Dict[str, Any]`
|
|
813
|
+
###### `read(key: str, environment: str) -> Dict[str, Any]`
|
|
707
814
|
|
|
708
|
-
Read and decrypt a configuration entry.
|
|
815
|
+
Read and decrypt a configuration entry by key and environment.
|
|
709
816
|
|
|
710
817
|
**Parameters:**
|
|
711
818
|
- `key`: Configuration key to retrieve
|
|
819
|
+
- `environment`: Environment identifier (REQUIRED)
|
|
712
820
|
|
|
713
821
|
**Returns:** Dictionary with configuration
|
|
714
822
|
|
|
715
823
|
**Raises:**
|
|
716
|
-
- `ConfigurationNotFoundError`: If key does not exist
|
|
717
|
-
- `ValueError`: If
|
|
824
|
+
- `ConfigurationNotFoundError`: If (key, environment) does not exist
|
|
825
|
+
- `ValueError`: If parameters are invalid
|
|
718
826
|
|
|
719
827
|
**Example:**
|
|
720
828
|
```python
|
|
721
|
-
|
|
722
|
-
|
|
829
|
+
prod_config = client.read("database", "production")
|
|
830
|
+
staging_config = client.read("database", "staging")
|
|
723
831
|
```
|
|
724
832
|
|
|
725
|
-
###### `update(key: str, value: Dict
|
|
833
|
+
###### `update(key: str, environment: str, value: Union[Dict, str, int, bool, list], category: Optional[str] = None) -> Dict[str, Any]`
|
|
726
834
|
|
|
727
835
|
Update an existing configuration entry.
|
|
728
836
|
|
|
729
837
|
**Parameters:**
|
|
730
838
|
- `key`: Configuration key to update
|
|
839
|
+
- `environment`: Environment identifier (REQUIRED, cannot be changed)
|
|
731
840
|
- `value`: New configuration data
|
|
732
841
|
- `category`: Optional new category
|
|
733
842
|
|
|
734
843
|
**Returns:** Dictionary with updated configuration
|
|
735
844
|
|
|
736
845
|
**Raises:**
|
|
737
|
-
- `ConfigurationNotFoundError`: If key does not exist
|
|
846
|
+
- `ConfigurationNotFoundError`: If (key, environment) does not exist
|
|
738
847
|
- `ValueError`: If parameters are invalid
|
|
739
848
|
|
|
740
849
|
**Example:**
|
|
741
850
|
```python
|
|
742
|
-
updated = client.update("database", {"host": "db.
|
|
851
|
+
updated = client.update("database", "production", {"host": "db-new.prod.com"})
|
|
743
852
|
```
|
|
744
853
|
|
|
745
|
-
###### `delete(key: str) -> Dict[str, str]`
|
|
854
|
+
###### `delete(key: str, environment: str) -> Dict[str, str]`
|
|
746
855
|
|
|
747
|
-
Delete a configuration entry permanently.
|
|
856
|
+
Delete a configuration entry permanently from specific environment.
|
|
748
857
|
|
|
749
858
|
**Parameters:**
|
|
750
859
|
- `key`: Configuration key to delete
|
|
860
|
+
- `environment`: Environment identifier (REQUIRED)
|
|
751
861
|
|
|
752
862
|
**Returns:** Dictionary with success message
|
|
753
863
|
|
|
754
864
|
**Raises:**
|
|
755
|
-
- `ConfigurationNotFoundError`: If key does not exist
|
|
756
|
-
- `ValueError`: If
|
|
865
|
+
- `ConfigurationNotFoundError`: If (key, environment) does not exist
|
|
866
|
+
- `ValueError`: If parameters are invalid
|
|
757
867
|
|
|
758
868
|
**Example:**
|
|
759
869
|
```python
|
|
760
|
-
|
|
761
|
-
|
|
870
|
+
# Delete from staging only
|
|
871
|
+
result = client.delete("database", "staging")
|
|
872
|
+
# Production remains untouched
|
|
762
873
|
```
|
|
763
874
|
|
|
764
|
-
###### `list_all(category: Optional[str] = None) -> List[Dict[str, Any]]`
|
|
875
|
+
###### `list_all(category: Optional[str] = None, environment: Optional[str] = None) -> List[Dict[str, Any]]`
|
|
765
876
|
|
|
766
|
-
List all configurations with optional
|
|
877
|
+
List all configurations with optional filters.
|
|
767
878
|
|
|
768
879
|
**Parameters:**
|
|
769
880
|
- `category`: Optional category filter
|
|
881
|
+
- `environment`: Optional environment filter
|
|
770
882
|
|
|
771
883
|
**Returns:** List of configuration dictionaries
|
|
772
884
|
|
|
773
885
|
**Example:**
|
|
774
886
|
```python
|
|
775
|
-
|
|
887
|
+
# List all production configurations
|
|
888
|
+
prod_configs = client.list_all(environment="production")
|
|
889
|
+
|
|
890
|
+
# List all database configurations
|
|
891
|
+
db_configs = client.list_all(category="database")
|
|
892
|
+
|
|
893
|
+
# List production database configurations
|
|
894
|
+
configs = client.list_all(category="database", environment="production")
|
|
776
895
|
```
|
|
777
896
|
|
|
778
897
|
##### Batch Operations
|
|
@@ -782,100 +901,110 @@ configs = client.list_all(category="production")
|
|
|
782
901
|
Create multiple configurations in batch.
|
|
783
902
|
|
|
784
903
|
**Parameters:**
|
|
785
|
-
- `configs`: List of configuration dictionaries
|
|
904
|
+
- `configs`: List of configuration dictionaries with 'key', 'value', 'environment' (REQUIRED), and optional 'category'
|
|
786
905
|
- `ignore_errors`: Continue on errors and return partial results
|
|
787
906
|
|
|
788
907
|
**Returns:** List of created configurations
|
|
789
908
|
|
|
790
909
|
**Raises:**
|
|
791
|
-
- `ValueError`: If configs format is invalid
|
|
910
|
+
- `ValueError`: If configs format is invalid or environment is missing
|
|
792
911
|
- `OpenSecureConfError`: If creation fails and ignore_errors is False
|
|
793
912
|
|
|
794
913
|
**Example:**
|
|
795
914
|
```python
|
|
796
915
|
configs = [
|
|
797
|
-
{"key": "
|
|
798
|
-
{"key": "
|
|
916
|
+
{"key": "db", "value": {"host": "localhost"}, "environment": "production", "category": "config"},
|
|
917
|
+
{"key": "db", "value": {"host": "localhost"}, "environment": "staging", "category": "config"}
|
|
799
918
|
]
|
|
800
919
|
results = client.bulk_create(configs)
|
|
801
920
|
```
|
|
802
921
|
|
|
803
|
-
###### `bulk_read(
|
|
922
|
+
###### `bulk_read(items: List[Dict[str, str]], ignore_errors: bool = False) -> List[Dict[str, Any]]`
|
|
804
923
|
|
|
805
924
|
Read multiple configurations in batch.
|
|
806
925
|
|
|
807
926
|
**Parameters:**
|
|
808
|
-
- `
|
|
927
|
+
- `items`: List of dictionaries with 'key' and 'environment' fields
|
|
809
928
|
- `ignore_errors`: Skip missing keys and return partial results
|
|
810
929
|
|
|
811
930
|
**Returns:** List of configuration dictionaries
|
|
812
931
|
|
|
813
932
|
**Example:**
|
|
814
933
|
```python
|
|
815
|
-
|
|
934
|
+
items = [
|
|
935
|
+
{"key": "database", "environment": "production"},
|
|
936
|
+
{"key": "database", "environment": "staging"}
|
|
937
|
+
]
|
|
938
|
+
configs = client.bulk_read(items)
|
|
816
939
|
```
|
|
817
940
|
|
|
818
|
-
###### `bulk_delete(
|
|
941
|
+
###### `bulk_delete(items: List[Dict[str, str]], ignore_errors: bool = False) -> Dict[str, Any]`
|
|
819
942
|
|
|
820
943
|
Delete multiple configurations in batch.
|
|
821
944
|
|
|
822
945
|
**Parameters:**
|
|
823
|
-
- `
|
|
946
|
+
- `items`: List of dictionaries with 'key' and 'environment' fields
|
|
824
947
|
- `ignore_errors`: Continue on errors
|
|
825
948
|
|
|
826
949
|
**Returns:** Dictionary with summary: `{"deleted": [...], "failed": [...]}`
|
|
827
950
|
|
|
828
951
|
**Example:**
|
|
829
952
|
```python
|
|
830
|
-
|
|
831
|
-
|
|
953
|
+
items = [
|
|
954
|
+
{"key": "temp1", "environment": "staging"},
|
|
955
|
+
{"key": "temp2", "environment": "staging"}
|
|
956
|
+
]
|
|
957
|
+
result = client.bulk_delete(items)
|
|
832
958
|
```
|
|
833
959
|
|
|
834
960
|
##### Utility Methods
|
|
835
961
|
|
|
836
|
-
###### `exists(key: str) -> bool`
|
|
962
|
+
###### `exists(key: str, environment: str) -> bool`
|
|
837
963
|
|
|
838
|
-
Check if a configuration key exists.
|
|
964
|
+
Check if a configuration key exists in specific environment.
|
|
839
965
|
|
|
840
966
|
**Parameters:**
|
|
841
967
|
- `key`: Configuration key to check
|
|
968
|
+
- `environment`: Environment identifier (REQUIRED)
|
|
842
969
|
|
|
843
|
-
**Returns:** `True` if key exists, `False` otherwise
|
|
970
|
+
**Returns:** `True` if key exists in the environment, `False` otherwise
|
|
844
971
|
|
|
845
972
|
**Example:**
|
|
846
973
|
```python
|
|
847
|
-
if client.exists("database"):
|
|
848
|
-
config = client.read("database")
|
|
974
|
+
if client.exists("database", "production"):
|
|
975
|
+
config = client.read("database", "production")
|
|
849
976
|
```
|
|
850
977
|
|
|
851
|
-
###### `get_or_default(key: str, default: Dict
|
|
978
|
+
###### `get_or_default(key: str, environment: str, default: Union[Dict, str, int, bool, list]) -> Dict[str, Any]`
|
|
852
979
|
|
|
853
980
|
Get configuration or return default if not found.
|
|
854
981
|
|
|
855
982
|
**Parameters:**
|
|
856
983
|
- `key`: Configuration key to retrieve
|
|
984
|
+
- `environment`: Environment identifier (REQUIRED)
|
|
857
985
|
- `default`: Default value to return if not found
|
|
858
986
|
|
|
859
987
|
**Returns:** Configuration dictionary or default
|
|
860
988
|
|
|
861
989
|
**Example:**
|
|
862
990
|
```python
|
|
863
|
-
config = client.get_or_default("optional", {"enabled": False})
|
|
991
|
+
config = client.get_or_default("optional", "production", {"enabled": False})
|
|
864
992
|
```
|
|
865
993
|
|
|
866
|
-
###### `count(category: Optional[str] = None) -> int`
|
|
994
|
+
###### `count(category: Optional[str] = None, environment: Optional[str] = None) -> int`
|
|
867
995
|
|
|
868
|
-
Count configurations, optionally filtered by category.
|
|
996
|
+
Count configurations, optionally filtered by category and/or environment.
|
|
869
997
|
|
|
870
998
|
**Parameters:**
|
|
871
999
|
- `category`: Optional category filter
|
|
1000
|
+
- `environment`: Optional environment filter
|
|
872
1001
|
|
|
873
1002
|
**Returns:** Number of configurations
|
|
874
1003
|
|
|
875
1004
|
**Example:**
|
|
876
1005
|
```python
|
|
877
1006
|
total = client.count()
|
|
878
|
-
prod_count = client.count(
|
|
1007
|
+
prod_count = client.count(environment="production")
|
|
879
1008
|
```
|
|
880
1009
|
|
|
881
1010
|
###### `list_categories() -> List[str]`
|
|
@@ -887,7 +1016,18 @@ Get list of all unique categories.
|
|
|
887
1016
|
**Example:**
|
|
888
1017
|
```python
|
|
889
1018
|
categories = client.list_categories()
|
|
890
|
-
|
|
1019
|
+
```
|
|
1020
|
+
|
|
1021
|
+
###### `list_environments() -> List[str]`
|
|
1022
|
+
|
|
1023
|
+
Get list of all unique environments.
|
|
1024
|
+
|
|
1025
|
+
**Returns:** Sorted list of environment names
|
|
1026
|
+
|
|
1027
|
+
**Example:**
|
|
1028
|
+
```python
|
|
1029
|
+
environments = client.list_environments()
|
|
1030
|
+
print(f"Environments: {', '.join(environments)}")
|
|
891
1031
|
```
|
|
892
1032
|
|
|
893
1033
|
##### Session Management
|
|
@@ -908,7 +1048,7 @@ The client supports context manager protocol for automatic cleanup.
|
|
|
908
1048
|
**Example:**
|
|
909
1049
|
```python
|
|
910
1050
|
with OpenSecureConfClient(base_url="...", user_key="...") as client:
|
|
911
|
-
config = client.create("key", {"value": "data"})
|
|
1051
|
+
config = client.create("key", {"value": "data"}, "production")
|
|
912
1052
|
# Session automatically closed
|
|
913
1053
|
```
|
|
914
1054
|
|
|
@@ -919,8 +1059,8 @@ with OpenSecureConfClient(base_url="...", user_key="...") as client:
|
|
|
919
1059
|
```
|
|
920
1060
|
OpenSecureConfError (base exception)
|
|
921
1061
|
├── AuthenticationError # Invalid or missing credentials
|
|
922
|
-
├── ConfigurationNotFoundError # Configuration key does not exist
|
|
923
|
-
├── ConfigurationExistsError # Configuration key already exists
|
|
1062
|
+
├── ConfigurationNotFoundError # Configuration (key, environment) does not exist
|
|
1063
|
+
├── ConfigurationExistsError # Configuration (key, environment) already exists
|
|
924
1064
|
└── ClusterError # Cluster operation failed
|
|
925
1065
|
```
|
|
926
1066
|
|
|
@@ -937,14 +1077,14 @@ from opensecureconf_client import (
|
|
|
937
1077
|
)
|
|
938
1078
|
|
|
939
1079
|
try:
|
|
940
|
-
config = client.create("mykey", {"data": "value"})
|
|
1080
|
+
config = client.create("mykey", {"data": "value"}, "production")
|
|
941
1081
|
except AuthenticationError:
|
|
942
1082
|
print("Authentication failed - check your user_key and api_key")
|
|
943
1083
|
except ConfigurationExistsError:
|
|
944
|
-
print("Configuration already exists - use update() instead")
|
|
945
|
-
config = client.update("mykey", {"data": "new_value"})
|
|
1084
|
+
print("Configuration already exists in this environment - use update() instead")
|
|
1085
|
+
config = client.update("mykey", "production", {"data": "new_value"})
|
|
946
1086
|
except ConfigurationNotFoundError:
|
|
947
|
-
print("Configuration not found")
|
|
1087
|
+
print("Configuration not found in specified environment")
|
|
948
1088
|
except ClusterError as e:
|
|
949
1089
|
print(f"Cluster error: {e}")
|
|
950
1090
|
except OpenSecureConfError as e:
|
|
@@ -958,20 +1098,20 @@ except ConnectionError as e:
|
|
|
958
1098
|
```python
|
|
959
1099
|
# 1. Use specific exceptions first
|
|
960
1100
|
try:
|
|
961
|
-
config = client.read("important_config")
|
|
1101
|
+
config = client.read("important_config", "production")
|
|
962
1102
|
except ConfigurationNotFoundError:
|
|
963
1103
|
# Create with defaults if not found
|
|
964
|
-
config = client.create("important_config", {"default": "value"})
|
|
1104
|
+
config = client.create("important_config", {"default": "value"}, "production")
|
|
965
1105
|
|
|
966
1106
|
# 2. Use exists() to avoid exceptions
|
|
967
|
-
if not client.exists("optional_config"):
|
|
968
|
-
client.create("optional_config", {"data": "value"})
|
|
1107
|
+
if not client.exists("optional_config", "production"):
|
|
1108
|
+
client.create("optional_config", {"data": "value"}, "production")
|
|
969
1109
|
|
|
970
1110
|
# 3. Use get_or_default() for optional configurations
|
|
971
|
-
config = client.get_or_default("optional", {"enabled": False})
|
|
1111
|
+
config = client.get_or_default("optional", "production", {"enabled": False})
|
|
972
1112
|
|
|
973
1113
|
# 4. Handle bulk operation failures
|
|
974
|
-
result = client.bulk_delete(
|
|
1114
|
+
result = client.bulk_delete(items, ignore_errors=True)
|
|
975
1115
|
if result['failed']:
|
|
976
1116
|
print(f"Some deletions failed: {result['failed']}")
|
|
977
1117
|
```
|
|
@@ -1045,45 +1185,71 @@ export OSC_LOG_LEVEL="INFO"
|
|
|
1045
1185
|
```python
|
|
1046
1186
|
# ✅ Good: Automatic cleanup
|
|
1047
1187
|
with OpenSecureConfClient(base_url="...", user_key="...") as client:
|
|
1048
|
-
config = client.create("key", {"data": "value"})
|
|
1188
|
+
config = client.create("key", {"data": "value"}, "production")
|
|
1049
1189
|
|
|
1050
1190
|
# ❌ Avoid: Manual cleanup required
|
|
1051
1191
|
client = OpenSecureConfClient(base_url="...", user_key="...")
|
|
1052
|
-
config = client.create("key", {"data": "value"})
|
|
1192
|
+
config = client.create("key", {"data": "value"}, "production")
|
|
1053
1193
|
client.close() # Easy to forget
|
|
1054
1194
|
```
|
|
1055
1195
|
|
|
1056
|
-
### 2.
|
|
1196
|
+
### 2. Always Specify Environment
|
|
1197
|
+
|
|
1198
|
+
```python
|
|
1199
|
+
# ✅ Good: Explicit environment
|
|
1200
|
+
config = client.create("api_url", "https://api.com", "production")
|
|
1201
|
+
prod_config = client.read("api_url", "production")
|
|
1202
|
+
|
|
1203
|
+
# ❌ Error: Environment is now required
|
|
1204
|
+
# config = client.create("api_url", "https://api.com") # This will fail
|
|
1205
|
+
```
|
|
1206
|
+
|
|
1207
|
+
### 3. Use Multi-Environment Configuration
|
|
1208
|
+
|
|
1209
|
+
```python
|
|
1210
|
+
# ✅ Good: Separate configurations per environment
|
|
1211
|
+
for env in ["production", "staging", "development"]:
|
|
1212
|
+
client.create("database", get_db_config(env), env, "config")
|
|
1213
|
+
|
|
1214
|
+
# ❌ Avoid: Mixing environments in one key
|
|
1215
|
+
client.create("database_prod", prod_config, "production")
|
|
1216
|
+
client.create("database_staging", staging_config, "production")
|
|
1217
|
+
```
|
|
1218
|
+
|
|
1219
|
+
### 4. Check Existence Before Operations
|
|
1057
1220
|
|
|
1058
1221
|
```python
|
|
1059
1222
|
# ✅ Good: Avoid exceptions
|
|
1060
|
-
if not client.exists("config"):
|
|
1061
|
-
client.create("config", {"data": "value"})
|
|
1223
|
+
if not client.exists("config", "production"):
|
|
1224
|
+
client.create("config", {"data": "value"}, "production")
|
|
1062
1225
|
|
|
1063
1226
|
# ❌ Avoid: Exception handling for flow control
|
|
1064
1227
|
try:
|
|
1065
|
-
client.create("config", {"data": "value"})
|
|
1228
|
+
client.create("config", {"data": "value"}, "production")
|
|
1066
1229
|
except ConfigurationExistsError:
|
|
1067
1230
|
pass
|
|
1068
1231
|
```
|
|
1069
1232
|
|
|
1070
|
-
###
|
|
1233
|
+
### 5. Use Batch Operations for Multiple Items
|
|
1071
1234
|
|
|
1072
1235
|
```python
|
|
1073
1236
|
# ✅ Good: Single batch operation
|
|
1074
|
-
|
|
1075
|
-
|
|
1237
|
+
items = [
|
|
1238
|
+
{"key": "config1", "environment": "production"},
|
|
1239
|
+
{"key": "config2", "environment": "production"}
|
|
1240
|
+
]
|
|
1241
|
+
configs = client.bulk_read(items, ignore_errors=True)
|
|
1076
1242
|
|
|
1077
1243
|
# ❌ Avoid: Multiple individual requests
|
|
1078
1244
|
configs = []
|
|
1079
|
-
for
|
|
1245
|
+
for item in items:
|
|
1080
1246
|
try:
|
|
1081
|
-
configs.append(client.read(key))
|
|
1247
|
+
configs.append(client.read(item["key"], item["environment"]))
|
|
1082
1248
|
except:
|
|
1083
1249
|
pass
|
|
1084
1250
|
```
|
|
1085
1251
|
|
|
1086
|
-
###
|
|
1252
|
+
### 6. Enable Retry for Production
|
|
1087
1253
|
|
|
1088
1254
|
```python
|
|
1089
1255
|
# ✅ Good: Resilient to transient failures
|
|
@@ -1102,7 +1268,7 @@ client = OpenSecureConfClient(
|
|
|
1102
1268
|
)
|
|
1103
1269
|
```
|
|
1104
1270
|
|
|
1105
|
-
###
|
|
1271
|
+
### 7. Use Structured Logging
|
|
1106
1272
|
|
|
1107
1273
|
```python
|
|
1108
1274
|
# ✅ Good: Enable logging for production monitoring
|
|
@@ -1120,7 +1286,7 @@ client = OpenSecureConfClient(
|
|
|
1120
1286
|
)
|
|
1121
1287
|
```
|
|
1122
1288
|
|
|
1123
|
-
###
|
|
1289
|
+
### 8. Secure Credential Management
|
|
1124
1290
|
|
|
1125
1291
|
```python
|
|
1126
1292
|
# ✅ Good: Load from environment or secrets manager
|
|
@@ -1141,7 +1307,7 @@ client = OpenSecureConfClient(
|
|
|
1141
1307
|
)
|
|
1142
1308
|
```
|
|
1143
1309
|
|
|
1144
|
-
###
|
|
1310
|
+
### 9. Monitor Cluster Health
|
|
1145
1311
|
|
|
1146
1312
|
```python
|
|
1147
1313
|
# ✅ Good: Regular health checks
|
|
@@ -1152,7 +1318,20 @@ if client.ping():
|
|
|
1152
1318
|
logger.warning(f"Cluster degraded: {status['healthy_nodes']}/{status['total_nodes']}")
|
|
1153
1319
|
|
|
1154
1320
|
# ❌ Avoid: No health monitoring
|
|
1155
|
-
config = client.read("config") # Might fail silently in degraded cluster
|
|
1321
|
+
config = client.read("config", "production") # Might fail silently in degraded cluster
|
|
1322
|
+
```
|
|
1323
|
+
|
|
1324
|
+
### 10. Organize by Environment
|
|
1325
|
+
|
|
1326
|
+
```python
|
|
1327
|
+
# ✅ Good: Clear environment separation
|
|
1328
|
+
environments = client.list_environments()
|
|
1329
|
+
for env in environments:
|
|
1330
|
+
configs = client.list_all(environment=env)
|
|
1331
|
+
print(f"{env}: {len(configs)} configurations")
|
|
1332
|
+
|
|
1333
|
+
# ❌ Avoid: Mixing all environments
|
|
1334
|
+
all_configs = client.list_all() # Hard to manage
|
|
1156
1335
|
```
|
|
1157
1336
|
|
|
1158
1337
|
## 🔨 Development
|
|
@@ -1284,6 +1463,6 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|
|
1284
1463
|
- Encryption powered by [cryptography](https://cryptography.io/)
|
|
1285
1464
|
- HTTP client using [requests](https://requests.readthedocs.io/)
|
|
1286
1465
|
|
|
1287
|
-
|
|
1466
|
+
***
|
|
1288
1467
|
|
|
1289
1468
|
**Made with ❤️ by the OpenSecureConf Team**
|