-
Notifications
You must be signed in to change notification settings - Fork 56
bundle and handle Azure PowerShell #47
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
dfff565
5ba4805
f59903e
5daeb6b
15be657
561473b
5d05e52
1601d6f
e40ad29
ea8fbaa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| { | ||
| "disabled": false, | ||
| "bindings": [ | ||
| { | ||
| "authLevel": "function", | ||
| "type": "httpTrigger", | ||
| "direction": "in", | ||
| "name": "req", | ||
| "methods": [ | ||
| "get", | ||
| "post" | ||
| ] | ||
| }, | ||
| { | ||
| "type": "http", | ||
| "direction": "out", | ||
| "name": "res" | ||
| } | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| # Trigger the function by running Invoke-RestMethod: | ||
| # Get everything: Invoke-RestMethod -Uri http://localhost:7071/api/GetAzureVmHttpTrigger | ||
| # Specify parameters: Invoke-RestMethod ` | ||
| # -Uri http://localhost:7071/api/MyHttpTrigger?Name=testVm&ResourceGroupName=TESTRESOURCEGROUP | ||
|
|
||
| # Input bindings are passed in via param block. | ||
| param($req, $TriggerMetadata) | ||
|
|
||
| $cmdletParameters = $req.Query | ||
|
|
||
| # If the cmdlet fails, we want it to throw an exception | ||
| $cmdletParameters.ErrorAction = "Stop" | ||
|
|
||
| try { | ||
| # Splat the parameters that were passed in via query parameters | ||
| $vms = Get-AzureRmVM @cmdletParameters | ||
| $response = [HttpResponseContext]@{ | ||
| StatusCode = '200' # OK | ||
| Body = ($vms | ConvertTo-Json) | ||
| } | ||
| } catch { | ||
| $response = [HttpResponseContext]@{ | ||
| StatusCode = '400' # Bad Request | ||
| Body = @{ Exception = $_.Exception } | ||
| } | ||
| } | ||
|
|
||
| # You associate values to output bindings by calling 'Push-OutputBinding'. | ||
| Push-OutputBinding -Name res -Value $response |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,7 @@ | |
| using System.Collections.Generic; | ||
| using System.Collections.ObjectModel; | ||
| using System.IO; | ||
| using System.Security; | ||
|
|
||
| using Microsoft.Azure.Functions.PowerShellWorker.Utility; | ||
| using Microsoft.Azure.WebJobs.Script.Grpc.Messages; | ||
|
|
@@ -57,6 +58,39 @@ internal void InitializeRunspace() | |
|
|
||
| // Set the PSModulePath | ||
| Environment.SetEnvironmentVariable("PSModulePath", Path.Join(AppDomain.CurrentDomain.BaseDirectory, "Modules")); | ||
|
|
||
| // TODO: remove this when we figure out why it fixed #48 | ||
| _pwsh.AddCommand("Microsoft.PowerShell.Core\\Import-Module").AddParameter("Name", "AzureRm.Netcore").InvokeAndClearCommands(); | ||
|
|
||
| // Try to authenticate to Azure | ||
| // TODO: The Azure Functions Host might supply these differently. This might change but works for the demo | ||
| string applicationId = Environment.GetEnvironmentVariable("ApplicationId"); | ||
| string applicationSecret = Environment.GetEnvironmentVariable("ApplicationSecret"); | ||
| string tenantId = Environment.GetEnvironmentVariable("TenantId"); | ||
|
|
||
| if (string.IsNullOrEmpty(applicationId) || | ||
| string.IsNullOrEmpty(applicationSecret) || | ||
| string.IsNullOrEmpty(tenantId)) | ||
| { | ||
| _logger.Log(LogLevel.Warning, "Required environment variables to authenticate to Azure were not present", isUserLog: true); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We won't be able to see the log if it's user log, but I think this message should be visible to us too. We probably shouldn't just return in this case, but should indicate a failure status so we can send a
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this actually should be a Additionally, this shouldn't be a user log, it should be a system log because in the local scenario the user sees the system logs... so I'll make that change.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The local scenario makes sense. But I don't see the logging gets changed to the system log.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed. |
||
| return; | ||
| } | ||
|
|
||
| // Build SecureString | ||
| var secureString = new SecureString(); | ||
| foreach (char item in applicationSecret) | ||
| { | ||
| secureString.AppendChar(item); | ||
| } | ||
|
|
||
| using (ExecutionTimer.Start(_logger, "Authentication to Azure completed.")) | ||
| { | ||
| _pwsh.AddCommand("Connect-AzureRmAccount") | ||
| .AddParameter("Credential", new PSCredential(applicationId, secureString)) | ||
| .AddParameter("ServicePrincipal") | ||
| .AddParameter("TenantId", tenantId) | ||
| .InvokeAndClearCommands(); | ||
| } | ||
| } | ||
|
|
||
| internal Hashtable InvokeFunction( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,25 +38,28 @@ internal async Task ProcessRequestLoop() | |
| while (await _msgStream.MoveNext()) | ||
| { | ||
| request = _msgStream.GetCurrentMessage(); | ||
| switch (request.ContentCase) | ||
|
|
||
| using (_logger.BeginScope(request.RequestId, request.InvocationRequest?.InvocationId)) | ||
| { | ||
| case StreamingMessage.ContentOneofCase.WorkerInitRequest: | ||
| response = ProcessWorkerInitRequest(request); | ||
| break; | ||
|
|
||
| case StreamingMessage.ContentOneofCase.FunctionLoadRequest: | ||
| response = ProcessFunctionLoadRequest(request); | ||
| break; | ||
|
|
||
| case StreamingMessage.ContentOneofCase.InvocationRequest: | ||
| response = ProcessInvocationRequest(request); | ||
| break; | ||
|
|
||
| default: | ||
| throw new InvalidOperationException($"Not supportted message type: {request.ContentCase}"); | ||
| switch (request.ContentCase) | ||
| { | ||
| case StreamingMessage.ContentOneofCase.WorkerInitRequest: | ||
| response = ProcessWorkerInitRequest(request); | ||
| break; | ||
|
|
||
| case StreamingMessage.ContentOneofCase.FunctionLoadRequest: | ||
| response = ProcessFunctionLoadRequest(request); | ||
| break; | ||
|
|
||
| case StreamingMessage.ContentOneofCase.InvocationRequest: | ||
| response = ProcessInvocationRequest(request); | ||
| break; | ||
|
|
||
| default: | ||
| throw new InvalidOperationException($"Not supportted message type: {request.ContentCase}"); | ||
| } | ||
| await _msgStream.WriteAsync(response); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This async operation can be moved out of the using block.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed |
||
| } | ||
|
|
||
| await _msgStream.WriteAsync(response); | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -156,14 +159,11 @@ internal StreamingMessage ProcessInvocationRequest(StreamingMessage request) | |
|
|
||
| // Set the RequestId and InvocationId for logging purposes | ||
| Hashtable result = null; | ||
| using (_logger.BeginScope(request.RequestId, invocationRequest.InvocationId)) | ||
| { | ||
| result = _powerShellManager.InvokeFunction( | ||
| functionInfo.ScriptPath, | ||
| functionInfo.EntryPoint, | ||
| triggerMetadata, | ||
| invocationRequest.InputData); | ||
| } | ||
| result = _powerShellManager.InvokeFunction( | ||
| functionInfo.ScriptPath, | ||
| functionInfo.EntryPoint, | ||
| triggerMetadata, | ||
| invocationRequest.InputData); | ||
|
|
||
| // Set out binding data and return response to be sent back to host | ||
| foreach (KeyValuePair<string, BindingInfo> binding in functionInfo.OutputBindings) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| @{ | ||
| # Packaged with the PowerShell Language Worker | ||
| 'PowerShellGet' = @{ | ||
| Version = '1.6.7' | ||
| Target = 'src/Modules' | ||
| } | ||
| 'Microsoft.PowerShell.Archive' = @{ | ||
| Version = '1.1.0.0' | ||
| Target = 'src/Modules' | ||
| } | ||
| 'AzureRM.Netcore' = @{ | ||
| Version = '0.13.1' | ||
| Target = 'src/Modules' | ||
| } | ||
|
|
||
| # Dev dependencies | ||
| 'Pester' = @{ | ||
| Version = 'latest' | ||
| Target = 'CurrentUser' | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| # | ||
| # Copyright (c) Microsoft. All rights reserved. | ||
| # Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
| # | ||
|
|
||
| $dependencies = Import-PowerShellDataFile "$PSScriptRoot/../src/requirements.psd1" | ||
|
|
||
| foreach ($key in $dependencies.Keys) { | ||
| $params = @{ Name = $key } | ||
|
|
||
| if ($dependencies[$key].Version -ne 'latest') { | ||
| # Save-Module doesn't have -Version so we have to specify Min and Max | ||
| $params.MinimumVersion = $dependencies[$key].Version | ||
| $params.MaximumVersion = $dependencies[$key].Version | ||
| } | ||
|
|
||
| if($dependencies[$key].Target -eq 'CurrentUser') { | ||
| $params.Scope = $dependencies[$key].Target | ||
| Install-Module @params | ||
|
TylerLeonhardt marked this conversation as resolved.
Outdated
|
||
| } else { | ||
| $params.Path = $dependencies[$key].Target | ||
| if (Test-Path "$($params.Path)/$key") { | ||
| Write-Host "'$key' - Module already installed" | ||
| } else { | ||
| Save-Module @params | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have to set the
InvocationId? What will happen if leaving it null or empty string?Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It has to be set. I've tried setting it to empty string or null but it still fails by saying can't be null or empty.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I understand, we will hit this code only for user logging, which only happens when the function runs.
_invocationIdwill not be null by that time. But when doing system logging,_invocationIdcould be null, so we should handle it there. And I think it should be_invocationId ?? "N/A".There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed.