Skip to content

Conversation

@maddieclayton
Copy link
Contributor

Description

#5496

Checklist

@maddieclayton
Copy link
Contributor Author

Copy link
Contributor

@dragav dragav left a comment

Choose a reason for hiding this comment

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

Many thanks for the thorough refreshing of the KV cmdlets. :-)

HelpMessage = "Specifies the email address of the contact.")]
[ValidateNotNullOrEmpty]
public string EmailAddress { get; set; }
public string[] EmailAddress { get; set; }
Copy link
Contributor

Choose a reason for hiding this comment

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

I assume this is marked as a breaking change.

Copy link
Member

Choose a reason for hiding this comment

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

@dragav For PowerShell, this is not a breaking change - this parameter will bind to a string or a list of strings.

VaultName = parsedResourceId.ResourceName;
}

foreach (String email in EmailAddress)
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems inefficient; we shouldn't have to make an outbound call to the service to get the current contacts for each email address. Furthermore, the cmdlet should validate the input list of emails does not contain duplicates before making an (expensive) service call. I would suggest adding the emails to a hashset, then merging the hashset into the existing contact list, skipping pre-existing, duplicate contacts.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch - does this fix look better?

/// Vault name
/// </summary>
[Parameter(Mandatory = true,
[Parameter(Mandatory = false,
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm trying to understand the behavior of the cmdlet after this change. We should be able to:

  • retrieve a vault by name (alone) -> name is required
  • list the vaults in a resource group -> name is optional, rg name is optional but indicates a scoped search
  • list the vaults in a subscription -> no parameters required

Does your change guarantee this behavior?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. By making VaultName option in the GetVault parameter set, I have enable the user to pass in just the resourcegroupname using this parameter. Thus, the parameter set with just resourcegroupname set as mandatory is no longer needed. Check out the help file to see the change a little more clearly if you still have questions.


private const string InputObjectByVaultNameParameterSet = "ByNameInputObject";
private const string InputObjectByCertificateNameandVersionParameterSet = "ByCertificateNameAndVersionInputObject";
private const string InputObjectByCertificateVersionsParameterSet = "ByCertificateAllVersionsInputObject";
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the scenario that gets a certificate given an input of a) list of certificate versions or b) a certificate itself?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There was never a scenario where the cmdlet accepted a list of versions, only a single version. Is this a scenario we want to enable? Additionally, why would we want to pass in a certificate itself to a get cmdlet? If we have the certificate, it seems unlikely we'd want to "get" it again. All I have done here is remodel the parameter sets so that we could eliminate one unnecessary extra parameter set (see the help file for the new syntax), and add an "inputobject" parameter set for each existing parameter set that would accept a vault object instead of a vault name.

Copy link
Contributor

Choose a reason for hiding this comment

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

I wasn't suggesting any new scenario; it seemed odd to me to have an "cert all versions input object" parameter set - it implied that "cert all versions" can be the input. Thanks for clarifying.

ParameterSetName = InputObjectByCertificateNameandVersionParameterSet,
Position = 0,
ValueFromPipeline = true,
HelpMessage = "KeyVault object.")]
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this is confusing. The only supported input object should be a vault identifier or a vault. Accepting any other object merely to extract the vault name is misleading as to what the cmdlet returns. Did I misunderstand the effect of this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This inputobject accepts a vault, and extracts the needed information from that vault object. Does that answer your question?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, thank you for the clarification.

