Coverage for src / lexigram / admin / config.py: 97%

278 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-08-13 22:14 +0800

1"""Admin configuration models and factory helpers.""" 

2 

3from __future__ import annotations 

4 

5from dataclasses import dataclass 

6from typing import Any, ClassVar, Literal, cast 

7 

8from lexigram.admin.constants import ENV_NESTED_DELIMITER, ENV_PREFIX 

9from lexigram.admin.resources.config import ResourceConfig, TableConfiguration 

10from lexigram.config import BaseConfig 

11from lexigram.domain import DomainModel 

12from lexigram.validation import ConfigDict, Field, model_validator 

13 

14 

15@dataclass(init=False) 

16class ResourceDefaults(DomainModel): 

17 """Default configuration for all resources.""" 

18 

19 per_page: int = Field(default=20, ge=1, le=1000) 

20 enable_search: bool = Field(default=True) 

21 enable_export: bool = Field(default=True) 

22 enable_bulk_actions: bool = Field(default=True) 

23 action_layout: Literal["horizontal", "vertical", "dropdown"] = Field( 

24 default="horizontal", 

25 ) 

26 soft_delete: bool = Field(default=True) 

27 timestamp_fields: bool = Field(default=True) 

28 

29 model_config = {"extra": "forbid"} 

30 

31 

32@dataclass(init=False) 

33class TableDefaults(DomainModel): 

34 """Default configuration for DataTables.""" 

35 

36 reorderable_columns: bool = Field(default=False) 

37 enable_column_visibility: bool = Field(default=True) 

38 sticky_header: bool = Field(default=True) 

39 virtualized: bool = Field(default=False) 

40 row_height: int = Field(default=48, ge=24, le=120) 

41 zebra_stripes: bool = Field(default=True) 

42 hover_highlight: bool = Field(default=True) 

43 

44 model_config = {"extra": "forbid"} 

45 

46 

47@dataclass(init=False) 

48class FormDefaults(DomainModel): 

49 """Default configuration for Forms.""" 

50 

51 show_required_indicator: bool = Field(default=True) 

52 autosave_enabled: bool = Field(default=False) 

53 autosave_interval_ms: int = Field(default=30000, ge=1000) 

54 confirm_unsaved_changes: bool = Field(default=True) 

55 inline_validation: bool = Field(default=True) 

56 

57 model_config = {"extra": "forbid"} 

58 

59 

60@dataclass(init=False) 

61class ResourceYAMLConfig(DomainModel): 

62 """Per-resource configuration from YAML.""" 

63 

64 enabled: bool = Field(default=True) 

65 icon: str | None = None 

66 label: str | None = None 

67 label_plural: str | None = None 

68 per_page: int | None = Field(default=None, ge=1, le=1000) 

69 searchable_fields: list[str] | None = None 

70 default_sort: str | None = None 

71 default_sort_order: Literal["asc", "desc"] = "desc" 

72 permissions: dict[str, list[str]] | None = None 

73 

74 model_config = {"extra": "allow"} 

75 

76 

77@dataclass(init=False) 

78class AdminNavigationGroup(DomainModel): 

79 """Navigation group configuration.""" 

80 

81 label: str 

82 icon: str | None = None 

83 order: int = Field(default=100, ge=0) 

84 resources: list[str] = Field(default_factory=list) 

85 permission: str | None = None 

86 collapsible: bool = Field(default=True) 

87 collapsed_by_default: bool = Field(default=False) 

88 

89 model_config = {"extra": "forbid"} 

90 

91 

92@dataclass(init=False) 

93class AdminFeaturesConfig(DomainModel): 

94 """Feature flags for admin functionality.""" 

95 

96 # UI Features 

97 command_palette: bool = Field(default=True) 

98 keyboard_shortcuts: bool = Field(default=True) 

99 theme_toggle: bool = Field(default=True) 

100 search: bool = Field(default=True) 

101 

102 # UX Features 

103 optimistic_updates: bool = Field(default=True) 

104 undo_redo: bool = Field(default=True) 

105 autosave: bool = Field(default=False) 

106 

107 # Advanced Features 

108 audit_logging: bool = Field(default=True) 

109 activity_feed: bool = Field(default=False) 

110 notifications: bool = Field(default=True) 

111 webhooks: bool = Field(default=False) 

112 api_docs: bool = Field(default=True) 

113 

114 model_config = {"extra": "allow"} 

115 

116 

117@dataclass(init=False) 

118class AdminPasswordPolicyConfig(DomainModel): 

119 """Password policy configuration for admin authentication. 

120 

121 Follows NIST SP 800-63B guidelines. 

122 """ 

123 

124 min_length: int = Field(default=12, ge=8, le=128) 

125 max_length: int = Field(default=128, ge=32, le=1024) 

126 require_uppercase: bool = Field(default=True) 

127 require_lowercase: bool = Field(default=True) 

128 require_digit: bool = Field(default=True) 

129 require_special: bool = Field(default=True) 

130 reject_common_passwords: bool = Field(default=True) 

131 reject_containing_email: bool = Field(default=True) 

132 

133 model_config = {"extra": "forbid"} 

134 

135 

136@dataclass(init=False) 

137class AdminSecurityConfig(DomainModel): 

138 """Security hardening configuration for admin authentication. 

139 

140 Controls rate limiting, progressive lockout, and setup token protection. 

141 """ 

142 

143 ip_rate_limit_enabled: bool = Field(default=True) 

144 ip_rate_limit_per_minute: int = Field(default=10, ge=1) 

145 ip_rate_limit_per_15_minutes: int = Field(default=30, ge=1) 

146 ip_rate_limit_per_hour: int = Field(default=60, ge=1) 

147 

148 # Progressive lockout thresholds: list of (failure_count, lockout_minutes) 

149 # e.g. [(5, 15), (10, 60), (15, 240), (20, 1440)] means: 

150 # 5 failures → 15 min lockout, 10 → 1hr, 15 → 4hr, 20 → 24hr 

151 lockout_thresholds: list[tuple[int, int]] = Field( 

152 default_factory=lambda: [(5, 15), (10, 60), (15, 240), (20, 1440)], 

153 ) 

154 permanent_lockout_threshold: int = Field(default=50, ge=10) 

155 

156 setup_token: str | None = Field( 

157 default=None, 

158 description="Optional ADMIN_SETUP_TOKEN — when set, must be provided during first-run setup.", 

159 ) 

160 

161 model_config = {"extra": "forbid"} 

162 

163 

164@dataclass(init=False) 

165class AdminAuthConfig(DomainModel): 

166 """Authentication configuration.""" 

167 

168 enabled: bool = Field(default=True, description="Enable authentication") 

169 env: Literal["development", "staging", "production"] = Field( 

170 default="development", 

171 description="Deployment environment for cookie security defaults", 

172 ) 

173 session_secret: str = Field( 

174 default="change-me-in-production", 

175 description="Session secret for signing", 

176 ) 

177 login_url: str = Field(default="/admin/login") 

178 logout_url: str = Field(default="/admin/logout") 

179 session_lifetime: int = Field(default=86400, ge=300) # 5 min minimum 

180 permission_cache_ttl: int = Field(default=300, ge=0) # 5 minutes 

181 

182 # Security settings 

183 idle_timeout: int = Field( 

184 default=3600, ge=60, description="Session idle timeout in seconds" 

185 ) 

186 csrf_token_lifetime: int = Field( 

187 default=3600, ge=60, description="CSRF token expiry in seconds" 

188 ) 

189 password_policy: AdminPasswordPolicyConfig = Field( 

190 default_factory=AdminPasswordPolicyConfig, 

191 ) 

192 security: AdminSecurityConfig = Field( 

193 default_factory=AdminSecurityConfig, 

194 ) 

195 

196 # Users and Roles (Sync) 

197 users: list[Any] = Field(default_factory=list) 

198 roles: dict[str, Any] = Field(default_factory=dict) 

199 

200 # OAuth/SSO (optional) 

201 oauth_enabled: bool = Field(default=False) 

202 oauth_providers: list[str] = Field(default_factory=list) 

203 

204 model_config = {"extra": "allow"} 

205 

206 @model_validator(mode="after") 

207 def validate_security(self) -> AdminAuthConfig: 

208 """Ensure secure settings in production.""" 

209 insecure_defaults = ( 

210 "change-me", 

211 "your-secret-key", 

212 "secret", 

213 "password", 

214 "change-me-in-production", 

215 ) 

216 

217 if ( 

218 self.env == "production" 

219 and self.session_secret.lower() in insecure_defaults 

220 ): 

221 raise ValueError( 

222 "CRITICAL SECURITY ERROR: Default admin session_secret detected in PRODUCTION.\n" 

223 "You MUST set a secure session secret via LEX_ADMIN__AUTH__SESSION_SECRET.", 

224 ) 

225 

226 if ( 

227 self.env in {"production", "staging"} 

228 and self.oauth_enabled 

229 and not self.oauth_providers 

230 ): 

231 raise ValueError( 

232 "oauth_providers must be configured when oauth_enabled=True" 

233 ) 

234 

235 if ( 

236 self.env in {"production", "staging"} 

237 and self.csrf_token_lifetime > self.idle_timeout 

238 ): 

239 raise ValueError("csrf_token_lifetime must not exceed idle_timeout") 

240 

241 return self 

242 

243 

244@dataclass(init=False) 

245class AdminAuditConfig(DomainModel): 

246 """Audit logging configuration.""" 

247 

248 read_audit_enabled: bool = Field( 

249 default=False, 

250 description="Log read operations (off by default; compliance mode only).", 

251 ) 

252 redaction_field_denylist: tuple[str, ...] = Field( 

253 default_factory=lambda: ("email", "phone", "password_hash", "ssn"), 

254 description="Field names whose values are always redacted in audit payloads.", 

255 ) 

256 redaction_patterns: tuple[str, ...] = Field( 

257 default_factory=lambda: ("email", "phone"), 

258 description="Pattern-based redaction strategies to apply.", 

259 ) 

260 

261 model_config = {"extra": "forbid"} 

262 

263 

264@dataclass(init=False) 

265class AdminUIConfig(DomainModel): 

266 """UI/Theme configuration.""" 

267 

268 theme: Literal["light", "dark", "system"] = Field(default="system") 

269 primary_color: str = Field(default="#6B7280") 

270 sidebar_width: int = Field(default=256, ge=200, le=400) 

271 sidebar_collapsed_width: int = Field(default=64, ge=48, le=100) 

272 content_max_width: int | None = Field(default=None, ge=800) 

273 logo_url: str | None = None 

274 favicon_url: str | None = None 

275 

276 model_config = {"extra": "allow"} 

277 

278 

279@dataclass(init=False) 

280class AdminRateLimitConfig(DomainModel): 

281 """Rate limiting configuration.""" 

282 

283 enabled: bool = Field(default=True) 

284 requests_per_minute: int = Field(default=60, ge=1) 

285 requests_per_hour: int = Field(default=1000, ge=1) 

286 burst_size: int = Field(default=10, ge=1) 

287 

288 # Per-action limits 

289 create_per_minute: int = Field(default=30) 

290 update_per_minute: int = Field(default=60) 

291 delete_per_minute: int = Field(default=20) 

292 bulk_per_minute: int = Field(default=5) 

293 

294 model_config = {"extra": "forbid"} 

295 

296 

297@dataclass(init=False) 

298class ContributorConfig(DomainModel): 

299 """Per-contributor enable/disable and options.""" 

300 

301 enabled: bool = Field(default=True) 

302 options: dict[str, Any] = Field(default_factory=dict) 

303 

304 

305@dataclass(init=False) 

306class DashboardLayoutConfig(DomainModel): 

307 """Dashboard layout configuration.""" 

308 

309 widget_refresh_default: int = Field(default=30) 

310 max_widgets: int = Field(default=20) 

311 layout: Literal["grid", "masonry"] = Field(default="grid") 

312 

313 

314@dataclass(init=False) 

315class TenancyConfig(DomainModel): 

316 """Multi-tenancy configuration for admin. 

317 

318 Controls tenant resolution, data isolation, and route scoping. 

319 

320 Attributes: 

321 enabled: Enable multi-tenancy support. 

322 tenant_field: Field name used for tenant data filtering (default ``"tenant_id"``). 

323 header_name: HTTP header name for tenant ID resolution (default ``"x-tenant-id"``). 

324 cookie_name: Cookie name for tenant ID resolution (default ``"admin_tenant"``). 

325 default_tenant_id: Fallback tenant ID when none can be resolved. 

326 route_prefix_template: If set (e.g. ``"{tenant}"``), routes are prefixed 

327 with the resolved tenant ID. Empty means no route prefix. 

328 """ 

329 

330 enabled: bool = Field(default=False) 

331 tenant_field: str = Field(default="tenant_id") 

332 header_name: str = Field(default="x-tenant-id") 

333 cookie_name: str = Field(default="admin_tenant") 

334 default_tenant_id: str = Field(default="") 

335 route_prefix_template: str = Field(default="") 

336 

337 model_config = {"extra": "forbid"} 

338 

339 

340# --------------------------------------------------------------------------- 

341# Data & Observability configs 

342# --------------------------------------------------------------------------- 

343 

344 

345@dataclass(init=False) 

346class AdminDataConfig(DomainModel): 

347 query_timeout_seconds: int = Field(default=5, ge=1, le=120) 

348 

349 

350@dataclass(init=False) 

351class AdminObservabilityConfig(DomainModel): 

352 metrics_enabled: bool = Field(default=True) 

353 high_cardinality_labels_enabled: bool = Field(default=False) 

354 

355 

356# --------------------------------------------------------------------------- 

357# Integration configs 

358# --------------------------------------------------------------------------- 

359 

360 

361@dataclass(init=False) 

362class CacheIntegrationConfig(DomainModel): 

363 """Configuration for the optional cache integration. 

364 

365 Controls ``lexigram-cache`` usage from admin resources. 

366 """ 

367 

368 enabled: bool = Field(default=True) 

369 default_ttl_seconds: int = Field(default=60, ge=1) 

370 key_prefix: str = Field(default="admin") 

371 

372 

373@dataclass(init=False) 

374class TasksIntegrationConfig(DomainModel): 

375 """Configuration for the optional tasks integration. 

376 

377 Controls ``lexigram-tasks`` usage from admin bulk actions. 

378 """ 

379 

380 enabled: bool = Field(default=True) 

381 bulk_threshold: int = Field(default=25, ge=1) 

382 

383 

384@dataclass(init=False) 

385class SearchIntegrationConfig(DomainModel): 

386 """Configuration for the optional search integration.""" 

387 

388 enabled: bool = Field(default=True) 

389 fallback_to_like: bool = Field(default=True) 

390 

391 

392@dataclass(init=False) 

393class ResilienceIntegrationConfig(DomainModel): 

394 """Configuration for the optional resilience integration.""" 

395 

396 enabled: bool = Field(default=True) 

397 retry_max_attempts: int = Field(default=3, ge=1) 

398 circuit_failure_threshold: int = Field(default=5, ge=1) 

399 

400 

401@dataclass(init=False) 

402class StorageIntegrationConfig(DomainModel): 

403 """Configuration for the optional storage integration.""" 

404 

405 enabled: bool = Field(default=True) 

406 presigned_url_expiry: int = Field(default=3600, ge=60) 

407 

408 

409@dataclass(init=False) 

410class FeaturesIntegrationConfig(DomainModel): 

411 """Configuration for the optional feature-flags integration.""" 

412 

413 enabled: bool = Field(default=True) 

414 

415 

416@dataclass(init=False) 

417class MonitorIntegrationConfig(DomainModel): 

418 """Configuration for the optional monitoring integration.""" 

419 

420 enabled: bool = Field(default=True) 

421 

422 

423@dataclass(init=False) 

424class AdminIntegrationsConfig(DomainModel): 

425 """Aggregate configuration for all optional integrations.""" 

426 

427 cache: CacheIntegrationConfig = Field(default_factory=CacheIntegrationConfig) 

428 tasks: TasksIntegrationConfig = Field(default_factory=TasksIntegrationConfig) 

429 search: SearchIntegrationConfig = Field(default_factory=SearchIntegrationConfig) 

430 resilience: ResilienceIntegrationConfig = Field( 

431 default_factory=ResilienceIntegrationConfig, 

432 ) 

433 storage: StorageIntegrationConfig = Field( 

434 default_factory=StorageIntegrationConfig, 

435 ) 

436 features: FeaturesIntegrationConfig = Field( 

437 default_factory=FeaturesIntegrationConfig, 

438 ) 

439 monitor: MonitorIntegrationConfig = Field(default_factory=MonitorIntegrationConfig) 

440 enabled: bool = Field(default=True) 

441 

442 model_config = {"extra": "forbid"} 

443 

444 

445@dataclass(init=False) 

446class FrameworkPagesConfig(DomainModel): 

447 """Framework management pages configuration.""" 

448 

449 enabled: bool = Field(default=True) 

450 require_permission: str = Field(default="admin:framework:access") 

