diff --git a/src/Aks/Aks.sln b/src/Aks/Aks.sln index 657cf9c48a38..26c43c8ca3bb 100644 --- a/src/Aks/Aks.sln +++ b/src/Aks/Aks.sln @@ -1,7 +1,6 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27703.2035 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30804.86 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aks", "Aks\Aks.csproj", "{8058D403-06E3-4BED-8924-D166CE303961}" EndProject @@ -39,6 +38,10 @@ Global {142D7B0B-388A-4CEB-A228-7F6D423C5C2E}.Debug|Any CPU.Build.0 = Debug|Any CPU {142D7B0B-388A-4CEB-A228-7F6D423C5C2E}.Release|Any CPU.ActiveCfg = Release|Any CPU {142D7B0B-388A-4CEB-A228-7F6D423C5C2E}.Release|Any CPU.Build.0 = Release|Any CPU + {6BD6B80A-06AF-4B5B-9230-69CCFC6C8D64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6BD6B80A-06AF-4B5B-9230-69CCFC6C8D64}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6BD6B80A-06AF-4B5B-9230-69CCFC6C8D64}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6BD6B80A-06AF-4B5B-9230-69CCFC6C8D64}.Release|Any CPU.Build.0 = Release|Any CPU {FF81DC73-B8EC-4082-8841-4FBF2B16E7CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FF81DC73-B8EC-4082-8841-4FBF2B16E7CE}.Debug|Any CPU.Build.0 = Debug|Any CPU {FF81DC73-B8EC-4082-8841-4FBF2B16E7CE}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/src/Aks/Aks/ChangeLog.md b/src/Aks/Aks/ChangeLog.md index dd67ed553e67..282b559e3300 100644 --- a/src/Aks/Aks/ChangeLog.md +++ b/src/Aks/Aks/ChangeLog.md @@ -18,6 +18,9 @@ - Additional information about change #1 --> ## Upcoming Release +* Refined error messages of cmdlet failure. +* Upgraded exception handling to use Azure PowerShell related exceptions. +* Fixed the issue that user could not use provided service principal to create Kubernetes cluster. [#13938] ## Version 2.0.1 * Fixed the issue that user cannot use service principal to create a new Kubernetes cluster. [#13012] diff --git a/src/Aks/Aks/Commands/Constants.cs b/src/Aks/Aks/Commands/Constants.cs index aa09e9799e28..53df5468c604 100644 --- a/src/Aks/Aks/Commands/Constants.cs +++ b/src/Aks/Aks/Commands/Constants.cs @@ -41,5 +41,9 @@ public static class Constants }; public const string AddOnNameMonitoring = "Monitoring"; public const string AddOnNameVirtualNode = "VirtualNode"; + + internal const string DotNetApiParameterResourceGroupName = "resourceGroupName"; + internal const string DotNetApiParameterResourceName = "resourceName"; + internal const string DotNetApiParameterAgentPoolName = "agentPoolName"; } } \ No newline at end of file diff --git a/src/Aks/Aks/Commands/CreateOrUpdateKubeBase.cs b/src/Aks/Aks/Commands/CreateOrUpdateKubeBase.cs index 9184d3d60c52..b231439a6593 100644 --- a/src/Aks/Aks/Commands/CreateOrUpdateKubeBase.cs +++ b/src/Aks/Aks/Commands/CreateOrUpdateKubeBase.cs @@ -38,6 +38,8 @@ using Microsoft.Rest.Azure.OData; using Microsoft.Azure.Management.Internal.Resources.Models; using Microsoft.WindowsAzure.Commands.Common.CustomAttributes; +using Microsoft.Azure.Commands.Common.Exceptions; +using Microsoft.WindowsAzure.Commands.Common; namespace Microsoft.Azure.Commands.Aks { @@ -150,7 +152,7 @@ protected virtual ManagedCluster BuildNewCluster() new ContainerServiceLinuxProfile(LinuxProfileAdminUserName, new ContainerServiceSshConfiguration(pubKey)); - var acsServicePrincipal = EnsureServicePrincipal(ServicePrincipalIdAndSecret?.UserName, ServicePrincipalIdAndSecret?.Password?.ToString()); + var acsServicePrincipal = EnsureServicePrincipal(ServicePrincipalIdAndSecret?.UserName, ServicePrincipalIdAndSecret?.Password?.ConvertToString()); var spProfile = new ManagedClusterServicePrincipalProfile( acsServicePrincipal.SpId, @@ -218,8 +220,6 @@ protected void BeforeBuildNewCluster() /// The SSH key or file argument was null and there was no default pub key in path. protected string GetSshKey(string sshKeyOrFile) { - const string helpLink = "https://docs.microsoft.com/en-us/azure/virtual-machines/linux/mac-create-ssh-keys"; - // SSH key was specified as either a file or as key data if (!string.IsNullOrEmpty(SshKeyValue)) { @@ -237,7 +237,8 @@ protected string GetSshKey(string sshKeyOrFile) var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".ssh", "id_rsa.pub"); if (!AzureSession.Instance.DataStore.FileExists(path)) { - throw new ArgumentException(string.Format(Resources.CouldNotFindSshPublicKeyInError, path, helpLink)); + var errorMessage = string.Format(Resources.CouldNotFindSshPublicKeyInError, path); + throw new AzPSArgumentException(errorMessage, nameof(SshKeyValue)); } WriteVerbose(string.Format(Resources.FetchSshPublicKeyFromFile, path)); @@ -248,12 +249,22 @@ protected string GetSshKey(string sshKeyOrFile) protected AcsServicePrincipal EnsureServicePrincipal(string spId = null, string clientSecret = null) { + //If user specifies service principal, just use it directly and no need to save to disk + if(!string.IsNullOrEmpty(spId) && !string.IsNullOrEmpty(clientSecret)) + { + return new AcsServicePrincipal() + { + SpId = spId, + ClientSecret = clientSecret + }; + } + var acsServicePrincipal = LoadServicePrincipal(); if (acsServicePrincipal == null) { - WriteVerbose(string.Format( + WriteWarning(string.Format( Resources.NoServicePrincipalFoundCreatingANewServicePrincipal, - AcsSpFilePath)); + AcsSpFilePath, DefaultContext.Subscription.Id)); // if nothing to load, make one if (clientSecret == null) @@ -296,14 +307,16 @@ private AcsServicePrincipal BuildServicePrincipal(string name, string url, strin if (!success) { - throw new CmdletInvocationException(Resources.CouldNotCreateAServicePrincipalWithTheRightPermissionsAreYouAnOwner); + throw new AzPSInvalidOperationException( + Resources.CouldNotCreateAServicePrincipalWithTheRightPermissionsAreYouAnOwner, + desensitizedMessage: Resources.CouldNotCreateAServicePrincipalWithTheRightPermissionsAreYouAnOwner); } AddSubscriptionRoleAssignment("Contributor", sp.ObjectId); return new AcsServicePrincipal { SpId = app.AppId, ClientSecret = clientSecret, ObjectId = app.ObjectId }; } - protected void AddAcrRoleAssignment(string acrName, AcsServicePrincipal acsServicePrincipal) + protected void AddAcrRoleAssignment(string acrName, string acrParameterName, AcsServicePrincipal acsServicePrincipal) { string acrResourceId = null; try @@ -313,9 +326,12 @@ protected void AddAcrRoleAssignment(string acrName, AcsServicePrincipal acsServi var acrObjects = RmClient.Resources.List(acrQuery); acrResourceId = acrObjects.First().Id; } - catch(Exception ex) + catch(Exception) { - throw new CmdletInvocationException(string.Format(Resources.CouldNotFindSpecifiedAcr, acrName), ex); + throw new AzPSArgumentException( + string.Format(Resources.CouldNotFindSpecifiedAcr, acrName), + acrParameterName, + string.Format(Resources.CouldNotFindSpecifiedAcr, "*")); } var roleId = GetRoleId("acrpull", acrResourceId); @@ -331,7 +347,10 @@ protected void AddAcrRoleAssignment(string acrName, AcsServicePrincipal acsServi } catch(Exception ex) { - throw new CmdletInvocationException(string.Format(Resources.CouldNotFindObjectIdForServicePrincipal, acsServicePrincipal.SpId), ex); + throw new AzPSInvalidOperationException( + string.Format(Resources.CouldNotFindObjectIdForServicePrincipal, acsServicePrincipal.SpId), + ex, + string.Format(Resources.CouldNotFindObjectIdForServicePrincipal,"*")); } } var success = RetryAction(() => @@ -342,8 +361,9 @@ protected void AddAcrRoleAssignment(string acrName, AcsServicePrincipal acsServi if (!success) { - throw new CmdletInvocationException( - Resources.CouldNotAddAcrRoleAssignment); + throw new AzPSInvalidOperationException( + Resources.CouldNotAddAcrRoleAssignment, + desensitizedMessage: Resources.CouldNotAddAcrRoleAssignment); } } @@ -374,8 +394,9 @@ protected void AddSubscriptionRoleAssignment(string role, string appId) if (!success) { - throw new CmdletInvocationException( - Resources.CouldNotCreateAServicePrincipalWithTheRightPermissionsAreYouAnOwner); + throw new AzPSInvalidOperationException( + Resources.CouldNotAssignServicePrincipalWithSubsContributorPermission, + desensitizedMessage: Resources.CouldNotAssignServicePrincipalWithSubsContributorPermission); } } @@ -407,7 +428,11 @@ protected bool RetryAction(Action action, string actionName = null) protected AcsServicePrincipal LoadServicePrincipal() { var config = LoadServicePrincipals(); - return config?[DefaultContext.Subscription.Id]; + if(config?.ContainsKey(DefaultContext.Subscription.Id) == true) + { + return config[DefaultContext.Subscription.Id]; + } + return null; } protected Dictionary LoadServicePrincipals() diff --git a/src/Aks/Aks/Commands/DisableAzureRmAddons.cs b/src/Aks/Aks/Commands/DisableAzureRmAddons.cs index 20069e56032e..0fbef223b5aa 100644 --- a/src/Aks/Aks/Commands/DisableAzureRmAddons.cs +++ b/src/Aks/Aks/Commands/DisableAzureRmAddons.cs @@ -13,18 +13,15 @@ // ---------------------------------------------------------------------------------- +using System.Collections.Generic; +using System.Management.Automation; + using Microsoft.Azure.Commands.Aks.Models; using Microsoft.Azure.Commands.Aks.Properties; -using Microsoft.Azure.Commands.Aks.Utils; +using Microsoft.Azure.Commands.Common.Exceptions; using Microsoft.Azure.Management.ContainerService.Models; using Microsoft.WindowsAzure.Commands.Utilities.Common; -using System; -using System.Collections.Generic; -using System.Management.Automation; -using System.Runtime.ExceptionServices; -using System.Text; - namespace Microsoft.Azure.Commands.Aks.Commands { [Cmdlet(VerbsLifecycle.Disable, ResourceManager.Common.AzureRMConstants.AzureRMPrefix + "AksAddOn", DefaultParameterSetName = DefaultParamSet, SupportsShouldProcess = true)] @@ -38,11 +35,13 @@ protected override IDictionary UpdateAddonsP string addonServiceName = Constants.AddOnUserReadNameToServiceNameMapper.GetValueOrDefault(addOn, null); if (addonServiceName == null) { - throw new ArgumentException(string.Format(Resources.AddonNotDefined, addOn)); + var errorMessage = string.Format(Resources.AddonNotDefined, addOn, string.Join(",", Constants.AddOnUserReadNameToServiceNameMapper.Keys)); + throw new AzPSArgumentException(errorMessage, nameof(Name), desensitizedMessage: errorMessage); } if (!addonProfiles.ContainsKey(addonServiceName)) { - throw new ArgumentException(string.Format(Resources.AddonIsNotInstalled, addOn)); + var errorMessage = string.Format(Resources.AddonIsNotInstalled, addOn); + throw new AzPSArgumentException(errorMessage, nameof(Name), desensitizedMessage: errorMessage); } ManagedClusterAddonProfile addonProfile = addonProfiles[addonServiceName]; addonProfile.Config = null; diff --git a/src/Aks/Aks/Commands/EnableAzureRmAddons.cs b/src/Aks/Aks/Commands/EnableAzureRmAddons.cs index 2960ad138ae3..4bd4c5c5c9b4 100644 --- a/src/Aks/Aks/Commands/EnableAzureRmAddons.cs +++ b/src/Aks/Aks/Commands/EnableAzureRmAddons.cs @@ -13,16 +13,12 @@ // ---------------------------------------------------------------------------------- +using System.Collections.Generic; +using System.Management.Automation; + using Microsoft.Azure.Commands.Aks.Models; -using Microsoft.Azure.Commands.Aks.Properties; using Microsoft.Azure.Commands.Aks.Utils; using Microsoft.Azure.Management.ContainerService.Models; -using Microsoft.WindowsAzure.Commands.Utilities.Common; - -using System; -using System.Collections.Generic; -using System.Management.Automation; -using System.Text; namespace Microsoft.Azure.Commands.Aks.Commands { @@ -44,7 +40,7 @@ public class EnableAzureRmAddons : UpdateAddonsBase protected override IDictionary UpdateAddonsProfile(IDictionary addonProfiles) { - return AddonUtils.EnableAddonsProfile(addonProfiles, Name, WorkspaceResourceId, SubnetName); + return AddonUtils.EnableAddonsProfile(addonProfiles, Name, nameof(Name), WorkspaceResourceId, nameof(WorkspaceResourceId), SubnetName, nameof(SubnetName)); } } } diff --git a/src/Aks/Aks/Commands/GetAzureRmAks.cs b/src/Aks/Aks/Commands/GetAzureRmAks.cs index e66dcc82bb26..fa61a3e13121 100644 --- a/src/Aks/Aks/Commands/GetAzureRmAks.cs +++ b/src/Aks/Aks/Commands/GetAzureRmAks.cs @@ -12,15 +12,17 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -using System; +using System.Collections.Generic; using System.Linq; using System.Management.Automation; using Microsoft.Azure.Commands.Aks.Models; using Microsoft.Azure.Commands.Aks.Properties; +using Microsoft.Azure.Commands.Common.Exceptions; using Microsoft.Azure.Commands.ResourceManager.Common.ArgumentCompleters; using Microsoft.Azure.Management.ContainerService; using Microsoft.Azure.Management.Internal.Resources.Utilities.Models; +using Microsoft.Rest; using Microsoft.WindowsAzure.Commands.Common.CustomAttributes; namespace Microsoft.Azure.Commands.Aks @@ -77,28 +79,42 @@ public override void ExecuteCmdlet() RunCmdLet(() => { - switch (ParameterSetName) + try { - case NameParameterSet: - var kubeCluster = Client.ManagedClusters.Get(ResourceGroupName, Name); - WriteObject(PSMapper.Instance.Map(kubeCluster), true); - break; - case IdParameterSet: - var resource = new ResourceIdentifier(Id); - var idCluster = Client.ManagedClusters.Get(resource.ResourceGroupName, resource.ResourceName); - WriteObject(PSMapper.Instance.Map(idCluster), true); - break; - case ResourceGroupParameterSet: - var kubeClusterList = string.IsNullOrEmpty(ResourceGroupName) - ? ListPaged(() => Client.ManagedClusters.List(), - nextPageLink => Client.ManagedClusters.ListNext(nextPageLink)) - : ListPaged(() => Client.ManagedClusters.ListByResourceGroup(ResourceGroupName), - nextPageLink => Client.ManagedClusters.ListNext(nextPageLink)); + switch (ParameterSetName) + { + case NameParameterSet: + var kubeCluster = Client.ManagedClusters.Get(ResourceGroupName, Name); + WriteObject(PSMapper.Instance.Map(kubeCluster), true); + break; + case IdParameterSet: + var resource = new ResourceIdentifier(Id); + var idCluster = Client.ManagedClusters.Get(resource.ResourceGroupName, resource.ResourceName); + WriteObject(PSMapper.Instance.Map(idCluster), true); + break; + case ResourceGroupParameterSet: + var kubeClusterList = string.IsNullOrEmpty(ResourceGroupName) + ? ListPaged(() => Client.ManagedClusters.List(), + nextPageLink => Client.ManagedClusters.ListNext(nextPageLink)) + : ListPaged(() => Client.ManagedClusters.ListByResourceGroup(ResourceGroupName), + nextPageLink => Client.ManagedClusters.ListNext(nextPageLink)); - WriteObject(kubeClusterList.Select(PSMapper.Instance.Map), true); - break; - default: - throw new ArgumentException(Resources.ParameterSetError); + WriteObject(kubeClusterList.Select(PSMapper.Instance.Map), true); + break; + default: + throw new AzPSArgumentException(Resources.ParameterSetError, "InvalidParameterSet", null, Resources.ParameterSetError); + } + } + catch (ValidationException e) + { + var sdkApiParameterMap = new Dictionary() + { + { Constants.DotNetApiParameterResourceGroupName, new CmdletParameterNameValuePair(nameof(ResourceGroupName), ResourceGroupName) }, + { Constants.DotNetApiParameterResourceName, new CmdletParameterNameValuePair(nameof(Name), Name) }, + }; + + if (!HandleValidationException(e, sdkApiParameterMap)) + throw; } }); } diff --git a/src/Aks/Aks/Commands/GetAzureRmAksNodePool.cs b/src/Aks/Aks/Commands/GetAzureRmAksNodePool.cs index a18fdbb5c7ff..260a3beeedf0 100644 --- a/src/Aks/Aks/Commands/GetAzureRmAksNodePool.cs +++ b/src/Aks/Aks/Commands/GetAzureRmAksNodePool.cs @@ -12,6 +12,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- +using System.Collections.Generic; using System.Linq; using System.Management.Automation; @@ -19,6 +20,7 @@ using Microsoft.Azure.Commands.ResourceManager.Common.ArgumentCompleters; using Microsoft.Azure.Management.ContainerService; using Microsoft.Azure.Management.Internal.Resources.Utilities.Models; +using Microsoft.Rest; namespace Microsoft.Azure.Commands.Aks.Commands { @@ -64,7 +66,7 @@ public override void ExecuteCmdlet() case Constants.IdParameterSet: var resource = new ResourceIdentifier(Id); ResourceGroupName = resource.ResourceGroupName; - ClusterName = Utilities.GetParentResourceName(resource.ParentResource); + ClusterName = Utilities.GetParentResourceName(resource.ParentResource, nameof(Id)); Name = resource.ResourceName; break; case Constants.ParentObjectParameterSet: @@ -75,16 +77,31 @@ public override void ExecuteCmdlet() case Constants.NameParameterSet: break; } - if (string.IsNullOrEmpty(Name)) + try { - var pools = ListPaged(() => Client.AgentPools.List(ResourceGroupName, ClusterName), - nextPageLink => Client.AgentPools.ListNext(nextPageLink)); - WriteObject(pools.Select(PSMapper.Instance.Map), true); + if (string.IsNullOrEmpty(Name)) + { + var pools = ListPaged(() => Client.AgentPools.List(ResourceGroupName, ClusterName), + nextPageLink => Client.AgentPools.ListNext(nextPageLink)); + WriteObject(pools.Select(PSMapper.Instance.Map), true); + } + else + { + var pool = Client.AgentPools.Get(ResourceGroupName, ClusterName, Name); + WriteObject(PSMapper.Instance.Map(pool)); + } } - else + catch (ValidationException e) { - var pool = Client.AgentPools.Get(ResourceGroupName, ClusterName, Name); - WriteObject(PSMapper.Instance.Map(pool)); + var sdkApiParameterMap = new Dictionary() + { + { Constants.DotNetApiParameterResourceGroupName, new CmdletParameterNameValuePair(nameof(ResourceGroupName), ResourceGroupName) }, + { Constants.DotNetApiParameterResourceName, new CmdletParameterNameValuePair(nameof(ClusterName), ClusterName) }, + { Constants.DotNetApiParameterAgentPoolName, new CmdletParameterNameValuePair(nameof(Name), Name) }, + }; + + if (!HandleValidationException(e, sdkApiParameterMap)) + throw; } }); } diff --git a/src/Aks/Aks/Commands/ImportAzureRmAksCredential.cs b/src/Aks/Aks/Commands/ImportAzureRmAksCredential.cs index b537170caf27..6347c640656f 100644 --- a/src/Aks/Aks/Commands/ImportAzureRmAksCredential.cs +++ b/src/Aks/Aks/Commands/ImportAzureRmAksCredential.cs @@ -18,14 +18,17 @@ using System.Linq; using System.Management.Automation; using System.Text; -using Microsoft.Azure.Management.ContainerService; + using Microsoft.Azure.Commands.Aks.Models; using Microsoft.Azure.Commands.Aks.Properties; using Microsoft.Azure.Commands.ResourceManager.Common.ArgumentCompleters; +using Microsoft.Azure.Management.ContainerService; +using Microsoft.Azure.Management.ContainerService.Models; using Microsoft.Azure.Management.Internal.Resources.Utilities.Models; +using Microsoft.Rest; using Microsoft.WindowsAzure.Commands.Utilities.Common; + using YamlDotNet.RepresentationModel; -using Microsoft.Azure.Management.ContainerService.Models; namespace Microsoft.Azure.Commands.Aks { @@ -144,13 +147,27 @@ public override void ExecuteCmdlet() CredentialResult credentialResult = null; - if(Admin) + try { - credentialResult = Client.ManagedClusters.ListClusterAdminCredentials(ResourceGroupName, Name).Kubeconfigs[0]; + if (Admin) + { + credentialResult = Client.ManagedClusters.ListClusterAdminCredentials(ResourceGroupName, Name).Kubeconfigs[0]; + } + else + { + credentialResult = Client.ManagedClusters.ListClusterUserCredentials(ResourceGroupName, Name).Kubeconfigs[0]; + } } - else + catch (ValidationException e) { - credentialResult = Client.ManagedClusters.ListClusterUserCredentials(ResourceGroupName, Name).Kubeconfigs[0]; + var sdkApiParameterMap = new Dictionary() + { + { Constants.DotNetApiParameterResourceGroupName, new CmdletParameterNameValuePair(nameof(ResourceGroupName), ResourceGroupName) }, + { Constants.DotNetApiParameterResourceName, new CmdletParameterNameValuePair(nameof(Name), Name) }, + }; + + if (!HandleValidationException(e, sdkApiParameterMap)) + throw; } var decodedKubeConfig = diff --git a/src/Aks/Aks/Commands/InstallAzureRMAksKubectl.cs b/src/Aks/Aks/Commands/InstallAzureRMAksKubectl.cs index 8ee559f919d2..d9819d6e5250 100644 --- a/src/Aks/Aks/Commands/InstallAzureRMAksKubectl.cs +++ b/src/Aks/Aks/Commands/InstallAzureRMAksKubectl.cs @@ -18,7 +18,10 @@ using System.Net; using System.Runtime.InteropServices; using System.Text; + using Microsoft.Azure.Commands.Aks.Properties; +using Microsoft.Azure.Commands.Common; +using Microsoft.Azure.Commands.Common.Exceptions; using Microsoft.WindowsAzure.Commands.Utilities.Common; namespace Microsoft.Azure.Commands.Aks.Commands @@ -91,14 +94,20 @@ public override void ExecuteCmdlet() { if(fromMirror) { - throw new PSInvalidOperationException(Resources.NoKubectlForOsxOnMirror); + throw new AzPSArgumentException( + Resources.NoKubectlForOsxOnMirror, + nameof(DownloadFromMirror), + desensitizedMessage: Resources.NoKubectlForOsxOnMirror); } destFilePath = Path.Combine(Destination, KubecliString); sourceUrlBuilder.AppendFormat(KubecliPathFormat, Version, "darwin", KubecliString); } else { - throw new PSInvalidOperationException(Resources.NotSupportOnThisOs); + var ex = new PlatformNotSupportedException(Resources.NotSupportOnThisOs); + ex.Data[AzurePSErrorDataKeys.ErrorKindKey] = ErrorKind.UserError; + ex.Data[AzurePSErrorDataKeys.DesensitizedErrorMessageKey] = Resources.NotSupportOnThisOs; + throw ex; } bool fileExists = File.Exists(destFilePath); diff --git a/src/Aks/Aks/Commands/KubeCmdletBase.cs b/src/Aks/Aks/Commands/KubeCmdletBase.cs index 6e7b949c7124..34e70082c4f7 100644 --- a/src/Aks/Aks/Commands/KubeCmdletBase.cs +++ b/src/Aks/Aks/Commands/KubeCmdletBase.cs @@ -15,11 +15,14 @@ using System; using System.Collections.Generic; using System.IO; -using System.Management.Automation; +using System.Runtime.CompilerServices; +using Microsoft.Azure.Commands.Aks.Models; using Microsoft.Azure.Commands.Aks.Properties; +using Microsoft.Azure.Commands.Common; using Microsoft.Azure.Commands.Common.Authentication; using Microsoft.Azure.Commands.Common.Authentication.Abstractions; +using Microsoft.Azure.Commands.Common.Exceptions; using Microsoft.Azure.Commands.ResourceManager.Common; using Microsoft.Azure.Graph.RBAC.Version1_6; using Microsoft.Azure.Management.Authorization.Version2015_07_01; @@ -39,6 +42,12 @@ public abstract class KubeCmdletBase : AzureRMCmdlet private IGraphRbacManagementClient _graphClient; protected const string KubeNounStr = "AzureRmAks"; + protected const string NameValueFormatString = "{0}({1})"; + protected readonly IDictionary ValueRegexToReadingStringMap = new Dictionary + { + {"^[a-zA-Z0-9]$|^[a-zA-Z0-9][-_a-zA-Z0-9]{0,61}[a-zA-Z0-9]$", " The value should be starting and ending with alphanumeric, allowed characters are alphanumeric, underscore and hyphen in the middle of name, the total length is between 1 and 63." }, + {"^[a-z][a-z0-9]{0,11}$", " The value should be starting with lower case letter, followed by from 0 to at most 11 lower case letter or digit." } + }; protected IContainerServiceClient Client => _client ?? (_client = BuildClient()); @@ -67,10 +76,34 @@ protected void RunCmdLet(Action action) } catch (CloudException ex) { - if (string.Equals(ex.Body?.Code, "AgentPoolK8sVersionNotSupported", StringComparison.InvariantCultureIgnoreCase)) - throw new PSInvalidOperationException(Resources.K8sVersionNotSupported); + if (ex.Response.StatusCode == System.Net.HttpStatusCode.NotFound) + { + var newEx = new AzPSResourceNotFoundCloudException(ex.Message, innerException: ex) + { + Request = ex.Request, + Response = ex.Response, + Body = ex.Body, + }; + throw newEx; + } + else if (string.Equals(ex.Body?.Code, "AgentPoolK8sVersionNotSupported", StringComparison.InvariantCultureIgnoreCase)) + { + var newEx = new AzPSCloudException(Resources.K8sVersionNotSupported, Resources.K8sVersionNotSupported, ex) + { + Request = ex.Request, + Response = ex.Response, + Body = ex.Body, + }; + throw newEx; + } else - throw new PSInvalidOperationException(ex.Body.Message, ex); + { + if (!string.IsNullOrEmpty(ex.Body?.Code)) + { + ex.Data[AzurePSErrorDataKeys.CloudErrorCodeKey] = ex.Body.Code; + } + throw; + } } } @@ -104,5 +137,28 @@ protected static IList ListPaged( return resultsList; } + + internal bool HandleValidationException(ValidationException e, IDictionary parametersMap, [CallerFilePath] string callerFilePath = null) + { + if (string.IsNullOrEmpty(e.Target) || !parametersMap.ContainsKey(e.Target)) + { + e.Data[AzurePSErrorDataKeys.ErrorKindKey] = ErrorKind.UserError; + //ValidationException thrown by SDK doesn't contain sensitive information + e.Data[AzurePSErrorDataKeys.DesensitizedErrorMessageKey] = e.Message; + return false; + } + + var desensitizedMessage = e.Message?.Replace(e.Target, parametersMap[e.Target].Name); + var errorMessage = e.Message?.Replace(e.Target, NameValueFormatString.FormatInvariant(parametersMap[e.Target].Name, parametersMap[e.Target].Value ?? string.Empty)); + + foreach(var pair in ValueRegexToReadingStringMap) + { + if(errorMessage.Contains(pair.Key)) + { + errorMessage += pair.Value; + } + } + throw new AzPSArgumentException(errorMessage, parametersMap[e.Target].Name, desensitizedMessage, filePath: callerFilePath); + } } } \ No newline at end of file diff --git a/src/Aks/Aks/Commands/NewAzureRmAks.cs b/src/Aks/Aks/Commands/NewAzureRmAks.cs index 2da69f997e5a..5b9f8587374a 100644 --- a/src/Aks/Aks/Commands/NewAzureRmAks.cs +++ b/src/Aks/Aks/Commands/NewAzureRmAks.cs @@ -13,14 +13,19 @@ // ---------------------------------------------------------------------------------- using System; +using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; using System.IO; +using System.Linq; using System.Management.Automation; -using System.Runtime.InteropServices; using Microsoft.Azure.Commands.Aks.Models; using Microsoft.Azure.Commands.Aks.Properties; +using Microsoft.Azure.Commands.Common; +using Microsoft.Azure.Commands.Common.Exceptions; using Microsoft.Azure.Management.ContainerService; +using Microsoft.Rest; using Microsoft.WindowsAzure.Commands.Common.CustomAttributes; using Microsoft.WindowsAzure.Commands.Utilities.Common; @@ -37,7 +42,7 @@ public class NewAzureRmAks : NewKubeBase [Parameter( Mandatory = false, - HelpMessage = "Generate ssh key file to {HOME}/.ssh/id_rsa.")] + HelpMessage = "Generate ssh key file to folder {HOME}/.ssh/ using pre-installed ssh-keygen.")] public SwitchParameter GenerateSshKey { get; set; } public override void ExecuteCmdlet() @@ -50,9 +55,26 @@ public override void ExecuteCmdlet() { WriteVerbose(Resources.PreparingForDeploymentOfYourManagedKubernetesCluster); var managedCluster = BuildNewCluster(); - var cluster = Client.ManagedClusters.CreateOrUpdate(ResourceGroupName, Name, managedCluster); - var psObj = PSMapper.Instance.Map(cluster); - WriteObject(psObj); + try + { + var cluster = Client.ManagedClusters.CreateOrUpdate(ResourceGroupName, Name, managedCluster); + var psObj = PSMapper.Instance.Map(cluster); + WriteObject(psObj); + } + catch (ValidationException e) + { + var sdkApiParameterMap = new Dictionary() + { + { Constants.DotNetApiParameterResourceGroupName, new CmdletParameterNameValuePair(nameof(ResourceGroupName), ResourceGroupName) }, + { Constants.DotNetApiParameterResourceName, new CmdletParameterNameValuePair(nameof(Name), Name) }, + { "Name", new CmdletParameterNameValuePair(nameof(NodeName), managedCluster.AgentPoolProfiles.FirstOrDefault()?.Name) }, + }; + + if (!HandleValidationException(e, sdkApiParameterMap)) + { + throw; + } + } }; var msg = $"{Name} in {ResourceGroupName}"; @@ -81,81 +103,82 @@ private void PreValidate() { if ((this.IsParameterBound(c => c.NodeMinCount) || this.IsParameterBound(c => c.NodeMaxCount) || this.EnableNodeAutoScaling.IsPresent) && !(this.IsParameterBound(c => c.NodeMinCount) && this.IsParameterBound(c => c.NodeMaxCount) && this.EnableNodeAutoScaling.IsPresent)) - throw new PSInvalidCastException(Resources.AksNodePoolAutoScalingParametersMustAppearTogether); + { + throw new AzPSArgumentException( + Resources.AksNodePoolAutoScalingParametersMustAppearTogether, + nameof(EnableNodeAutoScaling), + desensitizedMessage: Resources.AksNodePoolAutoScalingParametersMustAppearTogether); + } if (this.IsParameterBound(c => c.GenerateSshKey) && this.IsParameterBound(c => c.SshKeyValue)) { - throw new ArgumentException(Resources.DonotUseGenerateSshKeyWithSshKeyValue); + throw new AzPSArgumentException(Resources.DonotUseGenerateSshKeyWithSshKeyValue, + nameof(GenerateSshKey), + desensitizedMessage: Resources.DonotUseGenerateSshKeyWithSshKeyValue); } if ((this.IsParameterBound(c => c.WindowsProfileAdminUserName) && !this.IsParameterBound(c => c.WindowsProfileAdminUserPassword)) || (!this.IsParameterBound(c => c.WindowsProfileAdminUserName) && this.IsParameterBound(c => c.WindowsProfileAdminUserPassword))) { - throw new ArgumentException(Resources.WindowsUserNameAndPasswordShouldAppearTogether); + throw new AzPSArgumentException( + Resources.WindowsUserNameAndPasswordShouldAppearTogether, + nameof(WindowsProfileAdminUserName), + desensitizedMessage: Resources.WindowsUserNameAndPasswordShouldAppearTogether); } if (this.IsParameterBound(c => c.WindowsProfileAdminUserName)) { if (!string.Equals(this.NetworkPlugin, "azure")) { - throw new ArgumentException(Resources.NetworkPluginShouldBeAzure); - } - } - } - - private void VerifySshKeyGenBinaryExist() - { - using (Process process = new Process()) - { - if ((RuntimeInformation.IsOSPlatform(OSPlatform.Windows))) - { - process.StartInfo.FileName = "where.exe"; - } - else - { - process.StartInfo.FileName = "whereis"; - } - process.StartInfo.Arguments = "ssh-keygen"; - process.StartInfo.UseShellExecute = false; - process.StartInfo.RedirectStandardOutput = true; - - process.Start(); - process.WaitForExit(); - - string result = process.StandardOutput.ReadLine(); - if (result.Contains("not found") || result.Contains("Could not find") || result.Trim().Equals("ssh-keygen:")) - { - throw new ArgumentException(Resources.EnableSsh); - } - - if (process.ExitCode != 0) - { - throw new ArgumentException(Resources.EnableSsh); + throw new AzPSArgumentException( + Resources.NetworkPluginShouldBeAzure, + nameof(NetworkPlugin), + desensitizedMessage: Resources.NetworkPluginShouldBeAzure); } } } private string GenerateSshKeyValue() { - VerifySshKeyGenBinaryExist(); String generateSshKeyPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".ssh", "id_rsa"); ; if (File.Exists(generateSshKeyPath)) { - throw new ArgumentException(string.Format(Resources.DefaultSshKeyAlreadyExist)); + throw new AzPSArgumentException( + string.Format(Resources.DefaultSshKeyAlreadyExist, generateSshKeyPath), + nameof(GenerateSshKey), + desensitizedMessage: string.Format(Resources.DefaultSshKeyAlreadyExist, "*")); } using (Process process = new Process()) { - process.StartInfo.FileName = "ssh-keygen"; - process.StartInfo.Arguments = String.Format("-f \"{0}\"", generateSshKeyPath); - process.StartInfo.UseShellExecute = false; - process.StartInfo.RedirectStandardInput = true; - process.StartInfo.RedirectStandardError = true; - process.StartInfo.RedirectStandardOutput = true; - process.Start(); - - Console.WriteLine(process.StandardOutput.ReadToEnd()); - - process.WaitForExit(); + try + { + process.StartInfo.FileName = "ssh-keygen"; + process.StartInfo.Arguments = String.Format("-f \"{0}\"", generateSshKeyPath); + process.StartInfo.UseShellExecute = false; + process.StartInfo.RedirectStandardInput = true; + process.StartInfo.RedirectStandardError = true; + process.StartInfo.RedirectStandardOutput = true; + string errorOutput = null; + process.ErrorDataReceived += new DataReceivedEventHandler((sender, e) => errorOutput += e.Data); + process.Start(); + + string standOutput = process.StandardOutput.ReadToEnd(); + if (!string.IsNullOrEmpty(standOutput)) + { + WriteDebug(standOutput); + } + process.WaitForExit(); + if (!string.IsNullOrEmpty(errorOutput)) + { + var errorMessage = string.Format(Resources.FailedToGenerateSshKey, errorOutput); + throw new AzPSInvalidOperationException(errorMessage, ErrorKind.InternalError); + } + } + catch(Win32Exception exception) + { + var message = string.Format(Resources.FailedToRunSshKeyGen, exception.Message); + throw new AzPSInvalidOperationException(message, ErrorKind.InternalError); + } } return GetSshKey(generateSshKeyPath); } diff --git a/src/Aks/Aks/Commands/NewAzureRmAksNodePool.cs b/src/Aks/Aks/Commands/NewAzureRmAksNodePool.cs index 65f9924c3d3d..96878c312e5f 100644 --- a/src/Aks/Aks/Commands/NewAzureRmAksNodePool.cs +++ b/src/Aks/Aks/Commands/NewAzureRmAksNodePool.cs @@ -14,8 +14,10 @@ using System; using System.Management.Automation; + using Microsoft.Azure.Commands.Aks.Models; using Microsoft.Azure.Commands.Aks.Properties; +using Microsoft.Azure.Commands.Common.Exceptions; using Microsoft.Azure.Commands.ResourceManager.Common; using Microsoft.Azure.Commands.ResourceManager.Common.ArgumentCompleters; using Microsoft.Azure.Management.ContainerService; @@ -107,7 +109,10 @@ public override void ExecuteCmdlet() var msg = $"{Name} for {ClusterName} in {ResourceGroupName}"; if (GetAgentPoolObject() != null) - throw new PSInvalidOperationException(Resources.AgentPoolAlreadyExistsError); + throw new AzPSArgumentException( + Resources.AgentPoolAlreadyExistsError, + nameof(Name), + desensitizedMessage: Resources.AgentPoolAlreadyExistsError); if (ShouldProcess(msg, Resources.CreatingClusterAgentPool)) { @@ -186,15 +191,28 @@ private void PreValidate() if (string.Equals(this.OsType, "Windows")) { if (VmSetType != "VirtualMachineScaleSets") - throw new PSInvalidOperationException(Resources.VmSetTypeIsIncorrectForWindowsPool); + { + throw new AzPSArgumentException( + Resources.VmSetTypeIsIncorrectForWindowsPool, + nameof(VmSetType), + desensitizedMessage: Resources.VmSetTypeIsIncorrectForWindowsPool); + } if (Name?.Length > 6) - throw new PSInvalidOperationException(Resources.WindowsNodePoolNameLengthLimitation); + { + throw new AzPSArgumentException( + Resources.WindowsNodePoolNameLengthLimitation, + nameof(Name), + desensitizedMessage: Resources.WindowsNodePoolNameLengthLimitation); + } } if ((this.IsParameterBound(c => c.MinCount) || this.IsParameterBound(c => c.MaxCount) || this.EnableAutoScaling.IsPresent) && !(this.IsParameterBound(c => c.MinCount) && this.IsParameterBound(c => c.MaxCount) && this.EnableAutoScaling.IsPresent)) - throw new PSInvalidCastException(Resources.NodePoolAutoScalingParametersMustAppearTogether); + throw new AzPSArgumentException( + Resources.NodePoolAutoScalingParametersMustAppearTogether, + nameof(EnableAutoScaling), + desensitizedMessage: Resources.NodePoolAutoScalingParametersMustAppearTogether); } } } diff --git a/src/Aks/Aks/Commands/NewKubeBase.cs b/src/Aks/Aks/Commands/NewKubeBase.cs index 523cb4ba07a4..3c5b3ce98b69 100644 --- a/src/Aks/Aks/Commands/NewKubeBase.cs +++ b/src/Aks/Aks/Commands/NewKubeBase.cs @@ -111,7 +111,7 @@ public abstract class NewKubeBase : CreateOrUpdateKubeBase [Parameter(Mandatory = false, HelpMessage = "The administrator password to use for Windows VMs. Password requirement:" + "At least one lower case, one upper case, one special character !@#$%^&*(), the minimum lenth is 12.")] - [ValidateSecureString(RegularExpression = "^(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%\\^&\\*\\(\\)])[a-zA-Z\\d!@#$%\\^&\\*\\(\\)]{12,123}$")] + [ValidateSecureString(RegularExpression = "^(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%\\^&\\*\\(\\)])[a-zA-Z\\d!@#$%\\^&\\*\\(\\)]{12,123}$", ParameterName = nameof(WindowsProfileAdminUserPassword))] public SecureString WindowsProfileAdminUserPassword { get; set; } [Parameter(Mandatory = false, HelpMessage = "Network plugin used for building Kubernetes network.")] @@ -176,7 +176,7 @@ protected override ManagedCluster BuildNewCluster() if(this.IsParameterBound(c => c.AcrNameToAttach)) { - AddAcrRoleAssignment(AcrNameToAttach, acsServicePrincipal); + AddAcrRoleAssignment(AcrNameToAttach, nameof(AcrNameToAttach), acsServicePrincipal); } return managedCluster; @@ -263,7 +263,7 @@ private IDictionary CreateAddonsProfiles() if (this.IsParameterBound(c => c.AddOnNameToBeEnabled)) { Dictionary addonProfiles = new Dictionary(); - return AddonUtils.EnableAddonsProfile(addonProfiles, AddOnNameToBeEnabled, WorkspaceResourceId, SubnetName); + return AddonUtils.EnableAddonsProfile(addonProfiles, AddOnNameToBeEnabled, nameof(AddOnNameToBeEnabled), WorkspaceResourceId, nameof(WorkspaceResourceId), SubnetName, nameof(SubnetName)); } else { return null; diff --git a/src/Aks/Aks/Commands/RemoveAzureRmAksNodePool.cs b/src/Aks/Aks/Commands/RemoveAzureRmAksNodePool.cs index 82002335686e..2ce08a1cd9bd 100644 --- a/src/Aks/Aks/Commands/RemoveAzureRmAksNodePool.cs +++ b/src/Aks/Aks/Commands/RemoveAzureRmAksNodePool.cs @@ -92,13 +92,13 @@ public override void ExecuteCmdlet() case Constants.IdParameterSet: resource = new ResourceIdentifier(Id); ResourceGroupName = resource.ResourceGroupName; - ClusterName = Utilities.GetParentResourceName(resource.ParentResource); + ClusterName = Utilities.GetParentResourceName(resource.ParentResource, nameof(Id)); Name = resource.ResourceName; break; case Constants.InputObjectParameterSet: resource = new ResourceIdentifier(InputObject.Id); ResourceGroupName = resource.ResourceGroupName; - ClusterName = Utilities.GetParentResourceName(resource.ParentResource); + ClusterName = Utilities.GetParentResourceName(resource.ParentResource, nameof(InputObject)); Name = resource.ResourceName; break; } diff --git a/src/Aks/Aks/Commands/SetAzureRmAks.cs b/src/Aks/Aks/Commands/SetAzureRmAks.cs index 16200a450985..9c08152c3bff 100644 --- a/src/Aks/Aks/Commands/SetAzureRmAks.cs +++ b/src/Aks/Aks/Commands/SetAzureRmAks.cs @@ -15,13 +15,16 @@ using System.Collections.Generic; using System.Linq; using System.Management.Automation; + using Microsoft.Azure.Commands.Aks.Models; using Microsoft.Azure.Commands.Aks.Properties; +using Microsoft.Azure.Commands.Common.Exceptions; using Microsoft.Azure.Commands.ResourceManager.Common.ArgumentCompleters; using Microsoft.Azure.Commands.ResourceManager.Common.Tags; using Microsoft.Azure.Management.ContainerService; using Microsoft.Azure.Management.ContainerService.Models; using Microsoft.Azure.Management.Internal.Resources.Utilities.Models; +using Microsoft.WindowsAzure.Commands.Common; using Microsoft.WindowsAzure.Commands.Common.CustomAttributes; using Microsoft.WindowsAzure.Commands.Utilities.Common; @@ -99,7 +102,10 @@ public override void ExecuteCmdlet() if (this.IsParameterBound(c => c.Location)) { - throw new CmdletInvocationException(Resources.LocationCannotBeUpdateForExistingCluster); + throw new AzPSArgumentException( + Resources.LocationCannotBeUpdateForExistingCluster, + nameof(Location), + desensitizedMessage: Resources.LocationCannotBeUpdateForExistingCluster); } if (this.IsParameterBound(c => c.DnsNamePrefix)) @@ -119,7 +125,7 @@ public override void ExecuteCmdlet() if (this.IsParameterBound(c => c.ServicePrincipalIdAndSecret)) { WriteVerbose(Resources.UpdatingServicePrincipal); - var acsServicePrincipal = EnsureServicePrincipal(ServicePrincipalIdAndSecret.UserName, ServicePrincipalIdAndSecret.Password.ToString()); + var acsServicePrincipal = EnsureServicePrincipal(ServicePrincipalIdAndSecret.UserName, ServicePrincipalIdAndSecret.Password?.ConvertToString()); var spProfile = new ManagedClusterServicePrincipalProfile( acsServicePrincipal.SpId, @@ -149,7 +155,10 @@ public override void ExecuteCmdlet() } else { - throw new PSArgumentException(Resources.SpecifiedAgentPoolDoesNotExist); + throw new AzPSArgumentException( + Resources.SpecifiedAgentPoolDoesNotExist, + nameof(Name), + desensitizedMessage: Resources.SpecifiedAgentPoolDoesNotExist); } if (this.IsParameterBound(c => c.NodeMinCount)) diff --git a/src/Aks/Aks/Commands/StartAzureRmAksDashboard.cs b/src/Aks/Aks/Commands/StartAzureRmAksDashboard.cs index 6079864452c6..28644248d867 100644 --- a/src/Aks/Aks/Commands/StartAzureRmAksDashboard.cs +++ b/src/Aks/Aks/Commands/StartAzureRmAksDashboard.cs @@ -12,20 +12,19 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -using System; -using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Management.Automation; using System.Runtime.InteropServices; using System.Text; -using System.Threading; -using Microsoft.Azure.Commands.Common.Authentication; -using Microsoft.Azure.Management.ContainerService; + using Microsoft.Azure.Commands.Aks.Models; using Microsoft.Azure.Commands.Aks.Properties; +using Microsoft.Azure.Commands.Common.Authentication; +using Microsoft.Azure.Commands.Common.Exceptions; using Microsoft.Azure.Commands.ResourceManager.Common.ArgumentCompleters; +using Microsoft.Azure.Management.ContainerService; using Microsoft.Azure.Management.Internal.Resources.Utilities.Models; using Microsoft.WindowsAzure.Commands.Utilities.Common; @@ -120,7 +119,7 @@ public override void ExecuteCmdlet() RunCmdLet(() => { if (!GeneralUtilities.Probe("kubectl")) - throw new CmdletInvocationException(Resources.KubectlIsRequriedToBeInstalledAndOnYourPathToExecute); + throw new AzPSApplicationException(Resources.KubectlIsRequriedToBeInstalledAndOnYourPathToExecute); var tmpFileName = Path.GetTempFileName(); var credentials = Client.ManagedClusters.ListClusterAdminCredentials(ResourceGroupName, Name).Kubeconfigs; diff --git a/src/Aks/Aks/Commands/UpdateAzureRmAksNodePool.cs b/src/Aks/Aks/Commands/UpdateAzureRmAksNodePool.cs index cd903ba979cb..33fe109cf5cb 100644 --- a/src/Aks/Aks/Commands/UpdateAzureRmAksNodePool.cs +++ b/src/Aks/Aks/Commands/UpdateAzureRmAksNodePool.cs @@ -77,7 +77,7 @@ public override void ExecuteCmdlet() case Constants.IdParameterSet: resource = new ResourceIdentifier(Id); ResourceGroupName = resource.ResourceGroupName; - ClusterName = Utilities.GetParentResourceName(resource.ParentResource); + ClusterName = Utilities.GetParentResourceName(resource.ParentResource, nameof(Id)); Name = resource.ResourceName; break; case Constants.InputObjectParameterSet: @@ -85,7 +85,7 @@ public override void ExecuteCmdlet() pool = PSMapper.Instance.Map(InputObject); resource = new ResourceIdentifier(pool.Id); ResourceGroupName = resource.ResourceGroupName; - ClusterName = Utilities.GetParentResourceName(resource.ParentResource); + ClusterName = Utilities.GetParentResourceName(resource.ParentResource, nameof(InputObject)); Name = resource.ResourceName; break; case Constants.ParentObjectParameterSet: diff --git a/src/Aks/Aks/Commands/Utilities.cs b/src/Aks/Aks/Commands/Utilities.cs index c06630facd3e..af1cce97418e 100644 --- a/src/Aks/Aks/Commands/Utilities.cs +++ b/src/Aks/Aks/Commands/Utilities.cs @@ -14,14 +14,19 @@ using System; +using Microsoft.Azure.Commands.Common.Exceptions; + namespace Microsoft.Azure.Commands.Aks.Commands { internal static class Utilities { - public static string GetParentResourceName(string parentResource) + public static string GetParentResourceName(string parentResource, string parameterSource) { if (string.IsNullOrWhiteSpace(parentResource)) - throw new ArgumentNullException("parentResource"); + throw new AzPSArgumentNullException( + Properties.Resources.ParentResourceMustNotBeEmpty, + parameterSource, + desensitizedMessage: Properties.Resources.ParentResourceMustNotBeEmpty); var items = parentResource.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); diff --git a/src/Aks/Aks/Models/CmdletParameterNameValuePair.cs b/src/Aks/Aks/Models/CmdletParameterNameValuePair.cs new file mode 100644 index 000000000000..cc62a2092125 --- /dev/null +++ b/src/Aks/Aks/Models/CmdletParameterNameValuePair.cs @@ -0,0 +1,28 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +namespace Microsoft.Azure.Commands.Aks.Models +{ + internal struct CmdletParameterNameValuePair + { + public CmdletParameterNameValuePair(string name, object value) + { + Name = name; + Value = value; + } + + public string Name { get; set; } + public object Value { get; set; } + } +} diff --git a/src/Aks/Aks/Models/PSManagedClusterLoadBalancerProfileManagedOutboundIPs.cs b/src/Aks/Aks/Models/PSManagedClusterLoadBalancerProfileManagedOutboundIPs.cs index 1991c7d4a1d1..7a19cbaffb4d 100644 --- a/src/Aks/Aks/Models/PSManagedClusterLoadBalancerProfileManagedOutboundIPs.cs +++ b/src/Aks/Aks/Models/PSManagedClusterLoadBalancerProfileManagedOutboundIPs.cs @@ -54,23 +54,5 @@ public PSManagedClusterLoadBalancerProfileManagedOutboundIPs() /// of 1 to 100 (inclusive). The default value is 1. /// public int? Count { get; set; } - - /// - /// Validate the object. - /// - /// - /// Thrown if validation fails - /// - public virtual void Validate() - { - if (Count > 100) - { - throw new ValidationException(ValidationRules.InclusiveMaximum, "Count", 100); - } - if (Count < 1) - { - throw new ValidationException(ValidationRules.InclusiveMinimum, "Count", 1); - } - } } } diff --git a/src/Aks/Aks/Properties/Resources.Designer.cs b/src/Aks/Aks/Properties/Resources.Designer.cs index 8d4773c7fd23..7fd0b1c8b066 100644 --- a/src/Aks/Aks/Properties/Resources.Designer.cs +++ b/src/Aks/Aks/Properties/Resources.Designer.cs @@ -70,7 +70,7 @@ internal static string AddDirectoryToPath { } /// - /// Looks up a localized string similar to Addon {0} is not installed.. + /// Looks up a localized string similar to Add-on {0} is not installed for this cluster.. /// internal static string AddonIsNotInstalled { get { @@ -79,7 +79,7 @@ internal static string AddonIsNotInstalled { } /// - /// Looks up a localized string similar to Addon Monitoring should work with WorkspaceResourceId.. + /// Looks up a localized string similar to WorkspaceResourceId must not be null when enabling add-on Monitoring.. /// internal static string AddonMonitoringShouldWorkWithWorkspaceResourceId { get { @@ -88,7 +88,7 @@ internal static string AddonMonitoringShouldWorkWithWorkspaceResourceId { } /// - /// Looks up a localized string similar to Addon {0} is not defined.. + /// Looks up a localized string similar to Add-on {0} is not defined. The accepted add-on names are {1}.. /// internal static string AddonNotDefined { get { @@ -97,7 +97,7 @@ internal static string AddonNotDefined { } /// - /// Looks up a localized string similar to Addon VirtualNode should work with SubnetName.. + /// Looks up a localized string similar to SubnetName must not be null when enabling add-on VirtualNode.. /// internal static string AddonVirtualNodeShouldWorkWithSubnetName { get { @@ -214,7 +214,16 @@ internal static string CouldNotAddAcrRoleAssignment { } /// - /// Looks up a localized string similar to Could not create a service principal with the right permissions. Are you an Owner on this project?. + /// Looks up a localized string similar to Could not assign subscription contributor permission to service principal just created. Please make sure you have permission to assign subscription contributor role, or you could use parameter -ClientIdAndSecret to specify one existing service principal id and secret.. + /// + internal static string CouldNotAssignServicePrincipalWithSubsContributorPermission { + get { + return ResourceManager.GetString("CouldNotAssignServicePrincipalWithSubsContributorPermission", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Could not create a service principal. Do you have enough permission to create service principal? Or you could use parameter -ClientIdAndSecret to specify one existing service principal id and secret.. /// internal static string CouldNotCreateAServicePrincipalWithTheRightPermissionsAreYouAnOwner { get { @@ -241,7 +250,7 @@ internal static string CouldNotFindSpecifiedAcr { } /// - /// Looks up a localized string similar to Could not find SSH public key in {0}. See {1} for help generating a key pair.. + /// Looks up a localized string similar to Could not find SSH public key in default path '{0}' which is required for creating k8s cluster. See https://docs.microsoft.com/en-us/azure/virtual-machines/linux/mac-create-ssh-keys or https://docs.microsoft.com/en-us/azure/virtual-machines/linux/ssh-from-windows for generating a key pair manually; or you could try to add switch parameter -GenerateSshKey during calling New-AzAksCluster which will automatically generate SSH key file if you have ssh-keygen installed.. /// internal static string CouldNotFindSshPublicKeyInError { get { @@ -277,7 +286,7 @@ internal static string CreatingClusterAgentPool { } /// - /// Looks up a localized string similar to Default ssh key already exists. Please use -SshKeyVaule.. + /// Looks up a localized string similar to Default ssh key file {0} already exists. Please use parameter -SshKeyVaule '{0}' instead of -GenerateSshKey.. /// internal static string DefaultSshKeyAlreadyExist { get { @@ -376,11 +385,20 @@ internal static string DoYouWantToOverwriteExistingFile { } /// - /// Looks up a localized string similar to Cannot find ssh-keygen. Please enable OpenSSH on your local machine.. + /// Looks up a localized string similar to Failed to generate SSH key with detail error: {0}.\nPlease create one issue at https://github.com/Azure/azure-powershell/issues if issue remains.. + /// + internal static string FailedToGenerateSshKey { + get { + return ResourceManager.GetString("FailedToGenerateSshKey", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Failed to run 'ssh-keygen' to generate SSH key with detail error: {0}. \nPlease make sure 'ssh-keygen' is runable from cmd on Windows or shell on Linux/MacOS, usually you just need to install OpenSSH client which contains 'ssh-keygen', then restart PowerShell and try again.. /// - internal static string EnableSsh { + internal static string FailedToRunSshKeyGen { get { - return ResourceManager.GetString("EnableSsh", resourceCulture); + return ResourceManager.GetString("FailedToRunSshKeyGen", resourceCulture); } } @@ -502,7 +520,7 @@ internal static string NoKubectlForOsxOnMirror { } /// - /// Looks up a localized string similar to No Service Principal found in {0} for this subscription. Creating a new Service Principal.. + /// Looks up a localized string similar to No Service Principal found in {0} for current subscription {1}. Trying to create a new Service Principal with Contributor role for the subscription.. /// internal static string NoServicePrincipalFoundCreatingANewServicePrincipal { get { @@ -528,6 +546,15 @@ internal static string ParameterSetError { } } + /// + /// Looks up a localized string similar to Parent source must not be null or empty.. + /// + internal static string ParentResourceMustNotBeEmpty { + get { + return ResourceManager.GetString("ParentResourceMustNotBeEmpty", resourceCulture); + } + } + /// /// Looks up a localized string similar to pid doesn't exist or job is already dead. /// diff --git a/src/Aks/Aks/Properties/Resources.resx b/src/Aks/Aks/Properties/Resources.resx index c1c91ed84d72..1cb3c230d8bc 100644 --- a/src/Aks/Aks/Properties/Resources.resx +++ b/src/Aks/Aks/Properties/Resources.resx @@ -136,10 +136,10 @@ Using SSH public key data as command line string. - Could not find SSH public key in {0}. See {1} for help generating a key pair. + Could not find SSH public key in default path '{0}' which is required for creating k8s cluster. See https://docs.microsoft.com/en-us/azure/virtual-machines/linux/mac-create-ssh-keys or https://docs.microsoft.com/en-us/azure/virtual-machines/linux/ssh-from-windows for generating a key pair manually; or you could try to add switch parameter -GenerateSshKey during calling New-AzAksCluster which will automatically generate SSH key file if you have ssh-keygen installed. - No Service Principal found in {0} for this subscription. Creating a new Service Principal. + No Service Principal found in {0} for current subscription {1}. Trying to create a new Service Principal with Contributor role for the subscription. Created a new Service Principal and assigned the contributor role for this subscription. @@ -148,7 +148,7 @@ Service Principal Create - Could not create a service principal with the right permissions. Are you an Owner on this project? + Could not create a service principal. Do you have enough permission to create service principal? Or you could use parameter -ClientIdAndSecret to specify one existing service principal id and secret. Cluster exists: {0} @@ -370,16 +370,13 @@ Windows agent pool name can not be longer than 6 characters. - Add-on Monitoring should work with WorkspaceResourceId. + WorkspaceResourceId must not be null when enabling add-on Monitoring. Don't use -GenerateSshKey and -SshKeyVaule at the same time. - Default ssh key already exists. Please use -SshKeyVaule. - - - Cannot find ssh-keygen. Please enable OpenSSH on your local machine. + Default ssh key file {0} already exists. Please use parameter -SshKeyVaule '{0}' instead of -GenerateSshKey. Updating NodePoolMode. @@ -393,16 +390,25 @@ NetworkPlugin must be azure if you want to use Windows. - - - - Add-on {0} is not installed. + Add-on {0} is not installed for this cluster. - Add-on {0} is not defined. + Add-on {0} is not defined. The accepted add-on names are {1}. - Add-on VirtualNode should work with SubnetName. + SubnetName must not be null when enabling add-on VirtualNode. + + + Parent source must not be null or empty. + + + Failed to run 'ssh-keygen' to generate SSH key with detail error: {0}. \nPlease make sure 'ssh-keygen' is runable from cmd on Windows or shell on Linux/MacOS, usually you just need to install OpenSSH client which contains 'ssh-keygen', then restart PowerShell and try again. + + + Failed to generate SSH key with detail error: {0}.\nPlease create one issue at https://github.com/Azure/azure-powershell/issues if issue remains. + + + Could not assign subscription contributor permission to service principal just created. Please make sure you have permission to assign subscription contributor role, or you could use parameter -ClientIdAndSecret to specify one existing service principal id and secret. \ No newline at end of file diff --git a/src/Aks/Aks/Utils/AddonUtils.cs b/src/Aks/Aks/Utils/AddonUtils.cs index 06d8b481e0ea..6bbe07e27bc9 100644 --- a/src/Aks/Aks/Utils/AddonUtils.cs +++ b/src/Aks/Aks/Utils/AddonUtils.cs @@ -12,14 +12,13 @@ // limitations under the License. // ---------------------------------------------------------------------------------- +using System.Collections.Generic; + using Microsoft.Azure.Commands.Aks.Properties; +using Microsoft.Azure.Commands.Common.Exceptions; using Microsoft.Azure.Management.ContainerService.Models; using Microsoft.WindowsAzure.Commands.Utilities.Common; -using System; -using System.Collections.Generic; -using System.Text; - namespace Microsoft.Azure.Commands.Aks.Utils { static class AddonUtils @@ -29,24 +28,32 @@ public static string TrimWorkspaceResourceId(string workspaceResourceId) return string.Format("/{0}", workspaceResourceId.Trim().Trim('/')); } - public static IDictionary EnableAddonsProfile(IDictionary addonProfiles, string[] addOnName, string workspaceResourceId, string subnetName) + public static IDictionary EnableAddonsProfile( + IDictionary addonProfiles, + string[] addOnParameterValue, string addOnParameterName, + string workspaceResourceIdValue, string workspaceResourceIdParameterName, + string subnetNameValue, string subnetNameParameterName) { - foreach (var addOn in addOnName) + foreach (var addOn in addOnParameterValue) { if (addOn.Equals(Constants.AddOnNameMonitoring)) { - addonProfiles = EnableAddonMonitoring(addonProfiles, workspaceResourceId); + addonProfiles = EnableAddonMonitoring(addonProfiles, workspaceResourceIdValue, workspaceResourceIdParameterName); } else if (addOn.Equals(Constants.AddOnNameVirtualNode)) { - addonProfiles = EnableAddonVirtualNode(addonProfiles, subnetName); + addonProfiles = EnableAddonVirtualNode(addonProfiles, subnetNameValue, subnetNameParameterName); } else { string addonServiceName = Constants.AddOnUserReadNameToServiceNameMapper.GetValueOrDefault(addOn, null); if (addonServiceName == null) { - throw new ArgumentException(string.Format(Resources.AddonNotDefined, addOn)); + var message = string.Format(Resources.AddonNotDefined, addOn, string.Join(",", Constants.AddOnUserReadNameToServiceNameMapper.Keys)); + throw new AzPSArgumentException( + message, + addOnParameterName, + desensitizedMessage: message); } ManagedClusterAddonProfile addonProfile = new ManagedClusterAddonProfile(true, null); addonProfiles = EnableAddonsProfile(addonProfiles, addonServiceName, addonProfile); @@ -63,33 +70,43 @@ private static IDictionary EnableAddonsProfi return addonProfiles; } - private static IDictionary EnableAddonMonitoring(IDictionary addonProfiles, string workspaceResourceId) + private static IDictionary EnableAddonMonitoring( + IDictionary addonProfiles, + string workspaceResourceIdValue, string workspaceResourceIdParameterName) { - if (workspaceResourceId == null) + if (workspaceResourceIdValue == null) { - throw new ArgumentException(Resources.AddonMonitoringShouldWorkWithWorkspaceResourceId); + throw new AzPSArgumentException( + Resources.AddonMonitoringShouldWorkWithWorkspaceResourceId, + workspaceResourceIdParameterName, + desensitizedMessage: Resources.AddonMonitoringShouldWorkWithWorkspaceResourceId); } string addonServiceName = Constants.AddOnUserReadNameToServiceNameMapper.GetValueOrDefault(Constants.AddOnNameMonitoring, null); Dictionary config = new Dictionary { - { "logAnalyticsWorkspaceResourceID", TrimWorkspaceResourceId(workspaceResourceId) } + { "logAnalyticsWorkspaceResourceID", TrimWorkspaceResourceId(workspaceResourceIdValue) } }; ManagedClusterAddonProfile addonProfile = new ManagedClusterAddonProfile(true, config); addonProfiles = EnableAddonsProfile(addonProfiles, addonServiceName, addonProfile); return addonProfiles; } - private static IDictionary EnableAddonVirtualNode(IDictionary addonProfiles, string subnetName) + private static IDictionary EnableAddonVirtualNode( + IDictionary addonProfiles, + string subnetNameValue, string subnetNameParameterName) { - if (subnetName == null) + if (subnetNameValue == null) { - throw new ArgumentException(Resources.AddonVirtualNodeShouldWorkWithSubnetName); + throw new AzPSArgumentNullException( + Resources.AddonVirtualNodeShouldWorkWithSubnetName, + subnetNameParameterName, + desensitizedMessage: Resources.AddonVirtualNodeShouldWorkWithSubnetName); } string osType = "Linux"; string addonServiceName = string.Format("{0}{1}", Constants.AddOnUserReadNameToServiceNameMapper.GetValueOrDefault(Constants.AddOnNameVirtualNode, null), osType); Dictionary config = new Dictionary { - { "SubnetName", subnetName } + { "SubnetName", subnetNameValue } }; ManagedClusterAddonProfile addonProfile = new ManagedClusterAddonProfile(true, config); addonProfiles = EnableAddonsProfile(addonProfiles, addonServiceName, addonProfile); diff --git a/src/Aks/Aks/Utils/ValidateSecureString.cs b/src/Aks/Aks/Utils/ValidateSecureString.cs index ad6945600d54..df9b5e422efb 100644 --- a/src/Aks/Aks/Utils/ValidateSecureString.cs +++ b/src/Aks/Aks/Utils/ValidateSecureString.cs @@ -13,21 +13,22 @@ // ---------------------------------------------------------------------------------- -using Microsoft.Azure.Commands.Aks.Properties; -using Microsoft.WindowsAzure.Commands.Common; - -using System; using System.Management.Automation; -using System.Runtime.InteropServices; using System.Security; using System.Text.RegularExpressions; +using Microsoft.Azure.Commands.Aks.Properties; +using Microsoft.Azure.Commands.Common.Exceptions; +using Microsoft.WindowsAzure.Commands.Common; + namespace Microsoft.Azure.Commands.Aks.Utils { public sealed class ValidateSecureString: ValidateEnumeratedArgumentsAttribute { public string RegularExpression { get; set; } + public string ParameterName { get; set; } + protected override void ValidateElement(object element) { SecureString secureString = element as SecureString; @@ -35,7 +36,9 @@ protected override void ValidateElement(object element) Regex regex = new Regex(RegularExpression); if (!regex.IsMatch(content)) { - throw new ArgumentException(string.Format(Resources.SecureStringNotValid, RegularExpression)); + throw new AzPSArgumentException( + string.Format(Resources.SecureStringNotValid, RegularExpression), + ParameterName); } } }