/// </summary>
[Parameter( Mandatory = false,
HelpMessage = "Permanently remove the previously deleted certificate." )]
[Parameter(Mandatory = false,
Copy link
Contributor

Choose a reason for hiding this comment

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

(nitpick) I've seen that elsewhere in this changeset the InRemovedState parameter was marked as mandatory for the 'deleted' parameter set. Here it seems to be missing. Would you consider adding the 'deleted' parameter set to this cmdlet as well, and maintain consistency with other cmdlets?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So here, -InRemovedState is optional because the user can use the parameter set to either delete an existing certificate (if -inremovedstate is not specified) or to purge a deleted certificate (if -inremovedstate is specified). I could split this into two parameter sets, but there is not reason to as both deleting and purging certificates take the same parameters. Does that make sense? Remove-Key and Remove-Secret are also formatted like this, and were in the past.

public override void ExecuteCmdlet()
{
if (ShouldProcess(EmailAddress, Properties.Resources.RemoveCertificateContact))
foreach (string email in EmailAddress)
Copy link
Contributor

Choose a reason for hiding this comment

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

Same observation as in the matching AddCertContact cmdlet - consider storing the input email addresses in a hashset, making a single call to the service to retrieve the contacts, and then updating the contacts in a single pass.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good call, fixed.

HelpMessage = "Input file. The input file containing the backed-up blob")]
[ValidateNotNullOrEmpty]
public string InputFile { get; set; }
public string[] InputFile { get; set; }
Copy link
Contributor

Choose a reason for hiding this comment

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

Why an array? Is there a scenario that requires the batching of multiple Restore cmdlets in a single one? I get the convenience factor, but I'm concerned that error scenarios will lead to confusion: what was restored, which one failed and why, and do we continue on error or stop at the first one?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay, if you believe this scenario will cause more confusion then convenience then I will remove - I simply was adding arrays where I thought it might be useful to customers, but you have a better idea of where it is actually useful.

ParameterSetName = InputObjectByEmailAddress,
ValueFromPipelineByPropertyName = true,
HelpMessage = "Specifies certificate operation permissions to grant to a user or service principal.")]
[ValidateSet("get", "list", "delete", "create", "import", "update", "managecontacts", "getissuers", "listissuers", "setissuers", "deleteissuers", "manageissuers", "recover", "purge", "all")]
Copy link
Contributor

Choose a reason for hiding this comment

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

As an aside, we wanted to deprecate 'all' as a permission for some time. Since this is a deprecating change, we should just remove it with this commit. If you'd rather not, that's perfectly fine - we'll follow up. Thanks.

Copy link
Member

Choose a reason for hiding this comment

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

This is not a breaking change, but we should deprecate anything we would like to remove at the next breaking change release

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will add this to the list of breaking change and take care of it with the breaking change release, and the deprecation with the next PR where I will add deprecation to other upcoming breaking changes. Do you want "all" removed from each validateset, or only from "PermissionsToCertificates"?

Copy link
Contributor

Choose a reason for hiding this comment

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

All occurrences of "All" as a permission should be removed. Thanks.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added to list of breaking changes - will be included in my next PR.

[Parameter(Mandatory = true,
ValueFromPipelineByPropertyName = true,
ParameterSetName = ExpandedRenewNumberParameterSet,
HelpMessage = "Specifies the number of days after which the automatic process for the certificate renewal begins.")]
Copy link
Contributor

Choose a reason for hiding this comment

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

"the number of days before expiration when automatic renewal should start".

VaultName = parsedResourceId.ResourceName;
}

foreach (String email in EmailAddress)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch - does this fix look better?

/// Vault name
/// </summary>
[Parameter(Mandatory = true,
[Parameter(Mandatory = false,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. By making VaultName option in the GetVault parameter set, I have enable the user to pass in just the resourcegroupname using this parameter. Thus, the parameter set with just resourcegroupname set as mandatory is no longer needed. Check out the help file to see the change a little more clearly if you still have questions.

ParameterSetName = InputObjectByCertificateNameandVersionParameterSet,
Position = 0,
ValueFromPipeline = true,
HelpMessage = "KeyVault object.")]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This inputobject accepts a vault, and extracts the needed information from that vault object. Does that answer your question?


private const string InputObjectByVaultNameParameterSet = "ByNameInputObject";
private const string InputObjectByCertificateNameandVersionParameterSet = "ByCertificateNameAndVersionInputObject";
private const string InputObjectByCertificateVersionsParameterSet = "ByCertificateAllVersionsInputObject";
Copy link
Contributor Author

Choose a reason for hiding this comment

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

There was never a scenario where the cmdlet accepted a list of versions, only a single version. Is this a scenario we want to enable? Additionally, why would we want to pass in a certificate itself to a get cmdlet? If we have the certificate, it seems unlikely we'd want to "get" it again. All I have done here is remodel the parameter sets so that we could eliminate one unnecessary extra parameter set (see the help file for the new syntax), and add an "inputobject" parameter set for each existing parameter set that would accept a vault object instead of a vault name.

break;

case ByVaultNameParameterSet:
if (!string.IsNullOrEmpty(Version))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

If you go through each of these if statements, each one covers one case that you list above. I had to change to this structure because of the additional "inputobject" parameter sets.

/// </summary>
[Parameter( Mandatory = false,
HelpMessage = "Permanently remove the previously deleted certificate." )]
[Parameter(Mandatory = false,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

So here, -InRemovedState is optional because the user can use the parameter set to either delete an existing certificate (if -inremovedstate is not specified) or to purge a deleted certificate (if -inremovedstate is specified). I could split this into two parameter sets, but there is not reason to as both deleting and purging certificates take the same parameters. Does that make sense? Remove-Key and Remove-Secret are also formatted like this, and were in the past.

public override void ExecuteCmdlet()
{
if (ShouldProcess(EmailAddress, Properties.Resources.RemoveCertificateContact))
foreach (string email in EmailAddress)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good call, fixed.

HelpMessage = "Input file. The input file containing the backed-up blob")]
[ValidateNotNullOrEmpty]
public string InputFile { get; set; }
public string[] InputFile { get; set; }
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay, if you believe this scenario will cause more confusion then convenience then I will remove - I simply was adding arrays where I thought it might be useful to customers, but you have a better idea of where it is actually useful.

HelpMessage = "Input file. The input file containing the backed-up blob" )]
[ValidateNotNullOrEmpty]
public string InputFile { get; set; }
public string[] InputFile { get; set; }
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed the array here as well - let me know if you think it should be added back here.

ParameterSetName = InputObjectByEmailAddress,
ValueFromPipelineByPropertyName = true,
HelpMessage = "Specifies certificate operation permissions to grant to a user or service principal.")]
[ValidateSet("get", "list", "delete", "create", "import", "update", "managecontacts", "getissuers", "listissuers", "setissuers", "deleteissuers", "manageissuers", "recover", "purge", "all")]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will add this to the list of breaking change and take care of it with the breaking change release, and the deprecation with the next PR where I will add deprecation to other upcoming breaking changes. Do you want "all" removed from each validateset, or only from "PermissionsToCertificates"?