451 

452 

453@dataclass(init=False) 

454class AdminConfig(BaseConfig): 

455 """Complete admin configuration - SINGLE SOURCE OF TRUTH. 

456 

457 This model represents the full configuration hierarchy for lexigram-admin. 

458 Configuration is loaded from: 

459 1. Pydantic defaults (this model) 

460 2. application.yaml (admin: section) 

461 3. Environment variables (LEX_ADMIN_*) 

462 4. Runtime config (hot-reloadable) 

463 

464 Attributes: 

465 name: Configuration name (default: "admin") 

466 enabled: Whether admin module is enabled 

467 title: Admin UI title 

468 prefix: Admin URL prefix 

469 htmx_prefix: HTMX endpoint prefix 

470 api_prefix: API endpoint prefix 

471 static_prefix: Static files prefix 

472 require_auth: Require authentication 

473 debug: Debug mode 

474 templates_dir: Templates directory path 

475 static_dir: Static files directory path 

476 auth: Authentication settings 

477 features: Feature toggles 

478 """ 

479 

480 model_config = cast( 

481 "ConfigDict", 

482 { 

483 "env_prefix": ENV_PREFIX, 

484 "env_nested_delimiter": ENV_NESTED_DELIMITER, 

485 "extra": "ignore", 

486 }, 

487 ) 

488 

489 config_section: ClassVar[str] = "admin" 

490 

491 # Section identifier (used by config discovery) 

492 name: str = "admin" 

493 enabled: bool = True 

494 

495 # Core Settings 

496 title: str = Field(default="Lexigram Admin") 

497 prefix: str = Field(default="/admin") 

498 htmx_prefix: str = Field(default="/admin/htmx") 

499 api_prefix: str = Field(default="/admin/api") 

500 static_prefix: str = Field(default="/admin/static") 

501 

502 # Security 

503 require_auth: bool = Field(default=True) 

504 debug: bool = Field(default=False) 

505 

506 # Paths 

507 templates_dir: str | None = Field(default=None) 

508 static_dir: str | None = Field(default=None) 

509 

510 # Sub-configs 

511 auth: AdminAuthConfig = Field(default_factory=AdminAuthConfig) 

512 features: AdminFeaturesConfig = Field(default_factory=AdminFeaturesConfig) 

513 data: AdminDataConfig = Field(default_factory=AdminDataConfig) 

514 observability: AdminObservabilityConfig = Field( 

515 default_factory=AdminObservabilityConfig 

516 ) 

517 

518 ui: AdminUIConfig = Field(default_factory=AdminUIConfig) 

519 rate_limit: AdminRateLimitConfig = Field(default_factory=AdminRateLimitConfig) 

520 resource_defaults: ResourceDefaults = Field(default_factory=ResourceDefaults) 

521 table_defaults: TableDefaults = Field(default_factory=TableDefaults) 

522 form_defaults: FormDefaults = Field(default_factory=FormDefaults) 

523 

524 # Resource & Navigation 

525 resources: dict[str, ResourceYAMLConfig] = Field(default_factory=dict) 

526 navigation_groups: dict[str, AdminNavigationGroup] = Field(default_factory=dict) 

527 commands: list[dict[str, Any]] = Field(default_factory=list) 

528 extensions: dict[str, Any] = Field(default_factory=dict) 

529 

530 # Audit 

531 audit: AdminAuditConfig = Field(default_factory=AdminAuditConfig) 

532 

533 # Multi-tenancy 

534 tenancy: TenancyConfig = Field(default_factory=TenancyConfig) 

535 

536 # Optional integrations 

537 integrations: AdminIntegrationsConfig = Field( 

538 default_factory=AdminIntegrationsConfig, 

539 ) 

540 

541 # Contributor system 

542 contributors: dict[str, ContributorConfig] = Field(default_factory=dict) 

543 dashboard_layout: DashboardLayoutConfig = Field( 

544 default_factory=DashboardLayoutConfig 

545 ) 

546 framework_pages: FrameworkPagesConfig = Field(default_factory=FrameworkPagesConfig) 

547 

548 contributor_collision_mode: Literal["warn", "error"] = Field( 

549 default="warn", 

550 description=( 

551 "How to handle name collisions when multiple contributors register " 

552 "widgets, pages, or routes with the same name. 'warn' (default) " 

553 "logs a warning and keeps the first registration; 'error' raises " 

554 "at boot time." 

555 ), 

556 ) 

