Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,6 @@ TestsResults*.xml
PowerShell.sln.DotSettings.user
*.msp
StyleCop.Cache

src/Modules
!src/Modules/Microsoft.AzureFunctions.PowerShellWorker
33 changes: 9 additions & 24 deletions build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ param(

$NeededTools = @{
DotnetSdk = ".NET SDK latest"
PowerShellGet = "PowerShellGet latest"
Pester = "Pester latest"
}

function needsDotnetSdk () {
Expand All @@ -33,34 +31,12 @@ function needsDotnetSdk () {
return $false
}

function needsPowerShellGet () {
$modules = Get-Module -ListAvailable -Name PowerShellGet | Where-Object Version -ge 1.6.0
if ($modules.Count -gt 0) {
return $false
}
return $true
}

function needsPester () {
$modules = Get-Module -ListAvailable -Name Pester
if ($modules.Count -gt 0) {
return $false
}
return $true
}

function getMissingTools () {
$missingTools = @()

if (needsDotnetSdk) {
$missingTools += $NeededTools.DotnetSdk
}
if (needsPowerShellGet) {
$missingTools += $NeededTools.PowerShellGet
}
if (needsPester) {
$missingTools += $NeededTools.Pester
}

return $missingTools
}
Expand All @@ -87,8 +63,17 @@ if($Clean) {
}

# Build step

# Install using PSDepend if it's available, otherwise use the backup script
if ((Get-Module -ListAvailable -Name PSDepend).Count -gt 0) {
Invoke-PSDepend -Path src -Force
} else {
& "$PSScriptRoot/tools/InstallDependencies.ps1"
}

dotnet build -c $Configuration
dotnet publish -c $Configuration

Push-Location package
dotnet pack -c $Configuration
Pop-Location
Expand Down
20 changes: 20 additions & 0 deletions examples/PSCoreApp/GetAzureVmHttpTrigger/function.json
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"
}
]
}
29 changes: 29 additions & 0 deletions examples/PSCoreApp/GetAzureVmHttpTrigger/run.ps1
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
2 changes: 1 addition & 1 deletion src/Messaging/RpcLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public async void Log(LogLevel logLevel, string message, Exception exception = n
RpcLog = new RpcLog()
{
Exception = exception?.ToRpcException(),
InvocationId = _invocationId,
InvocationId = _invocationId ?? _requestId,
Copy link
Copy Markdown
Contributor

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?

Copy link
Copy Markdown
Member Author

@TylerLeonhardt TylerLeonhardt Sep 12, 2018

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.

Copy link
Copy Markdown
Contributor

@daxian-dbw daxian-dbw Sep 12, 2018

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. _invocationId will not be null by that time. But when doing system logging, _invocationId could be null, so we should handle it there. And I think it should be _invocationId ?? "N/A".

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

fixed.

Level = logLevel,
Message = message
}
Expand Down
34 changes: 34 additions & 0 deletions src/PowerShell/PowerShellManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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 FunctionLoadResponse message with the status and the error message.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I think this actually should be a Warning because we shouldn't require connecting to Azure to run the function locally - especially if their function doesn't interact Azure at all.

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.

Copy link
Copy Markdown
Contributor

@daxian-dbw daxian-dbw Sep 12, 2018

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The 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(
Expand Down
50 changes: 25 additions & 25 deletions src/RequestProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This async operation can be moved out of the using block.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

fixed

}

await _msgStream.WriteAsync(response);
}
}
}
Expand Down Expand Up @@ -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)
Expand Down
21 changes: 21 additions & 0 deletions src/requirements.psd1
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'
}
}
28 changes: 28 additions & 0 deletions tools/InstallDependencies.ps1
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
Comment thread
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
}
}
}