diff --git a/examples/lib/loginService.sh b/examples/lib/loginService.sh new file mode 100644 index 000000000000..86fd58f2aaec --- /dev/null +++ b/examples/lib/loginService.sh @@ -0,0 +1,2 @@ +#!/bin/bash +azure account add --spn --appid "$spn" --secret "$secret" -t "$tenant" -s "$subscription" \ No newline at end of file diff --git a/examples/lib/loginUser.sh b/examples/lib/loginUser.sh new file mode 100644 index 000000000000..fd442b568070 --- /dev/null +++ b/examples/lib/loginUser.sh @@ -0,0 +1,2 @@ +#!/bin/bash +azure account add -u "$azureUser" -p "$password" -s "$subscription" \ No newline at end of file diff --git a/examples/resource-management/01-ResourceGroups.sh b/examples/resource-management/01-ResourceGroups.sh index f03dc815d7ec..8673f807ea0b 100644 --- a/examples/resource-management/01-ResourceGroups.sh +++ b/examples/resource-management/01-ResourceGroups.sh @@ -3,13 +3,13 @@ set -e printf "\n=== Managing Resource Groups in Azure ===\n" printf "\n1. Creating a new resource group: %s and location: %s.\n" "$groupName" "$location" -azure group create --name "$groupName" --location "$location" +azure group create -n "$groupName" --location "$location" printf "\n2. Updating the group %s with tags.\n" "$groupName" -azure group set --name "$groupName" --tags "[{\"Value\":\"testval\",\"Name\":\"testtag\"}]" +azure group set -n "$groupName" --tags "[{\"Value\":\"testval\",\"Name\":\"testtag\"}]" printf "\n3. Get information about resource group : %s.\n" "$groupName" -resourceGroupInfo=`azure group get --name $groupName` +resourceGroupInfo=`azure group get -n $groupName` printf "\nValidating resource group name is: %s\n" "$groupName" [ $(echo $resourceGroupInfo | jq '.ResourceGroupName' --raw-output) == "$groupName" ] @@ -18,4 +18,4 @@ printf "\n4. Listing all resource groups in the subscription.\n" azure group get printf "\n5. Removing resource group: %s.\n" "$groupName" -azure group remove --name "$groupName" --force \ No newline at end of file +azure group remove -n "$groupName" -f \ No newline at end of file diff --git a/src/CLU/CLUCoreCLR.sln b/src/CLU/CLUCoreCLR.sln index bf8dbefe8ea0..9d3937fc16f3 100644 --- a/src/CLU/CLUCoreCLR.sln +++ b/src/CLU/CLUCoreCLR.sln @@ -39,13 +39,7 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.CLU", "Microsoft. EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.CLU.Test", "Microsoft.CLU.Test\Microsoft.CLU.Test.xproj", "{91422B55-28A5-48DE-BCA0-30C3E30FFB1C}" EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Azure.Commands.Compute", "Microsoft.Azure.Commands.Compute\Microsoft.Azure.Commands.Compute.xproj", "{04F9968A-5662-4508-BEE2-31F56848FCBA}" - ProjectSection(ProjectDependencies) = postProject - {99B1290D-A073-4907-8018-51C714431778} = {99B1290D-A073-4907-8018-51C714431778} - {3910613E-4ED2-49E2-8CCF-966D586665AC} = {3910613E-4ED2-49E2-8CCF-966D586665AC} - {81A48E48-89A7-4B93-8207-4F8FA6DC251B} = {81A48E48-89A7-4B93-8207-4F8FA6DC251B} - {45B05B68-516F-4D74-897F-56D12894946C} = {45B05B68-516F-4D74-897F-56D12894946C} - EndProjectSection +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Commands.Common.ScenarioTest", "Commands.Common.ScenarioTest\Commands.Common.ScenarioTest.xproj", "{B1D3CB1F-C0CA-401F-8146-B2E9C1EF460F}" EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Azure.Commands.Compute.Test", "Microsoft.Azure.Commands.Compute.Test\Microsoft.Azure.Commands.Compute.Test.xproj", "{13C34370-51A4-4726-81B8-BE0996FC9CFF}" ProjectSection(ProjectDependencies) = postProject @@ -55,6 +49,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Azure.Commands.Co {04F9968A-5662-4508-BEE2-31F56848FCBA} = {04F9968A-5662-4508-BEE2-31F56848FCBA} EndProjectSection EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Azure.Commands.Compute", "Microsoft.Azure.Commands.Compute\Microsoft.Azure.Commands.Compute.xproj", "{04F9968A-5662-4508-BEE2-31F56848FCBA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -133,14 +129,18 @@ Global {91422B55-28A5-48DE-BCA0-30C3E30FFB1C}.Debug|Any CPU.Build.0 = Debug|Any CPU {91422B55-28A5-48DE-BCA0-30C3E30FFB1C}.Release|Any CPU.ActiveCfg = Release|Any CPU {91422B55-28A5-48DE-BCA0-30C3E30FFB1C}.Release|Any CPU.Build.0 = Release|Any CPU - {04F9968A-5662-4508-BEE2-31F56848FCBA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {04F9968A-5662-4508-BEE2-31F56848FCBA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {04F9968A-5662-4508-BEE2-31F56848FCBA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {04F9968A-5662-4508-BEE2-31F56848FCBA}.Release|Any CPU.Build.0 = Release|Any CPU + {B1D3CB1F-C0CA-401F-8146-B2E9C1EF460F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B1D3CB1F-C0CA-401F-8146-B2E9C1EF460F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B1D3CB1F-C0CA-401F-8146-B2E9C1EF460F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B1D3CB1F-C0CA-401F-8146-B2E9C1EF460F}.Release|Any CPU.Build.0 = Release|Any CPU {13C34370-51A4-4726-81B8-BE0996FC9CFF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {13C34370-51A4-4726-81B8-BE0996FC9CFF}.Debug|Any CPU.Build.0 = Debug|Any CPU {13C34370-51A4-4726-81B8-BE0996FC9CFF}.Release|Any CPU.ActiveCfg = Release|Any CPU {13C34370-51A4-4726-81B8-BE0996FC9CFF}.Release|Any CPU.Build.0 = Release|Any CPU + {04F9968A-5662-4508-BEE2-31F56848FCBA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {04F9968A-5662-4508-BEE2-31F56848FCBA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {04F9968A-5662-4508-BEE2-31F56848FCBA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {04F9968A-5662-4508-BEE2-31F56848FCBA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/CLU/Commands.Common.ScenarioTest/Commands.Common.ScenarioTest.xproj b/src/CLU/Commands.Common.ScenarioTest/Commands.Common.ScenarioTest.xproj new file mode 100644 index 000000000000..41dbb6db1989 --- /dev/null +++ b/src/CLU/Commands.Common.ScenarioTest/Commands.Common.ScenarioTest.xproj @@ -0,0 +1,21 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + b1d3cb1f-c0ca-401f-8146-b2e9c1ef460f + Microsoft.Azure.Commands.Common.ScenarioTest + ..\artifacts\obj\$(MSBuildProjectName) + ..\artifacts\bin\$(MSBuildProjectName)\ + + + 2.0 + + + + + + \ No newline at end of file diff --git a/src/CLU/Commands.Common.ScenarioTest/EnvironmentConstants.cs b/src/CLU/Commands.Common.ScenarioTest/EnvironmentConstants.cs new file mode 100644 index 000000000000..7c49f5092c00 --- /dev/null +++ b/src/CLU/Commands.Common.ScenarioTest/EnvironmentConstants.cs @@ -0,0 +1,32 @@ +// ---------------------------------------------------------------------------------- +// +// 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. +// ---------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.Azure.Commands.Common.ScenarioTest +{ + public static class EnvironmentConstants + { + public const string UsernameKey = "Username"; + public const string PasswordKey = "Password"; + public const string ServicePrincipalKey = "ServicePrincipal"; + public const string TenantKey = "TenantId"; + public const string SubscriptionKey = "SubscriptionId"; + public const string TestRunDirectory = "TestRunDirectory"; + public const string ExampleDirectory = "ExamplesDirectory"; + } +} diff --git a/src/CLU/Commands.Common.ScenarioTest/EnvironmentContextFactory.cs b/src/CLU/Commands.Common.ScenarioTest/EnvironmentContextFactory.cs new file mode 100644 index 000000000000..fc907f9ebb04 --- /dev/null +++ b/src/CLU/Commands.Common.ScenarioTest/EnvironmentContextFactory.cs @@ -0,0 +1,67 @@ +// ---------------------------------------------------------------------------------- +// +// 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. +// ---------------------------------------------------------------------------------- + +using System.Collections.Generic; +using System.IO; + +namespace Microsoft.Azure.Commands.Common.ScenarioTest +{ + public class EnvironmentContextFactory + { + ICredentialsProvider _credentials; + + + public EnvironmentContextFactory(ICredentialsProvider credentials) + { + _credentials = credentials; + } + + public TestContext GetTestContext(string scriptDirectoryName) + { + var context = new TestContext(); + context.ExecutionDirectory = GetBaseDirectory(); + context.TestScriptDirectory =GetExamplesDirectory(context.ExecutionDirectory, scriptDirectoryName); + context.TestExecutableName = "bash.exe"; + context.TestScriptSuffix = ".sh"; + var helpers = new List(); + helpers.Add(_credentials.EnvironmentProvider); + context.EnvironmentHelpers = helpers; + return context; + } + + private string GetExamplesDirectory(string executionDirectory, string scriptDirectoryName) + { + string examplesDirectory; + if (!Utilities.TryGetEnvironmentVariable(EnvironmentConstants.ExampleDirectory, out examplesDirectory)) + { + examplesDirectory = Path.GetFullPath(Path.Combine(executionDirectory, "..", + "..", "..", "examples")); + } + + return Path.Combine(examplesDirectory, scriptDirectoryName); + } + private string GetBaseDirectory() + { + string baseDirectory; + if (!Utilities.TryGetEnvironmentVariable(EnvironmentConstants.TestRunDirectory, out baseDirectory)) + { + baseDirectory = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), "..", "TestResults")); + } + + Utilities.EnsureDirectoryExists(baseDirectory); + return baseDirectory; + } + + } +} diff --git a/src/CLU/Commands.Common.ScenarioTest/EnvironmentCredentialsProvider.cs b/src/CLU/Commands.Common.ScenarioTest/EnvironmentCredentialsProvider.cs new file mode 100644 index 000000000000..66b685bbe1db --- /dev/null +++ b/src/CLU/Commands.Common.ScenarioTest/EnvironmentCredentialsProvider.cs @@ -0,0 +1,99 @@ +// ---------------------------------------------------------------------------------- +// +// 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. +// ---------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Azure.Commands.Common.ScenarioTest; + +namespace Microsoft.Azure.Commands.Common.ScenarioTest +{ + public class EnvironmentCredentialsProvider : ICredentialsProvider + { + public const string userScript = "loginUser"; + public const string serviceScript = "loginService"; + + public IScriptEnvironmentHelper EnvironmentProvider { get; protected set; } + + public string LoginScriptName { get; protected set; } + + public virtual void Initialize(string key) + { + IDictionary settings = GetSettings(key); + if (!TryInitializeServiceCredentials(settings) && !TryInitializeUserCredentials(settings)) + { + throw new InvalidOperationException($"Unable to create credentials using key {key}. " + + "Please ensure your environment is correctly set up."); + } + } + + protected virtual IDictionary GetSettings(string key) + { + var environmentValue = Environment.GetEnvironmentVariable(key); + if (string.IsNullOrWhiteSpace(environmentValue)) + { + throw new InvalidOperationException($"Unable to create credentials. " + + "Please set environment ${key}"); + } + IDictionary settings = new Dictionary(); + foreach ( + var setting in + environmentValue.Split(new string[] {$"{Path.PathSeparator}"}, + StringSplitOptions.RemoveEmptyEntries) + ) + { + string[] pair = setting.Split(new char[] { '='}, 2, StringSplitOptions.RemoveEmptyEntries); + var pairKey = pair[0].Trim(); + var pairValue = pair[1].Trim(); + settings[pairKey] = pairValue; + } + + return settings; + } + + protected virtual bool TryInitializeServiceCredentials(IDictionary settings ) + { + if (settings.ContainsKey(EnvironmentConstants.ServicePrincipalKey) && + settings.ContainsKey(EnvironmentConstants.PasswordKey) && + settings.ContainsKey(EnvironmentConstants.TenantKey)) + { + LoginScriptName = serviceScript; + EnvironmentProvider = new ServiceAuthenticationHelper( + settings[EnvironmentConstants.ServicePrincipalKey], + settings[EnvironmentConstants.PasswordKey], + settings[EnvironmentConstants.TenantKey], + settings.ContainsKey(EnvironmentConstants.SubscriptionKey) ? settings[EnvironmentConstants.SubscriptionKey] : null); + return true; + } + return false; + } + + protected virtual bool TryInitializeUserCredentials(IDictionary settings ) + { + if (settings.ContainsKey(EnvironmentConstants.UsernameKey) && + settings.ContainsKey(EnvironmentConstants.PasswordKey)) + { + LoginScriptName = userScript; + EnvironmentProvider = new UserAuthenticationHelper( + settings[EnvironmentConstants.UsernameKey], + settings[EnvironmentConstants.PasswordKey], + settings.ContainsKey(EnvironmentConstants.SubscriptionKey) ? settings[EnvironmentConstants.SubscriptionKey] : null); + return true; + } + return false; + } + } +} diff --git a/src/CLU/Commands.Common.ScenarioTest/ExampleScriptRunner.cs b/src/CLU/Commands.Common.ScenarioTest/ExampleScriptRunner.cs new file mode 100644 index 000000000000..8d00e87e7d60 --- /dev/null +++ b/src/CLU/Commands.Common.ScenarioTest/ExampleScriptRunner.cs @@ -0,0 +1,183 @@ +// ---------------------------------------------------------------------------------- +// +// 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. +// ---------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using Microsoft.Azure.Commands.Common.Authentication; +using Microsoft.Azure.Commands.Common.Authentication.Factories; +using Microsoft.Azure.Commands.Common.Authentication.Models; +using Microsoft.Azure.Commands.Common.ScenarioTest; +using Microsoft.Azure.Management.Resources; +using Microsoft.Azure.Management.Resources.Models; +using Microsoft.Rest; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace Microsoft.Azure.Commands.Common.ScenarioTest +{ + public class ExampleScriptRunner + { + string _sessionId; + Random _generator; + string _resourceGroupName; + IClientFactory _clientFactory = new ClientFactory(); + TestContext _context; + ResourceManagementClient _client; + const string DefaultLocation = "westus"; + const string ResourceGroupNameKey = "groupName"; + const string locationKey = "location"; + const string SessionKey = "CmdletSessionID"; + + public ExampleScriptRunner(string sessionId) : this(new Random(), sessionId) + { + } + + public ExampleScriptRunner(int seed, string sessionId) : this(new Random(seed), sessionId) + { + } + + + public ExampleScriptRunner(Random generator, string sessionId) + { + _generator = generator; + _sessionId = sessionId; + } + + public IClientFactory ClientFactory + { + get { return _clientFactory; } + set { _clientFactory = value; } + } + + public TestContext TestContext + { + get { return _context; } + set { _context = value; } + } + + public string ScriptOutput { get; protected set; } + + public string RunScript(string testName) + { + var testDirectory = Path.GetFullPath(_context.TestScriptDirectory); + var executionDirectory = Path.GetFullPath(_context.ExecutionDirectory); + string testFile = $"{testName}{_context.TestScriptSuffix}"; + string logFile = $"{testName}.log"; + string logFilePath = Path.Combine(executionDirectory, logFile); + string testPath = Path.Combine(testDirectory, testFile); + if (!File.Exists(testPath)) + { + throw new InvalidOperationException($"Path to test script '{testPath}' does not exist."); + } + + string deploymentTemplatePath = Path.Combine(testDirectory, $"{testName}.json"); + TraceListener listener = null; + try + { + using (var stream = new FileStream(logFilePath, FileMode.Create)) + using (listener = new TextWriterTraceListener(stream)) + using (var process = new ProcessHelper(executionDirectory, _context.TestExecutableName, testPath)) + { + Trace.Listeners.Add(listener); + _resourceGroupName = CreateRandomName(); + if (File.Exists(deploymentTemplatePath)) + { + DeployTemplate(deploymentTemplatePath, _resourceGroupName); + } + + process.EnvironmentVariables[SessionKey] = _sessionId; + process.EnvironmentVariables[ResourceGroupNameKey] = _resourceGroupName; + process.EnvironmentVariables[locationKey] = DefaultLocation; + foreach (var helper in _context.EnvironmentHelpers) + { + helper.TrySetupScriptEnvironment(_context, _clientFactory, process.EnvironmentVariables); + } + int statusCode = process.StartAndWaitForExit(); + Assert.Equal(0, statusCode); + return process.Output; + } + } + finally + { + if (listener != null) + { + Trace.Listeners.Remove(listener); + } + Cleanup(); + } + } + + public void DeployTemplate(string deploymentTemplatePath, string resourceGroupName) + { + EnsureClient(); + var location = GetLocation(); + CreateResourceGroup(resourceGroupName, location); + var template = JObject.Parse(File.ReadAllText(deploymentTemplatePath)); + var deployment = _client.Deployments.CreateOrUpdateWithHttpMessagesAsync(resourceGroupName, "testDeployment", + new Deployment(new DeploymentProperties(template: template, mode: DeploymentMode.Complete))).GetAwaiter().GetResult(); + if (!deployment.Response.IsSuccessStatusCode) + { + throw new InvalidOperationException($"Deployment failed with response: {deployment.Response.AsFormattedString()}"); + } + } + + public string CreateRandomName() + { + return "clutst" + _generator.Next(10000, 99999); + } + + private void EnsureClient() + { + if (this._client == null) + { + var context = _context.Context; + var _client = _clientFactory.CreateArmClient(context, + AzureEnvironment.Endpoint.ResourceManager); + } + } + + + private string GetLocation() + { + return DefaultLocation; + } + + private void CreateResourceGroup(string resourceGroupName, string location) + { + var response = + _client.ResourceGroups.CreateOrUpdateWithHttpMessagesAsync(resourceGroupName, + new ResourceGroup(location: location)).GetAwaiter().GetResult(); + _resourceGroupName = resourceGroupName; + } + + public void Cleanup() + { + if (_client != null && !string.IsNullOrWhiteSpace(_resourceGroupName)) + { + try + { + _client.ResourceGroups.DeleteWithHttpMessagesAsync(_resourceGroupName).GetAwaiter().GetResult(); + } + catch (Exception exception) + { + Logger.Instance.WriteError($"Could not remove resource group: {exception}"); + } + } + _client = null; + _resourceGroupName = null; + } + } +} diff --git a/src/CLU/Commands.Common.ScenarioTest/ICredentialsProvider.cs b/src/CLU/Commands.Common.ScenarioTest/ICredentialsProvider.cs new file mode 100644 index 000000000000..e0aa8a56740e --- /dev/null +++ b/src/CLU/Commands.Common.ScenarioTest/ICredentialsProvider.cs @@ -0,0 +1,25 @@ +// ---------------------------------------------------------------------------------- +// +// 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. +// ---------------------------------------------------------------------------------- + +using Microsoft.Azure.Commands.Common.ScenarioTest; + +namespace Microsoft.Azure.Commands.Common.ScenarioTest +{ + public interface ICredentialsProvider + { + void Initialize(string key); + string LoginScriptName { get; } + IScriptEnvironmentHelper EnvironmentProvider { get; } + } +} diff --git a/src/CLU/Commands.Common.ScenarioTest/IScriptEnvironmentHelper.cs b/src/CLU/Commands.Common.ScenarioTest/IScriptEnvironmentHelper.cs new file mode 100644 index 000000000000..3119dacfd4b7 --- /dev/null +++ b/src/CLU/Commands.Common.ScenarioTest/IScriptEnvironmentHelper.cs @@ -0,0 +1,24 @@ +// ---------------------------------------------------------------------------------- +// +// 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. +// ---------------------------------------------------------------------------------- + +using System.Collections.Generic; +using Microsoft.Azure.Commands.Common.Authentication; + +namespace Microsoft.Azure.Commands.Common.ScenarioTest +{ + public interface IScriptEnvironmentHelper + { + bool TrySetupScriptEnvironment(TestContext testContext, IClientFactory clientFactory, IDictionary settings ); + } +} diff --git a/src/CLU/Commands.Common.ScenarioTest/ITestLogger.cs b/src/CLU/Commands.Common.ScenarioTest/ITestLogger.cs new file mode 100644 index 000000000000..8ce079c7063f --- /dev/null +++ b/src/CLU/Commands.Common.ScenarioTest/ITestLogger.cs @@ -0,0 +1,24 @@ +// ---------------------------------------------------------------------------------- +// +// 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. +// ---------------------------------------------------------------------------------- + +using Xunit.Abstractions; + +namespace Microsoft.Azure.Commands.Common.ScenarioTest +{ + public interface ITestLogger + { + void WriteMessage(string message); + void WriteError(string message); + } +} diff --git a/src/CLU/Commands.Common.ScenarioTest/Logger.cs b/src/CLU/Commands.Common.ScenarioTest/Logger.cs new file mode 100644 index 000000000000..5667793c8475 --- /dev/null +++ b/src/CLU/Commands.Common.ScenarioTest/Logger.cs @@ -0,0 +1,42 @@ +// ---------------------------------------------------------------------------------- +// +// 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. +// ---------------------------------------------------------------------------------- + +using System.Diagnostics; + +namespace Microsoft.Azure.Commands.Common.ScenarioTest +{ + public class Logger : ITestLogger + { + static Logger() + { + Logger.Instance = new Logger(); + } + public static ITestLogger Instance { get; private set; } + + private Logger() + { + } + + + public void WriteMessage(string message) + { + Trace.WriteLine(message); + } + + public void WriteError(string message) + { + Trace.WriteLine($"Error: {message}"); + } + } +} diff --git a/src/CLU/Commands.Common.ScenarioTest/ProcessHelper.cs b/src/CLU/Commands.Common.ScenarioTest/ProcessHelper.cs new file mode 100644 index 000000000000..a45e88ab2b81 --- /dev/null +++ b/src/CLU/Commands.Common.ScenarioTest/ProcessHelper.cs @@ -0,0 +1,193 @@ +// ---------------------------------------------------------------------------------- +// +// 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. +// ---------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Threading; +using Microsoft.Azure.Commands.Common.ScenarioTest; +using Xunit; + +namespace Microsoft.Azure.Commands.Common.ScenarioTest +{ + public class ProcessHelper : IDisposable + { + /// + /// The process running the service. + /// + + string _executableName = "bash.exe"; + + StringBuilder _processOutput = new StringBuilder(); + + string _arguments; + string _directory; + Dictionary _environment = new Dictionary(); + Process _process; + + public IDictionary EnvironmentVariables + { + get { return _environment; } + } + + public string ExecutableName + { + get { return _executableName; } + set { _executableName = value; } + } + + public string Output { get { return _processOutput.ToString(); }} + + public ProcessHelper(string directory, string executableName, params string[] arguments) + { + _executableName = executableName; + _directory = directory; + if (arguments != null && arguments.Length > 0) + { + _arguments = string.Join(" ", arguments); + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing && _process != null ) + { + try + { + EndProcess(); + } + catch + { + } + + _process = null; + } + } + + + public static string GetPathToExecutable(string executableName) + { + var paths = Environment.GetEnvironmentVariable("PATH"); + foreach (var path in paths.Split(new[] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries)) + { + var fullPath = Path.Combine(path, Path.GetFileName(executableName)); + if (File.Exists(fullPath)) + { + return fullPath; + } + + var exec = Path.GetFileNameWithoutExtension(executableName); + var fullPathNoExe = Path.Combine(path, exec); + if (File.Exists(fullPath)) + { + return fullPath; + } + + if (File.Exists(fullPathNoExe)) + { + return fullPathNoExe; + } + } + + return null; + } + + public int StartAndWaitForExit() + { + return StartAndWaitForExit(TimeSpan.FromMinutes(60)); + } + + public int StartAndWaitForExit(TimeSpan timeout) + { + var shellPath = GetPathToExecutable(_executableName); + if (shellPath == null) + { + throw new InvalidOperationException($"Could not find path to '{_executableName}'"); + } + + _process = StartProcess(shellPath, _arguments, _directory); + if (_process.WaitForExit((int) timeout.TotalMilliseconds)) + { + return _process.ExitCode; + } + + throw new TimeoutException($"Process using executable with path '{shellPath}' timed out"); + } + + /// + /// Run the given command with arguments. Return the result in standard output. + /// + /// The path to the command to execute. + /// The arguments to pass to the command. + /// The working directory for the process being launched. + /// The process + private Process StartProcess( + string path, + string arguments, + string workingDirectory) + { + var process = new Process(); + var startInfo = process.StartInfo; + startInfo.CreateNoWindow = false; + startInfo.RedirectStandardOutput = true; + startInfo.RedirectStandardError = true; + startInfo.WorkingDirectory = workingDirectory; + startInfo.UseShellExecute = false; + startInfo.FileName = path; + startInfo.Arguments = arguments; + SetEnvironmentVariables(startInfo); + process.OutputDataReceived += ProcessOutput; + process.ErrorDataReceived += ProcessError; + process.Start(); + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + return process; + } + + private void SetEnvironmentVariables(ProcessStartInfo startInfo) + { + foreach (var key in _environment.Keys) + { + startInfo.Environment[key] = _environment[key]; + } + } + + private void ProcessError(object sender, DataReceivedEventArgs e) + { + Logger.Instance.WriteError(e.Data); + } + + private void ProcessOutput(object sender, DataReceivedEventArgs e) + { + Logger.Instance.WriteMessage(e.Data); + _processOutput.Append(e.Data); + } + + private void EndProcess() + { + _process.CancelOutputRead(); + _process.CancelErrorRead(); + _process.WaitForExit(2000); + _process.Dispose(); + } + } +} diff --git a/src/CLU/Commands.Common.ScenarioTest/SampleTest.cs b/src/CLU/Commands.Common.ScenarioTest/SampleTest.cs new file mode 100644 index 000000000000..48e62f213bea --- /dev/null +++ b/src/CLU/Commands.Common.ScenarioTest/SampleTest.cs @@ -0,0 +1,42 @@ +// ---------------------------------------------------------------------------------- +// +// 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. +// ---------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Azure.Commands.Common.ScenarioTest; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.Azure.Commands.Common.ScenarioTest +{ + [Collection("SampleCollection")] + public class SampleTest + { + ScenarioTestFixture _collectionState; + public SampleTest(ScenarioTestFixture fixture) + { + _collectionState = fixture; + } + [Fact] + public void RunSampleTest() + { + var helper = _collectionState.GetRunner("resource-management"); + helper.RunScript("01-ResourceGroups"); + } + + } +} diff --git a/src/CLU/Commands.Common.ScenarioTest/SampleTestCollection.cs b/src/CLU/Commands.Common.ScenarioTest/SampleTestCollection.cs new file mode 100644 index 000000000000..f01b122ef604 --- /dev/null +++ b/src/CLU/Commands.Common.ScenarioTest/SampleTestCollection.cs @@ -0,0 +1,23 @@ +// ---------------------------------------------------------------------------------- +// +// 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. +// ---------------------------------------------------------------------------------- + +using Xunit; + +namespace Microsoft.Azure.Commands.Common.ScenarioTest +{ + [CollectionDefinition("SampleCollection")] + public class SampleTestCollection : ICollectionFixture + { + } +} diff --git a/src/CLU/Commands.Common.ScenarioTest/ScenarioTestFixture.cs b/src/CLU/Commands.Common.ScenarioTest/ScenarioTestFixture.cs new file mode 100644 index 000000000000..ba487dd7a344 --- /dev/null +++ b/src/CLU/Commands.Common.ScenarioTest/ScenarioTestFixture.cs @@ -0,0 +1,55 @@ +// ---------------------------------------------------------------------------------- +// +// 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. +// ---------------------------------------------------------------------------------- + +using System; +using Microsoft.Azure.Commands.Common.Authentication.Models; +using Microsoft.Azure.Commands.Common.ScenarioTest; +using Microsoft.Azure.Commands.Models; +using Moq.Protected; +using Newtonsoft.Json; + +namespace Microsoft.Azure.Commands.Common.ScenarioTest +{ + public class ScenarioTestFixture + { + protected EnvironmentContextFactory _contextFactory; + public ScenarioTestFixture() + { + Generator = new Random(); + SessionId = $"{Generator.Next(10000, 99999)}"; + var credentials = new EnvironmentCredentialsProvider(); + credentials.Initialize("TestCredentials"); + _contextFactory = new EnvironmentContextFactory(credentials); + var helper = GetRunner("lib"); + var profileText = helper.RunScript(credentials.LoginScriptName); + var profile = JsonConvert.DeserializeObject(profileText); + AzureContext = (AzureContext) (profile.Context); + } + + public string SessionId { get; protected set; } + public Random Generator { get; protected set; } + + public ExampleScriptRunner GetRunner(string directoryName) + { + var context = _contextFactory.GetTestContext(directoryName); + context.Context = AzureContext; + return new ExampleScriptRunner(Generator, SessionId) + { + TestContext = context + }; + } + + public AzureContext AzureContext { get; protected set;} + } +} diff --git a/src/CLU/Commands.Common.ScenarioTest/ServiceAuthenticationHelper.cs b/src/CLU/Commands.Common.ScenarioTest/ServiceAuthenticationHelper.cs new file mode 100644 index 000000000000..a145ae54225f --- /dev/null +++ b/src/CLU/Commands.Common.ScenarioTest/ServiceAuthenticationHelper.cs @@ -0,0 +1,63 @@ +// ---------------------------------------------------------------------------------- +// +// 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. +// ---------------------------------------------------------------------------------- + +using System.Collections.Generic; +using Microsoft.Azure.Commands.Common.Authentication; +using Microsoft.Azure.Commands.Common.ScenarioTest; + +namespace Microsoft.Azure.Commands.Common.ScenarioTest +{ + /// + /// Add the given SPN credentials to the environment for the login script + /// + public class ServiceAuthenticationHelper : IScriptEnvironmentHelper + { + string _spn; + string _secret; + string _tenant; + string _subscription; + const string SecretKey = "secret"; + const string SPNKey = "spn"; + const string TenantKey = "tenant"; + const string SubscriptionKey = "subscription"; + public ServiceAuthenticationHelper(string spn, string secret, string tenant) + : this(spn, secret, tenant, null) + { + } + + public ServiceAuthenticationHelper(string spn, string secret, string tenant, string subscription) + { + _spn = spn; + _secret = secret; + _tenant = tenant; + _subscription = subscription; + } + + public bool TrySetupScriptEnvironment(TestContext testContext, IClientFactory clientFactory, IDictionary settings) + { + Logger.Instance.WriteMessage($"Logging in using ServicePrincipal: {_spn}"); + settings[SPNKey] = _spn; + settings[SecretKey] = _secret; + Logger.Instance.WriteMessage($"Logging in using Tenant: {_tenant}"); + settings[TenantKey] = _tenant; + if (!string.IsNullOrWhiteSpace(_subscription)) + { + Logger.Instance.WriteMessage($"Logging in using Subscription: {_subscription}"); + settings[SubscriptionKey] = _subscription; + } + + return true; + } + } +} diff --git a/src/CLU/Commands.Common.ScenarioTest/TestContext.cs b/src/CLU/Commands.Common.ScenarioTest/TestContext.cs new file mode 100644 index 000000000000..3e66f7a414c8 --- /dev/null +++ b/src/CLU/Commands.Common.ScenarioTest/TestContext.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using Microsoft.Azure.Commands.Common.Authentication.Models; +// ---------------------------------------------------------------------------------- +// +// 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.Common.ScenarioTest +{ + public struct TestContext + { + public AzureContext Context { get; set; } + public string TestExecutableName { get; set; } + public string TestScriptSuffix { get; set; } + public string TestScriptDirectory { get; set; } + public string ExecutionDirectory { get; set; } + public IEnumerable EnvironmentHelpers { get; set; } + } +} diff --git a/src/CLU/Commands.Common.ScenarioTest/UserAuthenticationHelper.cs b/src/CLU/Commands.Common.ScenarioTest/UserAuthenticationHelper.cs new file mode 100644 index 000000000000..a95736e802f6 --- /dev/null +++ b/src/CLU/Commands.Common.ScenarioTest/UserAuthenticationHelper.cs @@ -0,0 +1,69 @@ +// ---------------------------------------------------------------------------------- +// +// 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. +// ---------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using Microsoft.Azure.Commands.Common.Authentication; +using Microsoft.Azure.Commands.Common.ScenarioTest; + +namespace Microsoft.Azure.Commands.Common.ScenarioTest +{ + public class UserAuthenticationHelper : IScriptEnvironmentHelper + { + public const string UsernameVariable = "azureUser"; + public const string PasswordVariable = "password"; + public const string SubscriptionVariable = "subscription"; + string _username; + string _password; + string _subscription; + + public UserAuthenticationHelper(string username, string password) + : this(username, password, null) + { + } + + public UserAuthenticationHelper(string username, string password, string subscription) + { + _username = username; + _password = password; + _subscription = subscription; + } + + public bool TrySetupScriptEnvironment(TestContext testContext, IClientFactory clientFactory, IDictionary settings) + { + if (string.IsNullOrWhiteSpace(_username) || string.IsNullOrWhiteSpace(_password)) + { + throw new ArgumentOutOfRangeException("textContext", + "Username and Password must be provided for user accounts"); + } + + if (settings == null) + { + throw new ArgumentNullException("settings"); + } + + settings[UsernameVariable] = _username; + Logger.Instance.WriteMessage($"Setting process environment {UsernameVariable} = {_username}"); + settings[PasswordVariable] = _password; + Logger.Instance.WriteMessage($"Setting process environment {PasswordVariable} = ***********"); + if (!string.IsNullOrWhiteSpace(_subscription)) + { + Logger.Instance.WriteMessage($"Logging in using Subscription: {_subscription}"); + settings[SubscriptionVariable] = _subscription; + } + return true; + + } + } +} diff --git a/src/CLU/Commands.Common.ScenarioTest/Utilities.cs b/src/CLU/Commands.Common.ScenarioTest/Utilities.cs new file mode 100644 index 000000000000..6f33e93f9980 --- /dev/null +++ b/src/CLU/Commands.Common.ScenarioTest/Utilities.cs @@ -0,0 +1,36 @@ +// ---------------------------------------------------------------------------------- +// +// 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. +// ---------------------------------------------------------------------------------- + +using System; +using System.IO; + +namespace Microsoft.Azure.Commands.Common.ScenarioTest +{ + public static class Utilities + { + public static bool TryGetEnvironmentVariable(string variableName, out string variableValue) + { + variableValue = Environment.GetEnvironmentVariable(variableName); + return !string.IsNullOrWhiteSpace(variableValue); + } + + public static void EnsureDirectoryExists(string directoryPath) + { + if (!Directory.Exists(directoryPath)) + { + Directory.CreateDirectory(directoryPath); + } + } + } +} diff --git a/src/CLU/Commands.Common.ScenarioTest/project.json b/src/CLU/Commands.Common.ScenarioTest/project.json new file mode 100644 index 000000000000..0b83615e7c8c --- /dev/null +++ b/src/CLU/Commands.Common.ScenarioTest/project.json @@ -0,0 +1,64 @@ +{ + "version": "1.0.0-*", + "description": "Tests for Authentication Management Cmdlets", + "authors": [ "markcowl" ], + "tags": [ "" ], + "projectUrl": "", + "licenseUrl": "", + "frameworks": { + "dnxcore50": { + "dependencies": { + "Microsoft.NETCore": "5.0.1-beta-23516", + "Microsoft.NETCore.Platforms": "1.0.1-beta-23516", + "Microsoft.CSharp": "4.0.1-beta-23516" + } + } + }, + "dependencies": { + "System.Linq": "4.0.1-beta-23516", + "Microsoft.CLU": "1.0.0", + "Commands.Common": "", + "Commands.Common.Authentication": "", + "Commands.ResourceManager.Common": "", + "Commands.ScenarioTests.ResourceManager.Common": "", + "Microsoft.Azure.Commands.Profile": "", + "Microsoft.Azure.Management.Resources": "3.3.0-preview", + "Microsoft.IdentityModel.Clients.ActiveDirectory": "3.6.212041202-alpha", + "Microsoft.Rest.ClientRuntime": "1.8.0", + "Microsoft.Rest.ClientRuntime.Azure": "2.5.1", + "Microsoft.Rest.ClientRuntime.Azure.Authentication": "1.2.1-preview", + "Newtonsoft.Json": "7.0.1", + "System.Collections": "4.0.11-beta-23516", + "System.Collections.Concurrent": "4.0.11-beta-23516", + "System.Diagnostics.Tools": "4.0.1-beta-23516", + "System.Diagnostics.TraceSource": "4.0.0-beta-23516", + "System.Diagnostics.Tracing": "4.0.21-beta-23516", + "System.IO": "4.0.11-beta-23516", + "System.IO.FileSystem": "4.0.1-beta-23516", + "System.Net.Http": "4.0.1-beta-23516", + "System.Net.WebHeaderCollection": "4.0.1-beta-23516", + "System.Reflection": "4.1.0-beta-23516", + "System.Reflection.Extensions": "4.0.1-beta-23516", + "System.Reflection.Primitives": "4.0.1-beta-23516", + "System.Reflection.TypeExtensions": "4.1.0-beta-23516", + "System.Runtime": "4.0.21-beta-23516", + "System.Runtime.Extensions": "4.0.11-beta-23516", + "System.Runtime.Serialization.Json": "4.0.1-beta-23516", + "System.Runtime.Serialization.Primitives": "4.1.0-beta-23516", + "System.Runtime.Serialization.Xml": "4.1.0-beta-23516", + "System.Security.Cryptography.Algorithms": "4.0.0-beta-23516", + "System.Security.Cryptography.X509Certificates": "4.0.0-beta-23516", + "System.Text.Encoding": "4.0.11-beta-23516", + "System.Text.Encoding.Extensions": "4.0.11-beta-23516", + "System.Threading": "4.0.11-beta-23516", + "System.Threading.Tasks": "4.0.11-beta-23516", + "System.Threading.Thread": "4.0.0-beta-23516", + "System.Xml.ReaderWriter": "4.0.11-beta-23516", + "xunit": "2.1.0", + "xunit.assert": "2.1.0", + "xunit.runner.dnx": "2.1.0-rc1-build204" + }, + "commands": { + "test": "xunit.runner.dnx" + } +} diff --git a/src/CLU/Commands.Common/Models/PSAzureProfile.cs b/src/CLU/Commands.Common/Models/PSAzureProfile.cs index 285dd052dfda..11a4cdf4b47c 100644 --- a/src/CLU/Commands.Common/Models/PSAzureProfile.cs +++ b/src/CLU/Commands.Common/Models/PSAzureProfile.cs @@ -66,7 +66,7 @@ public IDictionary Environments public string EnvironmentNames { - get { return _env == null? null : $"{string.Join(", ", _env.Keys.ToArray())}"; } + get { return _env == null || _env.Keys == null? null : string.Join(", ", _env.Keys.ToArray()); } } /// @@ -76,7 +76,7 @@ public string EnvironmentNames public override string ToString() { - return Context!= null? Context.ToString() : null; + return Context?.ToString(); } } } diff --git a/tools/CLU/azure.sh b/tools/CLU/azure.sh index e5149fc2f0e9..b35980f2e5bb 100644 --- a/tools/CLU/azure.sh +++ b/tools/CLU/azure.sh @@ -4,4 +4,4 @@ then export CmdletSessionID=$PPID fi SCRIPTPATH=$(dirname "$0") -$SCRIPTPATH/clurun -s azure -r $SCRIPTPATH/azure.lx $* +$SCRIPTPATH/clurun -s azure -r $SCRIPTPATH/azure.lx "$@"