557 

558 strict_resource_resolution: bool = Field( 

559 default=True, 

560 description=( 

561 "When True (production default), resource/controller resolution " 

562 "failures during AdminProvider.boot() raise immediately. " 

563 "When False, failures are logged and resolution continues with " 

564 "the remaining resources/controllers. Set to False in dev only." 

565 ), 

566 ) 

567 

568 def get_resource_config(self, name: str) -> ResourceYAMLConfig: 

569 """Get resource config with fallback to defaults.""" 

570 return self.resources.get(name, ResourceYAMLConfig()) 

571 

572 def get_navigation_group(self, name: str) -> AdminNavigationGroup | None: 

573 """Get navigation group by name.""" 

574 return self.navigation_groups.get(name) 

575 

576 def is_feature_enabled(self, feature: str) -> bool: 

577 """Check if a feature is enabled.""" 

578 return getattr(self.features, feature, False) 

579 

580 

581@dataclass(init=False) 

582class AdminStorageConfig(DomainModel): 

583 """Configuration for admin file storage service.""" 

584 

585 base_path: str = Field( 

586 default="uploads", description="Base path for uploaded files" 

587 ) 

588 max_file_size: int = Field( 

589 default=10 * 1024 * 1024, 

590 description="Maximum allowed file size in bytes (default 10 MB)", 

591 ) 

592 allowed_content_types: list[str] = Field( 

593 default_factory=lambda: [ 

594 "image/jpeg", 

595 "image/png", 

596 "image/gif", 

597 "image/webp", 

598 "application/pdf", 

599 "text/plain", 

600 "text/csv", 

601 ], 

602 description="Allowed MIME content types for uploads", 

603 ) 

604 presigned_url_expiry: int = Field( 

605 default=3600, 

606 description="Default expiry for presigned URLs in seconds", 

607 ) 

608 

609 

610@dataclass(init=False) 

611class AdminNotificationConfig(DomainModel): 

612 """Configuration for admin notification service. 

613 

614 Controls email notification behaviour, delivery channels, and 

615 retry settings. 

616 """ 

617 

618 email_from: str = Field( 

619 default="admin@localhost", description="Sender email address" 

620 ) 

621 email_from_name: str = Field( 

622 default="Admin Panel", description="Sender display name" 

623 ) 

624 default_channel: str = Field( 

625 default="email", description="Default delivery channel" 

626 ) 

627 max_retries: int = Field(default=3, description="Maximum delivery retry attempts") 

628 retry_delay_seconds: int = Field(default=60, description="Seconds between retries") 

629 enabled: bool = Field(default=True, description="Enable notification sending") 

630 

631 

632def make_admin_config(**kwargs: Any) -> AdminConfig: 

633 """Helper to create admin config from kwargs.""" 

634 return AdminConfig(**kwargs) 

635 

636 

637def default_admin_config() -> AdminConfig: 

638 """Factory for default admin configuration.""" 

639 return AdminConfig() 

640 

641 

642__all__ = [ 

643 "AdminAuditConfig", 

644 "AdminAuthConfig", 

645 "AdminConfig", 

646 "AdminDataConfig", 

647 "AdminFeaturesConfig", 

648 "AdminIntegrationsConfig", 

649 "AdminNavigationGroup", 

650 "AdminNotificationConfig", 

651 "AdminObservabilityConfig", 

652 "AdminPasswordPolicyConfig", 

653 "AdminRateLimitConfig", 

654 "AdminSecurityConfig", 

655 "AdminStorageConfig", 

656 "AdminUIConfig", 

657 "CacheIntegrationConfig", 

658 "ContributorConfig", 

659 "DashboardLayoutConfig", 

660 "FeaturesIntegrationConfig", 

661 "FormDefaults", 

662 "FrameworkPagesConfig", 

663 "MonitorIntegrationConfig", 

664 "ResilienceIntegrationConfig", 

665 "ResourceConfig", 

666 "ResourceDefaults", 

667 "ResourceYAMLConfig", 

668 "SearchIntegrationConfig", 

669 "StorageIntegrationConfig", 

670 "TableConfiguration", 

671 "TableDefaults", 

672 "TasksIntegrationConfig", 

673 "TenancyConfig", 

674 "default_admin_config", 

675 "make_admin_config", 

676]