Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

import json
import azure.cli.command_modules.appconfig._azconfig.models as models

@shenmuxiaosen shenmuxiaosen Sep 19, 2019

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can revert changes in this module. #Resolved



Expand Down Expand Up @@ -32,3 +33,4 @@ def __get_value(item, argument):
return item[argument]
except (KeyError, TypeError, IndexError):
return None

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# --------------------------------------------------------------------------------------------

from enum import Enum
import json
import uuid
import azure.cli.command_modules.appconfig._azconfig.constants as constants

Expand Down Expand Up @@ -50,7 +51,7 @@ def __str__(self):
"\netag: " + self.etag + \
"\nLast Modified: " + self.last_modified + \
"\nContent Type: " + self.content_type + \
"\nTags: " + '{!s}'.format(self.tags)
"\nTags: " + (str(self.tags) if self.tags else '')


class QueryFields(Enum):
Expand Down Expand Up @@ -79,6 +80,8 @@ class QueryKeyValueOptions(object):
A request ID that, if provided, can be used to help track the operation.
:ivar string correlation_request_id:
An ID that can be used to correlate the request with a more general operation.
:ivar string content_type:
Content_type of the key-value entry
'''

empty_label = u'\0'
Expand All @@ -88,14 +91,16 @@ def __init__(self,
query_datetime=None,
fields=None,
client_request_id=None,
correlation_request_id=None):
correlation_request_id=None,
content_type=None):
self.label = label

@shenmuxiaosen shenmuxiaosen Sep 18, 2019

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we support query by content_type. Currently we can only query kv by key/label/fields/datetime #Resolved

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed


In reply to: 325806389 [](ancestors = 325806389)

self.query_datetime = query_datetime
self.fields = fields
self.client_request_id = str(
uuid.uuid4()) if client_request_id is None else client_request_id
self.correlation_request_id = str(
uuid.uuid4()) if correlation_request_id is None else correlation_request_id
self.content_type = content_type


class QueryKeyValueCollectionOptions(object):
Expand All @@ -114,6 +119,8 @@ class QueryKeyValueCollectionOptions(object):
A request ID that, if provided, can be used to help track the operation.
:ivar string correlation_request_id:
An ID that can be used to correlate the request with a more general operation.
:ivar string content_type:
Content_type of the key-value entry
'''

any_key = '*'
Expand All @@ -126,7 +133,8 @@ def __init__(self,
query_datetime=None,
fields=None,
client_request_id=None,
correlation_request_id=None):
correlation_request_id=None,
content_type=None):
self.key_filter = key_filter
self.label_filter = label_filter
self.query_datetime = query_datetime
Expand All @@ -135,6 +143,7 @@ def __init__(self,
uuid.uuid4()) if client_request_id is None else client_request_id
self.correlation_request_id = str(
uuid.uuid4()) if correlation_request_id is None else correlation_request_id
self.content_type = content_type


class ModifyKeyValueOptions(object):
Expand Down Expand Up @@ -170,4 +179,4 @@ def __init__(self, user_agent=None, max_retries=None, max_retry_wait_time=None):
self.user_agent = "AzconfigClient/{0}/CLI".format(
constants.Versions.SDKVersion) if user_agent is None else user_agent
self.max_retries = 9 if max_retries is None else max_retries
self.max_retry_wait_time = 30 if max_retry_wait_time is None else max_retry_wait_time
self.max_retry_wait_time = 30 if max_retry_wait_time is None else max_retry_wait_time
220 changes: 220 additions & 0 deletions src/azure-cli/azure/cli/command_modules/appconfig/_featuremodels.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

from enum import Enum
import json

# pylint: disable=too-few-public-methods
# pylint: disable=too-many-instance-attributes

class FeatureState(Enum):
OFF = 1
ON = 2
CONDITIONAL = 3


class FeatureQueryFields(Enum):
KEY = 0x001
LABEL = 0x002
LAST_MODIFIED = 0x020
LOCKED = 0x040
STATE = 0x100
DESCRIPTION = 0x200
CONDITIONS = 0x400
ALL = KEY | LABEL | LAST_MODIFIED | LOCKED | STATE | DESCRIPTION | CONDITIONS


class FeatureFlagDisplay(object):

@shenmuxiaosen shenmuxiaosen Sep 21, 2019

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FeatureFlagDisplay [](start = 6, length = 18)

rename it to just FeatureFlag, along with several helper method, variable name. No need to call it display

'''
Feature Flag schema as displayed to the user.

:ivar str key:
FeatureName (key) of the entry.
:ivar str label:
Label of the entry.
:ivar str state:
Represents if the Feature flag is On/Off/Conditionally On
:ivar str description:
Description of Feature Flag
:ivar bool locked:
Represents whether the feature flag is locked.
:ivar datetime last_modified:
A datetime object representing the last time the feature flag was modified.
:ivar str etag:
The ETag contains a value that you can use to perform operations.
:ivar dict {string, FeatureFilter[]>} conditions:
Dictionary that contains client_filters List (and server_filters List in future)
'''

def __init__(self,
key,
label=None,
state=None,
description=None,
conditions=None):
self.key = key
self.label = label
self.state = state.name.lower()
self.description = description
self.conditions = conditions
self.last_modified = None
self.locked = None

def __str__(self):
featureflagdisplay = {
"Key": self.key,
"Label": self.label,
"State": self.state,
"Locked": self.locked,
"Description": self.description,
"Last Modified": self.last_modified,
"Conditions": custom_serialize_conditions(self.conditions)
}

return json.dumps(featureflagdisplay, indent=2)


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

:ivar str id:
ID (key) of the feature.
:ivar str description:
Description of Feature Flag
:ivar bool enabled:
Represents if the Feature flag is On/Off/Conditionally On
:ivar str label:
Label of the entry.
:ivar dict {string, FeatureFilter[]>} conditions:
Disctionary that contains client_filters List (and server_filters List in future)
'''
def __init__(self,
id,
description=None,
enabled=None,
label=None,
conditions=None):
self.id = id
self.description = description
self.enabled = enabled
self.label = label
self.conditions = conditions

def __str__(self):
featureflagvalue = {
"id": self.id,
"description": self.description,
"enabled": self.enabled,
"label": self.label,
"conditions": custom_serialize_conditions(self.conditions)
}

return json.dumps(featureflagvalue, indent=2)


class FeatureFilter(object):
'''
Feature filters class.

:ivar str Name:
Name of the filter
:ivar dict {str, str} parameters:
Name-Value pairs of parameters
'''

def __init__(self,
name,
parameters=None):
self.name = name
self.parameters = parameters

def __repr__(self):
featurefilter = {
"name": self.name,
"parameters": self.parameters
}
return json.dumps(featurefilter,indent=2)


# Helper Function to serialize Conditions
# Conditions will be dict {str, List[FeatureFilter]}
def custom_serialize_conditions(object):
featurefilterdict = {}
if object:
for key,value in object.items():
featurefilters = []
for filter in value:
featurefilters.append(str(filter))
featurefilterdict[key] = featurefilters
return featurefilterdict


def map_keyvalue_to_featureflagdisplay(keyvalue, show_conditions=True):
feature_flag_value = map_json_to_featureflagvalue(json.loads(keyvalue.value))
feature_flag_display = map_featureflag_value_to_display(feature_flag_value)
feature_flag_display.locked = keyvalue.locked
feature_flag_display.last_modified = keyvalue.last_modified

# By Default, we will try to show conditions unless the user has
# specifically filtered them using --fields arg.
# But in some operations like 'Delete feature', we don't want
# to display all the conditions as a result of delete operation
if not show_conditions:
del feature_flag_display.conditions
return feature_flag_display


def map_featureflag_value_to_display(featureflagvalue):
state = FeatureState.OFF
if (getattr(featureflagvalue, 'enabled')):
state = FeatureState.ON

conditions = getattr(featureflagvalue, 'conditions')

# if conditions["client_filters"] list is not empty, make state conditional
# generalizing for conditions["server_filters"] in future
for value in conditions.values():
if value and state == FeatureState.ON:

@shenmuxiaosen shenmuxiaosen Sep 19, 2019

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for the for loop since this is a dict. As conditions can contains any keys not only client_filters, server_filters, we should explicitly check.

filters = conditions.get("client_filter", [])
if filters and state == FeatureState.ON: #Resolved

state = FeatureState.CONDITIONAL
break

featureflag = FeatureFlagDisplay(
getattr(featureflagvalue, 'id'),
getattr(featureflagvalue, 'label'),
state,
getattr(featureflagvalue, 'description'),
conditions)

return featureflag


def map_json_to_featureflagvalue(json_object):
conditions = __get_value(json_object, 'conditions')
conditions_with_filters = {}
conditions_with_filters = custom_serialize_conditions(conditions)

featureflagvalue = FeatureFlagValue(
__get_value(json_object, 'id'),
__get_value(json_object, 'description'),
__get_value(json_object, 'enabled'),
__get_value(json_object, 'label'),
conditions_with_filters)
return featureflagvalue


def map_json_to_featurefilter(json_object):
featurefilters = FeatureFilter(
__get_value(json_object, 'name'),
__get_value(json_object, 'parameters'))
return featurefilters


def __get_value(item, argument):
try:
return item[argument]
except (KeyError, TypeError, IndexError):
return None

13 changes: 13 additions & 0 deletions src/azure-cli/azure/cli/command_modules/appconfig/_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ def configstore_credential_format(result):
def keyvalue_entry_format(result):
return _output_format(result, _keyvalue_entry_format_group)

def featureflag_entry_format(result):
return _output_format(result, _featureflag_entry_format_group)

def _output_format(result, format_group):
if 'value' in result and isinstance(result['value'], list):
Expand Down Expand Up @@ -59,6 +61,17 @@ def _keyvalue_entry_format_group(item):
])


def _featureflag_entry_format_group(item):
return OrderedDict([
('KEY', _get_value(item, 'key')),
('LABEL', _get_value(item, 'label')),
('STATE', _get_value(item, 'state')),
('LOCKED', _get_value(item, 'locked')),
('DESCRIPTION', _get_value(item, 'description')),
('LAST MODIFIED', _format_datetime(_get_value(item, 'lastModified'))),
('CONDITIONS', _get_value(item, 'conditions'))
])

def _format_datetime(date_string):
from dateutil.parser import parse
try:
Expand Down
24 changes: 24 additions & 0 deletions src/azure-cli/azure/cli/command_modules/appconfig/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,3 +219,27 @@
text:
az appconfig kv unlock --connection-string Endpoint=https://contoso.azconfig.io;Id=xxx;Secret=xxx --key color --label test --yes
"""

helps['appconfig feature show'] = """
type: command
short-summary: Show all attributes of a feature flag.
examples:
- name: Show a feature flag using App Configuration name with a specific label
text:
az appconfig feature show -n MyAppConfiguration --feature color --label MyLabel
- name: Show a feature flag using connection string and field filters
text:
az appconfig feature show --connection-string Endpoint=https://contoso.azconfig.io;Id=xxx;Secret=xxx --feature color --fields key locked conditions state
"""

helps['appconfig feature set'] = """
type: command

@shenmuxiaosen shenmuxiaosen Sep 20, 2019

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add help for feature lock/unlock/enable/disable #Resolved

short-summary: Set a feature flag.
examples:
- name: Set a feature flag with label MyLabel.
text:
az appconfig feature set -n MyAppConfiguration --feature color --label MyLabel
- name: Set a feature flag with null label using connection string and set a description.
text:
az appconfig feature set --connection-string Endpoint=https://contoso.azconfig.io;Id=xxx;Secret=xxx --feature color --description "This is a colorful feature"
"""
Loading