/// Key version.
/// </summary>
[Parameter(Mandatory = false,
[Parameter(Mandatory = true,
Copy link
Member

Choose a reason for hiding this comment

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

@maddieclayton do you know if this parameter set worked without providing the Version parameter before?

Copy link
Member

Choose a reason for hiding this comment

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

Same q. Are we breaking any existing scenarios?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If you look at the help file, I added an optional "Name" parameter to the first parameter set, which will take care of the case where -Version is not provided to this parameter set. This leaves all scenarios working, and I believed that the parameter sets made more sense.

/// Secret version
/// </summary>
[Parameter(Mandatory = false,
[Parameter(Mandatory = true,
Copy link
Member

Choose a reason for hiding this comment

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

@maddieclayton do you know if this parameter set worked before without providing the Version parameter?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same comment here. I added an optional "Name" parameter to the first parameter set, which will take care of the case where -Version is not provided to this parameter set. This leaves all scenarios working, and I believed that the parameter sets made more sense.

/// </summary>
[Parameter(Mandatory = true,
Position = 2,
ValueFromPipelineByPropertyName = true,
Copy link
Member

Choose a reason for hiding this comment

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

@maddieclayton should this be ValueFromPipeline?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good call, thanks for the catch.

/// If present, operate on the deleted vault entity.
/// </summary>
[Parameter(Mandatory = false,
[Parameter(Mandatory = true,
Copy link
Member

Choose a reason for hiding this comment

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

@maddieclayton do we know if this parameter set worked previously if the user did not provide InRemovedState?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If they provided all the parameters except -InRemovedState, it would default to the first parameter set, because the first is the default and contains all the parameters in the second.

ValueFromPipeline = true,
ParameterSetName = ImportWithPrivateKeyFromCollectionParameterSet,
HelpMessage = "Specifies the certificate collection to add to key vault.")]
[Alias("InputObject")]
Copy link
Member

Choose a reason for hiding this comment

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

@maddieclayton any reason we don't want to call this parameter InputObject with an alias to CertificateCollection like we do for all other cmdlets?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Deleting alias as it doesn't really make sense in this context.

HelpMessage = "Specifies the certificate issuer to set.")]
[ValidateNotNullOrEmpty]
public KeyVaultCertificateIssuer Issuer { get; set; }
public PSKeyVaultCertificateIssuerIdentityItem Issuer { get; set; }
Copy link
Member

Choose a reason for hiding this comment

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

@maddieclayton do we want to name this InputObject with an alias to Issuer?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@@ -1,4 +1,4 @@
---
Copy link
Member

Choose a reason for hiding this comment

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

@maddieclayton it looks like the underlying call for this cmdlet is a PATCH, so we should look at renaming this cmdlet to Update-AzureKeyVaultCertificateAttribute with an alias to Set-*

Copy link
Contributor

Choose a reason for hiding this comment

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

Please don't do this. There is no separate command for adding or importing a secret - Set fits the intended profile here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm going to skip doing these aliases for now and we will discuss again for the breaking change PR - I think some of these aliases might make sense while others do not.

@@ -1,4 +1,4 @@
---
Copy link
Member

Choose a reason for hiding this comment

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

@maddieclayton same comment about this call being a PATCH, so we should change the verb to Update

Copy link
Contributor

Choose a reason for hiding this comment

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

Same comment as above - there is no Add-Policy, and we don't want one. A Set creates the policy if missing, and overwrites it if existing.

@@ -1,4 +1,4 @@
---
Copy link
Member

Choose a reason for hiding this comment

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

@maddieclayton same comment about this call being a PATCH, so we should change the verb to Update

@@ -1,4 +1,4 @@
---
Copy link
Member

Choose a reason for hiding this comment

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

@maddieclayton same comment about this call being a PATCH, so we should change the verb to Update

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Going to rename to Update-AzureKeyVaultSecret as I will combine this with Set-AzureKeyVaultSecret at the breaking change release and I don't want to aliases to get out of control.

Copy link
Member

@markcowl markcowl left a comment

Choose a reason for hiding this comment

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

a couple of questions and potential changes

/// </summary>
[Parameter(Mandatory = false,
Position = 1,
ValueFromPipelineByPropertyName = true,
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 be removing these valuefrompipelinebypropertyname for the interactive parameter sets, right? I assume this is happening in the breaking change update.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, this will be happening for the breaking change update.

/// Key version.
/// </summary>
[Parameter(Mandatory = false,
[Parameter(Mandatory = true,
Copy link
Member

Choose a reason for hiding this comment

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

Same q. Are we breaking any existing scenarios?

ParameterSetName = DNSNamesParameterSet,
ValueFromPipelineByPropertyName = true,
HelpMessage = "Specifies the DNS names in the certificate.")]
public List<string> DnsNames { get; set; }
Copy link
Member

Choose a reason for hiding this comment

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

should rename as DnsName, with alias to old name DnsNames (and deprecate)

Copy link
Contributor

Choose a reason for hiding this comment

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

Why? This parameter does take a list..

Copy link
Member

Choose a reason for hiding this comment

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

@dragav it comes from the Strongly Encouraged Developer Guidelines for PowerShell cmdlets:

Avoid using plural names for parameters whose value is a single element. This includes parameters that take arrays or lists because the user might supply an array or list with only one element.

Copy link
Contributor

Choose a reason for hiding this comment

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

I disagree. We don't have a parameter called "PermissionToSecrets" - plural is appropriate here as it conveys the fact that the certificate accepts an enumeration of DNS names. Having a suggestively named parameter outweighs a convention based solely on the parameter's type.

Copy link
Member

Choose a reason for hiding this comment

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

@dragav I agree that this convention is silly, but it is one that we enforce for new changes across all modules and one that has been brought up by users numerous times (e.g., this issue).

Going back to the guidelines mentioned above, as long as the user can provide a single object to the list/array parameter, than the parameter noun should be singular. If, however, you can do extra validation to ensure that the user always provides more than one object, then having a plural noun is allowed:

Plural parameter names should be used only in those cases where the value of the parameter is always a multiple-element value. In these cases, the cmdlet should verify that multiple elements are supplied, and the cmdlet should display a warning to the user if multiple elements are not supplied.

@markcowl markcowl assigned maddieclayton and unassigned markcowl Mar 21, 2018
@cormacpayne cormacpayne changed the base branch from preview to release-2018-03-23 March 21, 2018 15:45
cormacpayne
cormacpayne previously approved these changes Mar 21, 2018
@cormacpayne cormacpayne assigned markcowl and unassigned cormacpayne Mar 21, 2018
@maddieclayton
Copy link
Contributor Author

maddieclayton commented Mar 21, 2018

@maddieclayton maddieclayton changed the base branch from release-2018-03-23 to preview March 22, 2018 00:00
@maddieclayton maddieclayton merged commit 0b76130 into Azure:preview Mar 23, 2018
@maddieclayton maddieclayton deleted the KeyVault1 branch March 23, 2018 23:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet