diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 0000000..0180b9f --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,24 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "minver-cli": { + "version": "2.2.0", + "commands": [ + "minver" + ] + }, + "dotnet-retire": { + "version": "4.0.1", + "commands": [ + "dotnet-retire" + ] + }, + "gpr": { + "version": "0.1.122", + "commands": [ + "gpr" + ] + } + } + } \ No newline at end of file diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index e3c6f54..dcf075b 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -6,6 +6,18 @@ env: on: [push] jobs: + vulnerability-scan: + runs-on: ubuntu-latest + name: ci/github/scan-vulnerabilities + container: mcr.microsoft.com/dotnet/core/sdk:3.1-bionic + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Scan for Vulnerabilities + run: | + dotnet tool restore + dotnet restore + dotnet tool run dotnet-retire build: runs-on: ubuntu-latest steps: @@ -25,4 +37,43 @@ jobs: with: name: functions path: ${{ env.OUTPUT_PATH }} + publish: + needs: [vulnerability-scan, build] + runs-on: ubuntu-latest + name: ci/github/publish + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Get Version + id: get_version + run: | + echo "::set-output name=branch::${GITHUB_REF:10}" + + dotnet tool restore + version=$(dotnet tool run minver -- --tag-prefix=v) + echo "::set-output name=version::${version}" + - shell: bash + run: | + git fetch --prune --unshallow + - name: Setup Dotnet + uses: actions/setup-dotnet@v1 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + - name: Dotnet Pack + shell: bash + run: | + dotnet pack /p:Version=${{ steps.get_version.outputs.version }} --configuration=Release --output=./packages \ + + - name: Publish Artifacts + uses: actions/upload-artifact@v1 + with: + path: packages + name: nuget-packages + - name: Dotnet Push to Nuget.org + shell: bash + if: contains(steps.get_version.outputs.branch, 'v') + run: | + dotnet tool restore + find . -name "*.nupkg" | xargs -n1 dotnet nuget push --api-key=${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json + \ No newline at end of file diff --git a/.idea/.idea.SandboxFunctions/.idea/.name b/.idea/.idea.SandboxFunctions/.idea/.name new file mode 100644 index 0000000..3d7a599 --- /dev/null +++ b/.idea/.idea.SandboxFunctions/.idea/.name @@ -0,0 +1 @@ +SandboxFunctions \ No newline at end of file diff --git a/.idea/.idea.SandboxFunctions/.idea/contentModel.xml b/.idea/.idea.SandboxFunctions/.idea/contentModel.xml new file mode 100644 index 0000000..2b32b15 --- /dev/null +++ b/.idea/.idea.SandboxFunctions/.idea/contentModel.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.SandboxFunctions/.idea/encodings.xml b/.idea/.idea.SandboxFunctions/.idea/encodings.xml new file mode 100644 index 0000000..df87cf9 --- /dev/null +++ b/.idea/.idea.SandboxFunctions/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.SandboxFunctions/.idea/indexLayout.xml b/.idea/.idea.SandboxFunctions/.idea/indexLayout.xml new file mode 100644 index 0000000..27ba142 --- /dev/null +++ b/.idea/.idea.SandboxFunctions/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.SandboxFunctions/.idea/modules.xml b/.idea/.idea.SandboxFunctions/.idea/modules.xml new file mode 100644 index 0000000..00d3818 --- /dev/null +++ b/.idea/.idea.SandboxFunctions/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.SandboxFunctions/.idea/projectSettingsUpdater.xml b/.idea/.idea.SandboxFunctions/.idea/projectSettingsUpdater.xml new file mode 100644 index 0000000..4bb9f4d --- /dev/null +++ b/.idea/.idea.SandboxFunctions/.idea/projectSettingsUpdater.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/.idea.SandboxFunctions/.idea/riderModule.iml b/.idea/.idea.SandboxFunctions/.idea/riderModule.iml new file mode 100644 index 0000000..fbc44a8 --- /dev/null +++ b/.idea/.idea.SandboxFunctions/.idea/riderModule.iml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.SandboxFunctions/.idea/vcs.xml b/.idea/.idea.SandboxFunctions/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/.idea.SandboxFunctions/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/.idea.SandboxFunctions/.idea/workspace.xml b/.idea/.idea.SandboxFunctions/.idea/workspace.xml new file mode 100644 index 0000000..d31c003 --- /dev/null +++ b/.idea/.idea.SandboxFunctions/.idea/workspace.xml @@ -0,0 +1,112 @@ + + + + CustomerFunctions/CustomerFunctions.csproj + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1609631825791 + + + + + + + + + + + + + + + + + file://$PROJECT_DIR$/AcceptanceTests/CustomerAcceptanceTests.cs + 92 + + + + + + + + + + + \ No newline at end of file diff --git a/AcceptanceTests/AcceptanceTests.csproj b/AcceptanceTests/AcceptanceTests.csproj new file mode 100644 index 0000000..b915bc5 --- /dev/null +++ b/AcceptanceTests/AcceptanceTests.csproj @@ -0,0 +1,36 @@ + + + + netcoreapp3.1 + + false + + + + + + + + + + + + + + + + + + + + + + Always + + + + Always + + + + diff --git a/AcceptanceTests/CustomerAcceptanceTests.cs b/AcceptanceTests/CustomerAcceptanceTests.cs new file mode 100644 index 0000000..ea0feb1 --- /dev/null +++ b/AcceptanceTests/CustomerAcceptanceTests.cs @@ -0,0 +1,27 @@ +using Microsoft.Extensions.Primitives; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using AzureFunctions.AcceptanceTest.Runner; +using CustomerFunctions; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Azure.WebJobs; + +namespace AcceptanceTests +{ + [TestClass] + public class CustomerAcceptanceTests : BaseFunctionAcceptanceTests + { + [TestMethod] + public async Task WhenCallCustomerGet_ShouldReturn201() + { + var query = new Dictionary(); + var body = "{\"name\":\"yamada\"}"; + var req = HttpRequestSetup(query, body, "get"); + var result = await AzureFunctionInvoker.Invoke((request) => CustomerFunction.Run(request, logger), req); + var resultObject = (OkObjectResult) result; + Assert.AreEqual("Hello, yamada. This HTTP triggered function executed successfully.", resultObject.Value); + } + } +} \ No newline at end of file diff --git a/AcceptanceTests/appsettings.Debug.json b/AcceptanceTests/appsettings.Debug.json new file mode 100644 index 0000000..73e9de5 --- /dev/null +++ b/AcceptanceTests/appsettings.Debug.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + }, + "environment": "debug" +} \ No newline at end of file diff --git a/AcceptanceTests/appsettings.json b/AcceptanceTests/appsettings.json new file mode 100644 index 0000000..8f7d855 --- /dev/null +++ b/AcceptanceTests/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + }, + "environment": "localhost" +} \ No newline at end of file diff --git a/AzureFunctions.AcceptanceTest.Runner/AzureFunctionInvoker.cs b/AzureFunctions.AcceptanceTest.Runner/AzureFunctionInvoker.cs new file mode 100644 index 0000000..b511c3a --- /dev/null +++ b/AzureFunctions.AcceptanceTest.Runner/AzureFunctionInvoker.cs @@ -0,0 +1,82 @@ +using System; +using System.IO; +using System.Linq.Expressions; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using RestSharp; + +namespace AzureFunctions.AcceptanceTest.Runner +{ + public static class AzureFunctionInvoker + { + public static async Task Invoke(Expression>> func, + HttpRequest data) + { + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true); + + var config = builder.Build(); + var stage = config["environment"]; + switch (stage) + { + case "remote": + { + var url = config["url"]; + return await RestCall(data, url); + } + case "localhost": + { + throw new NotImplementedException("Need to implement this feature..."); + } + case "debug": + { + return await func.Compile().Invoke(data); + } + default: + { + throw new ArgumentException("Please set stage variable to run acceptance tests."); + } + } + } + + private static async Task RestCall(HttpRequest data, string url) + { + if (string.IsNullOrEmpty(url)) + { + throw new ApplicationException("Remote call exception url not defined"); + } + + var client = new RestClient(url); + var request = new RestRequest($"customer", FromString(data.Method)); + StreamReader reader = new StreamReader(data.Body); + string text = await reader.ReadToEndAsync(); + request.AddJsonBody(text); + var result = await client.ExecuteAsync(request); + return new ActionResult(result.Content).Result; + } + + private static Method FromString(string method) + { + switch (method.ToUpper()) + { + case "POST": + return Method.POST; + case "GET": + return Method.GET; + case "PUT": + return Method.PUT; + } + + throw new NotImplementedException($"Not Supported Method: {method}"); + } + } + + public class FunctionParameters + { + public string Endpoint { get; set; } + public string[] Verbs { get; set; } + } +} \ No newline at end of file diff --git a/AzureFunctions.AcceptanceTest.Runner/AzureFunctions.AcceptanceTest.Runner.csproj b/AzureFunctions.AcceptanceTest.Runner/AzureFunctions.AcceptanceTest.Runner.csproj new file mode 100644 index 0000000..7372176 --- /dev/null +++ b/AzureFunctions.AcceptanceTest.Runner/AzureFunctions.AcceptanceTest.Runner.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp3.1 + + + + + + + + + + + + + + + + diff --git a/AzureFunctions.AcceptanceTest.Runner/FunctionTest.cs b/AzureFunctions.AcceptanceTest.Runner/FunctionTest.cs new file mode 100644 index 0000000..e4bf6f4 --- /dev/null +++ b/AzureFunctions.AcceptanceTest.Runner/FunctionTest.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System.IO; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Internal; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Primitives; +using Moq; + +namespace AzureFunctions.AcceptanceTest.Runner +{ + public abstract class BaseFunctionAcceptanceTests + { + protected ILogger logger = TestLogger.Create(); + + public HttpRequest HttpRequestSetup(Dictionary query, string body, string verb) + { + var reqMock = new Mock(); + + reqMock.Setup(req => req.Query).Returns(new QueryCollection(query)); + var stream = new MemoryStream(); + var writer = new StreamWriter(stream); + writer.Write(body); + writer.Flush(); + stream.Position = 0; + reqMock.Setup(req => req.Body).Returns(stream); + reqMock.Setup(x => x.Method).Returns(verb); + return reqMock.Object; + } + } +} \ No newline at end of file diff --git a/AzureFunctions.AcceptanceTest.Runner/TestLogger.cs b/AzureFunctions.AcceptanceTest.Runner/TestLogger.cs new file mode 100644 index 0000000..461f984 --- /dev/null +++ b/AzureFunctions.AcceptanceTest.Runner/TestLogger.cs @@ -0,0 +1,30 @@ +using System; +using Microsoft.Extensions.Logging; + +namespace AzureFunctions.AcceptanceTest.Runner +{ + public static class TestLogger + { + public static ILogger Create() + { + var logger = new ConsoleUnitLogger(); + return logger; + } + + class ConsoleUnitLogger : ILogger, IDisposable + { + private readonly Action output = Console.WriteLine; + + public void Dispose() + { + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, + Func formatter) => output(formatter(state, exception)); + + public bool IsEnabled(LogLevel logLevel) => true; + + public IDisposable BeginScope(TState state) => this; + } + } +} \ No newline at end of file diff --git a/CustomerFunctions/CustomerFunction.cs b/CustomerFunctions/CustomerFunction.cs index f39fb68..1140615 100644 --- a/CustomerFunctions/CustomerFunction.cs +++ b/CustomerFunctions/CustomerFunction.cs @@ -10,7 +10,7 @@ namespace CustomerFunctions { - public static class Function1 + public static class CustomerFunction { [FunctionName("customer")] public static async Task Run( diff --git a/SandboxFunctions.sln b/SandboxFunctions.sln index 2deb33f..fb088de 100644 --- a/SandboxFunctions.sln +++ b/SandboxFunctions.sln @@ -3,7 +3,11 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.30804.86 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CustomerFunctions", "CustomerFunctions\CustomerFunctions.csproj", "{47F2C452-9AC5-492B-962E-AB9A4765196F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CustomerFunctions", "CustomerFunctions\CustomerFunctions.csproj", "{47F2C452-9AC5-492B-962E-AB9A4765196F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AcceptanceTests", "AcceptanceTests\AcceptanceTests.csproj", "{1E18BAC8-E356-467E-9219-562A77D7BED8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AzureFunctions.AcceptanceTest.Runner", "AzureFunctions.AcceptanceTest.Runner\AzureFunctions.AcceptanceTest.Runner.csproj", "{1C603F55-2144-4B7B-89AE-4E2E5FE82AF7}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -15,6 +19,14 @@ Global {47F2C452-9AC5-492B-962E-AB9A4765196F}.Debug|Any CPU.Build.0 = Debug|Any CPU {47F2C452-9AC5-492B-962E-AB9A4765196F}.Release|Any CPU.ActiveCfg = Release|Any CPU {47F2C452-9AC5-492B-962E-AB9A4765196F}.Release|Any CPU.Build.0 = Release|Any CPU + {1E18BAC8-E356-467E-9219-562A77D7BED8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1E18BAC8-E356-467E-9219-562A77D7BED8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1E18BAC8-E356-467E-9219-562A77D7BED8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1E18BAC8-E356-467E-9219-562A77D7BED8}.Release|Any CPU.Build.0 = Release|Any CPU + {1C603F55-2144-4B7B-89AE-4E2E5FE82AF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1C603F55-2144-4B7B-89AE-4E2E5FE82AF7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1C603F55-2144-4B7B-89AE-4E2E5FE82AF7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1C603F55-2144-4B7B-89AE-4E2E5FE82AF7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE