-
Notifications
You must be signed in to change notification settings - Fork 53
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
auth: support caching of values retrieves from Azure CLI to avoid unnecessary repeated invocations #753
Conversation
…ecessary repeated invocations
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've taken a look through and left some comments inline, but on the whole this change looks good, however I think we probably should look to:
- Remove the
CachingEnabled
toggle - is there a use-case where this wouldn't be beneficial? In the event there's an issue that arises from this, we can do another release ofhashicorp/go-azure-sdk
(which is more work, but we're going to want this cache enabled everywhere?) - Change how the cache is built, so that we're building this only when the object is nil (which achieves this one-time load without the
enabled
flag) - Centralise the Azure CLI checks, as we're obtaining details about the Azure CLI in
newAzureCliConfig
- meaning we don't need to re-check this inToken
andAuxiliaryTokens
WDYT?
sdk/auth/azure_cli_authorizer.go
Outdated
} | ||
|
||
// NewAzureCliAuthorizer returns an Authorizer which authenticates using the Azure CLI. | ||
func NewAzureCliAuthorizer(ctx context.Context, options AzureCliAuthorizerOptions) (Authorizer, error) { | ||
conf, err := newAzureCliConfig(options.Api, options.TenantId, options.AuxTenantIds) | ||
conf, err := newAzureCliConfig(options.Api, options.TenantId, options.AuxTenantIds, options.EnableCaching) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we only need to check the Azure CLI version once (in newAzureCliConfig
) - rather than at each usage? If newAzureCliConfig
returns an error (because the version is outdated) we'd return an error from this method - so we wouldn't have an AzureCliAuthorizer
instance?
sdk/auth/azure_cli_authorizer.go
Outdated
@@ -145,7 +176,6 @@ func (a *AzureCliAuthorizer) AuxiliaryTokens(_ context.Context, _ *http.Request) | |||
|
|||
const ( | |||
AzureCliMinimumVersion = "2.0.81" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps we should move this into azurecli
so that the only place this is checked is in CheckAzVersion
?
sdk/auth/azure_cli_authorizer.go
Outdated
var azVersion *string | ||
if a.conf.CacheEnabled && cliCache.AzVersion != nil { | ||
azVersion = cliCache.AzVersion | ||
} else { | ||
if azVersion, err = azurecli.GetAzVersion(); err != nil { | ||
return nil, fmt.Errorf("%s. Please ensure you have installed Azure CLI version %s or newer", err, AzureCliMinimumVersion) | ||
} | ||
} | ||
if err = azurecli.CheckAzVersion(*azVersion, azurecli.MsalVersion, nil); err != nil { | ||
return nil, fmt.Errorf("checking the version of the Azure CLI: %+v", err) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rather than doing this in both Token
and AuxiliaryTokens
, as we're already doing this in newAzureCliConfig
we can remove this check?
var azVersion *string | |
if a.conf.CacheEnabled && cliCache.AzVersion != nil { | |
azVersion = cliCache.AzVersion | |
} else { | |
if azVersion, err = azurecli.GetAzVersion(); err != nil { | |
return nil, fmt.Errorf("%s. Please ensure you have installed Azure CLI version %s or newer", err, AzureCliMinimumVersion) | |
} | |
} | |
if err = azurecli.CheckAzVersion(*azVersion, azurecli.MsalVersion, nil); err != nil { | |
return nil, fmt.Errorf("checking the version of the Azure CLI: %+v", err) | |
} |
sdk/auth/azure_cli_authorizer.go
Outdated
var azVersion *string | ||
if a.conf.CacheEnabled && cliCache.AzVersion != nil { | ||
azVersion = cliCache.AzVersion | ||
} else { | ||
if azVersion, err = azurecli.GetAzVersion(); err != nil { | ||
return nil, fmt.Errorf("%s. Please ensure you have installed Azure CLI version %s or newer", err, AzureCliMinimumVersion) | ||
} | ||
} | ||
if err = azurecli.CheckAzVersion(*azVersion, azurecli.MsalVersion, nil); err != nil { | ||
return nil, fmt.Errorf("checking the version of the Azure CLI: %+v", err) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(as above)
var azVersion *string | |
if a.conf.CacheEnabled && cliCache.AzVersion != nil { | |
azVersion = cliCache.AzVersion | |
} else { | |
if azVersion, err = azurecli.GetAzVersion(); err != nil { | |
return nil, fmt.Errorf("%s. Please ensure you have installed Azure CLI version %s or newer", err, AzureCliMinimumVersion) | |
} | |
} | |
if err = azurecli.CheckAzVersion(*azVersion, azurecli.MsalVersion, nil); err != nil { | |
return nil, fmt.Errorf("checking the version of the Azure CLI: %+v", err) | |
} |
sdk/auth/azure_cli_authorizer.go
Outdated
@@ -113,12 +135,21 @@ func (a *AzureCliAuthorizer) AuxiliaryTokens(_ context.Context, _ *http.Request) | |||
} | |||
|
|||
azArgs := []string{"account", "get-access-token"} | |||
var err error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(for below)
var err error |
sdk/auth/azure_cli_authorizer.go
Outdated
// EnableCaching permits module-scoped caching of the parsed results of `az` command invocations, and can be set to improve | ||
// performance when instantiating multiple authorizers, at the cost of detectability during long-running applications. | ||
EnableCaching bool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a benefit to making caching configurable? This feels like something we'd always want on?
// EnableCaching permits module-scoped caching of the parsed results of `az` command invocations, and can be set to improve | |
// performance when instantiating multiple authorizers, at the cost of detectability during long-running applications. | |
EnableCaching bool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good to me, I've refactored accordingly 👍
sdk/auth/azure_cli_authorizer.go
Outdated
var azVersion *string | ||
if cacheEnabled && cliCache.AzVersion != nil { | ||
azVersion = cliCache.AzVersion | ||
} else { | ||
azVersion, err = azurecli.GetAzVersion() | ||
if err != nil { | ||
return nil, fmt.Errorf("%s. Please ensure you have installed Azure CLI version %s or newer", err, AzureCliMinimumVersion) | ||
} | ||
|
||
if cacheEnabled { | ||
cliCache.AzVersion = azVersion | ||
} | ||
} | ||
nextMajor := AzureCliNextMajorVersion | ||
if err = azurecli.CheckAzVersion(*azVersion, azurecli.MsalVersion, &nextMajor); err != nil { | ||
return nil, err | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we make cliCache
a pointer, we should be able to populate the entire object one-time, which'd achieve the same thing albeit a little simpler?
var azVersion *string | |
if cacheEnabled && cliCache.AzVersion != nil { | |
azVersion = cliCache.AzVersion | |
} else { | |
azVersion, err = azurecli.GetAzVersion() | |
if err != nil { | |
return nil, fmt.Errorf("%s. Please ensure you have installed Azure CLI version %s or newer", err, AzureCliMinimumVersion) | |
} | |
if cacheEnabled { | |
cliCache.AzVersion = azVersion | |
} | |
} | |
nextMajor := AzureCliNextMajorVersion | |
if err = azurecli.CheckAzVersion(*azVersion, azurecli.MsalVersion, &nextMajor); err != nil { | |
return nil, err | |
} | |
if cliCache == nil { | |
var err error | |
azVersion, err = azurecli.GetAzVersion() | |
if err != nil { | |
return nil, fmt.Errorf("%s. Please ensure you have installed Azure CLI version %s or newer", err, AzureCliMinimumVersion) | |
} | |
var defaultTenantId *string | |
if defaultTenantId, err = azurecli.CheckTenantID(tenantId); err != nil { | |
return nil, err | |
} | |
if defaultTenantId == nil { | |
return nil, errors.New("invalid tenantId or unable to determine tenantId") | |
} | |
var defaultSubscriptionId *string | |
if defaultSubscriptionId, err = azurecli.GetDefaultSubscriptionID(); err != nil { | |
return nil, err | |
} | |
cliCache = &cliCache{ | |
AzVersion: azVersion, | |
DefaultSubscriptionId: defaultSubscriptionId, | |
DefaultTenantId: defaultTenantId, | |
} | |
} | |
nextMajor := AzureCliNextMajorVersion | |
if err = azurecli.CheckAzVersion(*azVersion, azurecli.MsalVersion, &nextMajor); err != nil { | |
return nil, err | |
} |
sdk/auth/config.go
Outdated
// AzureCLIUseCache specifies whether caching should be enabled for idempotent operation results | ||
AzureCLIUseCache bool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which'd mean we could remove this?
// AzureCLIUseCache specifies whether caching should be enabled for idempotent operation results | |
AzureCLIUseCache bool |
@tombuildsstuff Thanks for the feedback. I think it's probably fine to remove the toggle and just assume caching for whitelisted I've refactored this so the caching now happens within the |
Totally missed this was sitting for a re-review, sorry 👀 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 👍
sdk/internal/azurecli/azcli.go
Outdated
validTenantId, err := regexp.MatchString("^[a-zA-Z0-9._-]+$", tenantId) | ||
if err != nil { | ||
return "", fmt.Errorf("could not parse tenant ID %q: %s", tenantId, err) | ||
return nil, fmt.Errorf("could not parse tenant ID %q: %s", tenantId, err) | ||
} | ||
|
||
if !validTenantId { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit / not a blocker but since GetDefaultSubscriptionID
exists perhaps this method wants splitting into GetDefaultTenantID
and CheckTenantID
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done :)
Thank you! Much better now. |
This implements caching of results from Azure CLI to avoid unnecessary repeated forks of
az
. The first time a particularaz
command invocation is made, if thecacheable
argument toazurecli.JSONUnmarshalAzCmd()
is true, the resulting bytes will be cached and can be reused later on for unmarshalling (either the same, or additional data fields). On subsequent function calls using the sameaz
arguments (e.g. from additional authorizers being set up), the result is retrieved from the cache instead, yielding much-improved performance.Before
After
The initial implementation of this happened within the authorizer, rather than at the lower-level
azurecli
internal helper package. However, with the latter, the caching is more robust as the command result is cached rather than the parsed data, so it can be re-used for unmarshalling other data.Resolves: #752
Replaces: #707
Related: hashicorp/terraform-provider-azurerm#23977
Downstream PR: hashicorp/terraform-provider-azurerm#24056