Skip to content

Conversation

@mrm9084
Copy link
Member

@mrm9084 mrm9084 commented Jan 9, 2023

Description

Moves the load method of AppConfigurationProvider from a class method to a module method.

All SDK Contribution checklist:

  • The pull request does not introduce [breaking changes]
  • CHANGELOG is updated for new features, bug fixes or other significant changes.
  • I have read the contribution guidelines.

General Guidelines and Best Practices

  • Title of the pull request is clear and informative.
  • There are a small number of commits, each of which have an informative message. This means that previously merged commits do not appear in the history of the PR. For more information on cleaning up the commits in your PR, see this page.

Testing Guidelines

  • Pull request includes test coverage for the included changes.

@ghost ghost added the App Configuration Azure.ApplicationModel.Configuration label Jan 9, 2023
@azure-sdk
Copy link
Collaborator

API change check

APIView has identified API level changes in this PR and created following API reviews.

azure-appconfiguration-provider


class AzureAppConfigurationProvider:
#pylint:disable=protected-access
def loadProvider(*, connection_string=None, endpoint=None, credential=None, **kwargs):
Copy link
Member

Choose a reason for hiding this comment

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

Type hints should go in the method signature, and we should also add overloads.
The Provider object itself currently has no methods outside of the implemented protocols - so we can simply document the return type as a Mapping. However once we start to add things like the refresh, which may expose other methods, we would need to update this (so that the ref docs link correctly).

Suggested change
def loadProvider(*, connection_string=None, endpoint=None, credential=None, **kwargs):
@overload
def load_provider(endpoint: str, credential: TokenCredential, **kwargs) -> Mapping[str, str]:
...
@overload
def load_provider(connection_string: str, **kwargs) -> Mapping[str, str]:
...
def load_provider(*args, **kwargs) -> Mapping[str, str]:

Copy link
Member Author

Choose a reason for hiding this comment

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

I have made some of this modification, but something seems wrong. endpoint and the other method variables are put into kwargs, not args.

Also, it seems like Mapping isn't a typing type. I tried making a new one but it didn't work.

return key_vault_options.secret_resolver(config.secret_id)

key_vault_identifier = KeyVaultSecretIdentifier(config.secret_id)
raise AttributeError(
Copy link
Member

Choose a reason for hiding this comment

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

Not sure if AttributeError is the right pick here - but we can take a closer look at that later.

def __init__(self, credential=None, secret_clients=None, secret_resolver=None):
# type: (TokenCredential, List[SecretClient], Callable) -> None
def __init__(self, *, credential=None, secret_clients=None, secret_resolver=None):
# type: (TokenCredential, List[SecretClient], Callable[[str], str]) -> None
Copy link
Member

Choose a reason for hiding this comment

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

We should use inline type hints and overloads (though I can't see where the Vault endpoint comes from?):

@overload
def __init__(self, credential: TokenCredential,*, secret_resolver: Optional[Callable[[str], str]] = None) -> None
    ...

@overload
def __init__(self, secret_client: List[SecretClient], *, secret_resolver: Optional[Callcable[[str], str]] = None) -> None
    ...

Copy link
Member Author

Choose a reason for hiding this comment

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

If I know what you are talking about, the "vault endpoint" comes from App Configuration inside of the key vault reference.

Copy link
Member Author

Choose a reason for hiding this comment

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

Also, the options here are, Credential with List of Clients, or List of Clients with Secret Resolver.

The idea is that we default either to the TokenCredential provided or the Secret Resolver.

Copy link
Member Author

Choose a reason for hiding this comment

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

I also ran into an issue with TokenCredential it works fine locally, but for some reason it breaks in the build pipeline, I got ImportError: cannot import name 'TokenCredential'. I was using from azure.core.credential import TokenCredential. https://dev.azure.com/azure-sdk/public/_build/results?buildId=2105499&view=ms.vss-test-web.build-test-results-tab&runId=38663166&resultId=100000&paneView=debug

sub_types = sub_type.split("+")
if "json" in sub_types:
return True
class AzureAppConfigurationProvider:
Copy link
Member

Choose a reason for hiding this comment

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

Given that this object holds an instance of a client, we will need to expose this as a context manger and allow it to be closeable (such that the connection can be cleaned up).
This means that we will probably need to still keep the object public so that it can be properly documented....

Copy link
Member Author

Choose a reason for hiding this comment

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

Well, this would get a bit awkward when it comes to refresh. Typically it's best to reuse the same clients as it is a costly operation to create new ones.

Also, how would this work given we have one to many clients, for App Configuration (will be with geo-replication) and Key Vault.

if key_vault_options and (
key_vault_options.credential or key_vault_options.secret_clients or key_vault_options.secret_resolver
):
correlation_context += ",UsesKeyVault"
Copy link
Member

Choose a reason for hiding this comment

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

So we are adding RequestType and UsesKeyVault tracing right now. Is there a way to get host type and dev environment tracing for python apps?

Copy link
Member Author

Choose a reason for hiding this comment

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

It was already there. This was just moved. I can work on adding those, but not in this branch.


trimmed_key = config.key
# Trim the key if it starts with one of the prefixes provided
for trim in provider._trim_prefixes:
Copy link
Member

Choose a reason for hiding this comment

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

In .NET, we process KeyVault/Json types first, then trim prefixes. Can we do the same here to maintain the same behavior? Changing the order might produce different results, especially when we start supporting feature flags.

Copy link
Member Author

Choose a reason for hiding this comment

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

If you look down a bit, the trimmed_key is only used in the dictionary, _resolve_keyvault_reference uses the config which contains the untrimmed key.

Copy link
Member

Choose a reason for hiding this comment

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

Just thinking about how feature flags would look in future - we'd want to maintain the same schema that we use in .net and spring providers, right? So for a feature "Beta", dictionary key would be something like "FeatureManagement:Beta" (or whatever separator makes sense for python).
Since feature flag flattening changes the key being added to dictionary, we shouldn't use trimmed_key as dictionary key.

Copy link
Member Author

Choose a reason for hiding this comment

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

I see two options:

elif config.content_type == FeatureFlagConfigurationSetting._feature_flag_content_type: #pylint: disable=protected-access
    feature_management = provider._dict.get("FeatureManagement", {})
    feature_management[trimmed_key] = config.value

or

elif config.content_type == FeatureFlagConfigurationSetting._feature_flag_content_type: #pylint: disable=protected-access
    provider._dict["FeatureManagement." + trimmed_key] = config.value

Copy link
Member

Choose a reason for hiding this comment

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

Discussed with Matt offline. Since the config dictionary structure is not limited to <string,string> in python, what you have right now should be good.

@mrm9084
Copy link
Member Author

mrm9084 commented Jan 31, 2023

Closing, I think I have gotten all of the comments from here and moved them too #28497.

@mrm9084 mrm9084 closed this Jan 31, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

App Configuration Azure.ApplicationModel.Configuration

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants