Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[App Config] BREAKING CHANGE: az appconfig feature: Support namespacing for feature flags and change output fields #18990

Merged
merged 5 commits into from
Jul 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions scripts/ci/credscan/CredScanSuppressions.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"src\\azure-cli\\azure\\cli\\command_modules\\appconfig\\tests\\latest\\recordings\\test_azconfig_local_auth.yaml",
"src\\azure-cli\\azure\\cli\\command_modules\\appconfig\\tests\\latest\\recordings\\test_azconfig_credential.yaml",
"src\\azure-cli\\azure\\cli\\command_modules\\appconfig\\tests\\latest\\recordings\\test_azconfig_feature.yaml",
"src\\azure-cli\\azure\\cli\\command_modules\\appconfig\\tests\\latest\\recordings\\test_azconfig_feature_namespacing.yaml",
"src\\azure-cli\\azure\\cli\\command_modules\\appconfig\\tests\\latest\\recordings\\test_azconfig_feature_filter.yaml",
"src\\azure-cli\\azure\\cli\\command_modules\\appconfig\\tests\\latest\\recordings\\test_azconfig_import_export_naming_conventions.yaml",
"src\\azure-cli\\azure\\cli\\command_modules\\appconfig\\tests\\latest\\recordings\\test_azconfig_import_export.yaml",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,21 @@ class FeatureState(Enum):
class FeatureQueryFields(Enum):
KEY = 0x001
LABEL = 0x002
NAME = 0x004
LAST_MODIFIED = 0x020
LOCKED = 0x040
STATE = 0x100
DESCRIPTION = 0x200
CONDITIONS = 0x400
ALL = KEY | LABEL | LAST_MODIFIED | LOCKED | STATE | DESCRIPTION | CONDITIONS
ALL = KEY | LABEL | NAME | LAST_MODIFIED | LOCKED | STATE | DESCRIPTION | CONDITIONS


class FeatureFlagValue:
'''
Schema of Value inside KeyValue when key is a Feature Flag.

:ivar str id:
ID (key) of the feature.
ID (name) of the feature.
:ivar str description:
Description of Feature Flag
:ivar bool enabled:
Expand All @@ -57,7 +58,7 @@ def __init__(self,
default_conditions = {'client_filters': []}

self.id = id_
self.description = description
self.description = "" if description is None else description
self.enabled = enabled if enabled else False
self.conditions = conditions if conditions else default_conditions

Expand All @@ -76,10 +77,12 @@ class FeatureFlag:
'''
Feature Flag schema as displayed to the user.

:ivar str name:
Name (ID) of the feature flag.
avanigupta marked this conversation as resolved.
Show resolved Hide resolved
:ivar str key:
FeatureName (key) of the entry.
Key of the feature flag.
:ivar str label:
Label of the entry.
Label of the feature flag.
:ivar str state:
Represents if the Feature flag is On/Off/Conditionally On
:ivar str description:
Expand All @@ -95,13 +98,15 @@ class FeatureFlag:
'''

def __init__(self,
key,
name,
key=None,
label=None,
state=None,
description=None,
conditions=None,
locked=None,
last_modified=None):
self.name = name
self.key = key
self.label = label
self.state = state.name.lower()
Expand All @@ -112,6 +117,7 @@ def __init__(self,

def __repr__(self):
featureflag = {
"Feature Name": self.name,
"Key": self.key,
"Label": self.label,
"State": self.state,
Expand Down Expand Up @@ -185,12 +191,12 @@ def map_featureflag_to_keyvalue(featureflag):
if featureflag.state in ("on", "conditional"):
enabled = True

feature_flag_value = FeatureFlagValue(id_=featureflag.key,
feature_flag_value = FeatureFlagValue(id_=featureflag.name,
description=featureflag.description,
enabled=enabled,
conditions=featureflag.conditions)

set_kv = KeyValue(key=FeatureFlagConstants.FEATURE_FLAG_PREFIX + featureflag.key,
set_kv = KeyValue(key=featureflag.key,
label=featureflag.label,
value=json.dumps(feature_flag_value,
default=lambda o: o.__dict__,
Expand Down Expand Up @@ -223,8 +229,8 @@ def map_keyvalue_to_featureflag(keyvalue, show_conditions=True):
Return:
FeatureFlag object
'''
feature_name = keyvalue.key[len(FeatureFlagConstants.FEATURE_FLAG_PREFIX):]
feature_flag_value = map_keyvalue_to_featureflagvalue(keyvalue)
feature_name = feature_flag_value.id
state = FeatureState.OFF
if feature_flag_value.enabled:
state = FeatureState.ON
Expand All @@ -238,6 +244,7 @@ def map_keyvalue_to_featureflag(keyvalue, show_conditions=True):
state = FeatureState.CONDITIONAL

feature_flag = FeatureFlag(feature_name,
keyvalue.key,
keyvalue.label,
state,
feature_flag_value.description,
Expand Down Expand Up @@ -269,7 +276,6 @@ def map_keyvalue_to_featureflagvalue(keyvalue):
try:
# Make sure value string is a valid json
feature_flag_dict = shell_safe_json_parse(keyvalue.value)
feature_name = keyvalue.key[len(FeatureFlagConstants.FEATURE_FLAG_PREFIX):]

# Make sure value json has all the fields we support in the backend
valid_fields = {
Expand All @@ -278,9 +284,13 @@ def map_keyvalue_to_featureflagvalue(keyvalue):
'enabled',
'conditions'}
if valid_fields != feature_flag_dict.keys():
logger.debug("'%s' feature flag is missing required values or it contains ", feature_name +
logger.debug("'%s' feature flag is missing required values or it contains ", keyvalue.key +
"unsupported values. Setting missing value to defaults and ignoring unsupported values\n")

feature_name = feature_flag_dict.get('id', '')
if not feature_name:
raise ValueError("Feature flag 'id' cannot be empty.")

conditions = feature_flag_dict.get('conditions', None)
if conditions:
client_filters = conditions.get('client_filters', [])
Expand All @@ -300,10 +310,8 @@ def map_keyvalue_to_featureflagvalue(keyvalue):
conditions['client_filters'] = client_filters_list

feature_flag_value = FeatureFlagValue(id_=feature_name,
description=feature_flag_dict.get(
'description', ''),
enabled=feature_flag_dict.get(
'enabled', False),
description=feature_flag_dict.get('description', ''),
enabled=feature_flag_dict.get('enabled', False),
conditions=conditions)

except ValueError as exception:
Expand Down
26 changes: 25 additions & 1 deletion src/azure-cli/azure/cli/command_modules/appconfig/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,9 @@
- name: Set a feature flag using your 'az login' credentials.
text:
az appconfig feature set --endpoint https://myappconfiguration.azconfig.io --feature color --label MyLabel --auth-mode login
- name: Set a feature flag with name "Beta" and custom key ".appconfig.featureflag/MyApp1:Beta".
text:
az appconfig feature set -n MyAppConfiguration --feature Beta --key .appconfig.featureflag/MyApp1:Beta
"""

helps['appconfig feature delete'] = """
Expand All @@ -320,6 +323,9 @@
- name: Delete a feature using App Configuration endpoint and your 'az login' credentials.
text:
az appconfig feature delete --endpoint https://myappconfiguration.azconfig.io --feature color --auth-mode login
- name: Delete a feature whose name is "Beta" but key is ".appconfig.featureflag/MyApp1:Beta".
text:
az appconfig feature delete -n MyAppConfiguration --key .appconfig.featureflag/MyApp1:Beta --yes
"""

helps['appconfig feature show'] = """
Expand All @@ -335,6 +341,9 @@
- name: Show a feature flag using App Configuration endpoint and your 'az login' credentials.
text:
az appconfig feature show --endpoint https://myappconfiguration.azconfig.io --feature color --auth-mode login
- name: Show a feature whose name is "Beta" but key is ".appconfig.featureflag/MyApp1:Beta".
text:
az appconfig feature show -n MyAppConfiguration --key .appconfig.featureflag/MyApp1:Beta
"""

helps['appconfig feature list'] = """
Expand All @@ -347,7 +356,7 @@
- name: List all feature flags with null labels.
text:
az appconfig feature list -n MyAppConfiguration --label \\0
- name: List a specfic feature for any label start with v1. using connection string.
- name: List a specific feature for any label start with v1. using connection string.
text:
az appconfig feature list --feature color --connection-string Endpoint=https://contoso.azconfig.io;Id=xxx;Secret=xxx --label v1.*
- name: List all features with any labels and query only key, state and conditions.
Expand All @@ -359,6 +368,9 @@
- name: List feature flags with multiple labels.
text:
az appconfig feature list --label test,prod,\\0 -n MyAppConfiguration
- name: List all features starting with "MyApp1".
text:
az appconfig feature list -n MyAppConfiguration --key .appconfig.featureflag/MyApp1*
"""

helps['appconfig feature lock'] = """
Expand All @@ -371,6 +383,9 @@
- name: Force locking a feature using connection string.
text:
az appconfig feature lock --connection-string Endpoint=https://contoso.azconfig.io;Id=xxx;Secret=xxx --feature color --label test --yes
- name: Lock a feature whose name is "Beta" but key is ".appconfig.featureflag/MyApp1:Beta".
text:
az appconfig feature lock -n MyAppConfiguration --key .appconfig.featureflag/MyApp1:Beta
"""

helps['appconfig feature unlock'] = """
Expand All @@ -383,6 +398,9 @@
- name: Force unlocking a feature using connection string.
text:
az appconfig feature unlock --connection-string Endpoint=https://contoso.azconfig.io;Id=xxx;Secret=xxx --feature color --label test --yes
- name: Unlock a feature whose name is "Beta" but key is ".appconfig.featureflag/MyApp1:Beta".
text:
az appconfig feature unlock -n MyAppConfiguration --key .appconfig.featureflag/MyApp1:Beta
"""

helps['appconfig feature enable'] = """
Expand All @@ -395,6 +413,9 @@
- name: Force enabling a feature using connection string.
text:
az appconfig feature enable --connection-string Endpoint=https://contoso.azconfig.io;Id=xxx;Secret=xxx --feature color --label test --yes
- name: Enable a feature whose name is "Beta" but key is ".appconfig.featureflag/MyApp1:Beta".
text:
az appconfig feature enable -n MyAppConfiguration --key .appconfig.featureflag/MyApp1:Beta
"""

helps['appconfig feature disable'] = """
Expand All @@ -407,6 +428,9 @@
- name: Force disabling a feature using connection string.
text:
az appconfig feature disable --connection-string Endpoint=https://contoso.azconfig.io;Id=xxx;Secret=xxx --feature color --label test --yes
- name: Disable a feature whose name is "Beta" but key is ".appconfig.featureflag/MyApp1:Beta".
text:
az appconfig feature disable -n MyAppConfiguration --key .appconfig.featureflag/MyApp1:Beta
"""

helps['appconfig feature filter'] = """
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

import io
import json
import re

import chardet
import javaproperties
Expand Down Expand Up @@ -107,10 +106,8 @@ def validate_import_key(key):

def validate_import_feature(feature):
if feature:
invalid_pattern = re.compile(r'[^a-zA-Z0-9._-]')
invalid = re.search(invalid_pattern, feature)
if invalid:
logger.warning("Ignoring invalid feature '%s'. Only alphanumeric characters, '.', '-' and '_' are allowed in feature name.", feature)
if '%' in feature:
logger.warning("Ignoring invalid feature '%s'. Feature name cannot contain the '%%' character.", feature)
avanigupta marked this conversation as resolved.
Show resolved Hide resolved
return False
else:
logger.warning("Ignoring invalid feature ''. Feature name cannot be empty.")
Expand Down Expand Up @@ -552,8 +549,8 @@ def __serialize_feature_list_to_comparable_json_object(features):
feature_json['description'] = feature.description
# conditions
feature_json['conditions'] = feature.conditions
# key
res[feature.key] = feature_json
# name
res[feature.name] = feature_json
return res


Expand Down Expand Up @@ -877,7 +874,7 @@ def __export_features(retrieved_features, naming_convention):
feature_filter["Parameters"] = filter_.parameters
feature_state[feature_reserved_keywords.enabledfor].append(feature_filter)

feature_entry = {feature.key: feature_state}
feature_entry = {feature.name: feature_state}

exported_dict[feature_reserved_keywords.featuremanagement].update(feature_entry)

Expand Down
Loading