|
24 | 24 | validate_feature_flag_key, |
25 | 25 | is_http_endpoint) |
26 | 26 | from ._models import QueryFields |
27 | | -from ._constants import ImportExportProfiles |
| 27 | +from ._constants import ImportExportProfiles, FeatureFlagConstants |
28 | 28 | from ._featuremodels import FeatureQueryFields |
29 | 29 | from ._snapshotmodels import SnapshotQueryFields |
30 | 30 |
|
@@ -330,6 +330,72 @@ def validate_feature(namespace): |
330 | 330 | validate_feature_flag_name(namespace.feature) |
331 | 331 |
|
332 | 332 |
|
| 333 | +# Properties allowed in the enhanced feature flag input accepted by --flag. Matches the schema |
| 334 | +# emitted by 'az appconfig enhanced-feature-flag show' and the data-plane /ff API, so a flag can be |
| 335 | +# shown, edited and set back. |
| 336 | +FEATURE_FLAG_PROPERTIES = { |
| 337 | + FeatureFlagConstants.NAME, |
| 338 | + FeatureFlagConstants.ENABLED, |
| 339 | + FeatureFlagConstants.LABEL, |
| 340 | + FeatureFlagConstants.DESCRIPTION, |
| 341 | + FeatureFlagConstants.CONDITIONS, |
| 342 | + FeatureFlagConstants.ALLOCATION, |
| 343 | + FeatureFlagConstants.VARIANTS, |
| 344 | + FeatureFlagConstants.TELEMETRY, |
| 345 | + FeatureFlagConstants.TAGS, |
| 346 | +} |
| 347 | + |
| 348 | +# Read-only properties emitted by 'show' that are tolerated (and ignored) on input. |
| 349 | +FEATURE_FLAG_READONLY_PROPERTIES = { |
| 350 | + FeatureFlagConstants.LAST_MODIFIED, |
| 351 | + "etag", |
| 352 | +} |
| 353 | + |
| 354 | + |
| 355 | +def validate_feature_flag(namespace): |
| 356 | + if getattr(namespace, 'flag', None) is None: |
| 357 | + return |
| 358 | + |
| 359 | + # --flag is a full-object replacement; it cannot be mixed with the per-property content flags. |
| 360 | + conflicting = { |
| 361 | + '--enable': namespace.enable, |
| 362 | + '--description': namespace.description, |
| 363 | + '--requirement-type': namespace.requirement_type, |
| 364 | + '--telemetry-enabled': namespace.telemetry_enabled, |
| 365 | + } |
| 366 | + specified = [option for option, value in conflicting.items() if value is not None] |
| 367 | + if specified: |
| 368 | + raise MutuallyExclusiveArgumentError( |
| 369 | + "--flag cannot be combined with {}. Provide the entire feature flag with --flag, " |
| 370 | + "or set individual properties without --flag.".format(", ".join(specified))) |
| 371 | + |
| 372 | + flag = namespace.flag |
| 373 | + if not isinstance(flag, dict): |
| 374 | + raise InvalidArgumentValueError( |
| 375 | + "--flag must be a JSON object or shorthand-syntax object representing a single feature flag.") |
| 376 | + |
| 377 | + allowed = FEATURE_FLAG_PROPERTIES | FEATURE_FLAG_READONLY_PROPERTIES |
| 378 | + unknown_keys = set(flag.keys()) - allowed |
| 379 | + if unknown_keys: |
| 380 | + raise InvalidArgumentValueError( |
| 381 | + "Unsupported feature flag propert{}: {}. Allowed properties: {}.".format( |
| 382 | + "y" if len(unknown_keys) == 1 else "ies", |
| 383 | + ", ".join(sorted(unknown_keys)), |
| 384 | + ", ".join(sorted(FEATURE_FLAG_PROPERTIES)))) |
| 385 | + |
| 386 | + flag_name = flag.get(FeatureFlagConstants.NAME) |
| 387 | + if namespace.feature is None and flag_name is None: |
| 388 | + raise RequiredArgumentMissingError( |
| 389 | + "The feature flag 'name' is required. Provide it inside --flag or via --feature-name.") |
| 390 | + |
| 391 | + if namespace.feature is not None and flag_name is not None and namespace.feature != flag_name: |
| 392 | + raise MutuallyExclusiveArgumentError( |
| 393 | + "Feature name mismatch: --feature-name '{}' does not match the 'name' '{}' in --flag. " |
| 394 | + "Provide only one, or make them identical.".format(namespace.feature, flag_name)) |
| 395 | + |
| 396 | + validate_feature_flag_name(flag_name if flag_name is not None else namespace.feature) |
| 397 | + |
| 398 | + |
333 | 399 | def validate_feature_key(namespace): |
334 | 400 | if namespace.key is not None: |
335 | 401 | validate_feature_flag_key(namespace.key) |
|
0 commit comments