diff --git a/core/net8.0/CHANGELOG.md b/core/net8.0/CHANGELOG.md new file mode 100644 index 0000000..8abaee1 --- /dev/null +++ b/core/net8.0/CHANGELOG.md @@ -0,0 +1,23 @@ + + +# .NET 8.0 OpenWhisk Runtime Container + +## N.NN +- Initial release diff --git a/core/net8.0/Dockerfile b/core/net8.0/Dockerfile new file mode 100644 index 0000000..4436468 --- /dev/null +++ b/core/net8.0/Dockerfile @@ -0,0 +1,42 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# + +FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build + +WORKDIR /app +COPY proxy/Apache.OpenWhisk.Runtime.Common/*.csproj ./Apache.OpenWhisk.Runtime.Common/ +COPY proxy/Apache.OpenWhisk.Runtime.Dotnet.Minimal/*.csproj ./Apache.OpenWhisk.Runtime.Dotnet.Minimal/ +COPY proxy/openwhisk-runtime-dotnet.sln ./openwhisk-runtime-dotnet.sln +RUN dotnet restore + +COPY proxy/Apache.OpenWhisk.Runtime.Common/. ./Apache.OpenWhisk.Runtime.Common/ +COPY proxy/Apache.OpenWhisk.Runtime.Dotnet.Minimal/. ./Apache.OpenWhisk.Runtime.Dotnet.Minimal/ +WORKDIR /app/Apache.OpenWhisk.Runtime.Dotnet.Minimal +RUN dotnet publish -c Release -o out + +FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine AS runtime + +# Get the latest security fixes in case the base image does not contain them already. +RUN apk update \ + && apk upgrade \ + && rm -f /var/cache/apk/* + +WORKDIR /app +COPY --from=build /app/Apache.OpenWhisk.Runtime.Dotnet.Minimal/out ./ +ENV ASPNETCORE_URLS=http://+:8080 +EXPOSE 8080/tcp +ENTRYPOINT ["dotnet", "Apache.OpenWhisk.Runtime.Dotnet.Minimal.dll"] diff --git a/core/net8.0/QUICKSTART.md b/core/net8.0/QUICKSTART.md new file mode 100644 index 0000000..75a4324 --- /dev/null +++ b/core/net8.0/QUICKSTART.md @@ -0,0 +1,238 @@ + + +# Quick .NET 8.0 Action + +A .NET action is a .NET class library with a method called `Main` or `MainAsync` that has the exact signature as follows: + +Synchronous: + +```csharp +public Newtonsoft.Json.Linq.JObject Main(Newtonsoft.Json.Linq.JObject); +``` + +Asynchronous: + +```csharp +public async System.Threading.Tasks.Task MainAsync(Newtonsoft.Json.Linq.JObject); +``` + +In order to compile, test and archive .NET projects, you must have the [.NET SDK](https://dotnet.microsoft.com/en-us/download) installed locally and ensure that the `dotnet` executable is included in the `PATH` environment variable. + +For example, create a C# project called `Apache.OpenWhisk.Example.Dotnet`: + +```bash +dotnet new classlib -n Apache.OpenWhisk.Example.Dotnet -lang C# -f net8.0 +cd Apache.OpenWhisk.Example.Dotnet +``` + +Install the [Newtonsoft.Json](https://www.newtonsoft.com/json) NuGet package as follows: + +```bash +dotnet add package Newtonsoft.Json -v 13.0.3 +``` + +Now create a file called `Hello.cs` with the following content: + +Synchronous example: + +```csharp +using System; +using Newtonsoft.Json.Linq; + +namespace Apache.OpenWhisk.Example.Dotnet +{ + public class Hello + { + public JObject Main(JObject args) + { + string name = "stranger"; + if (args.ContainsKey("name")) { + name = args["name"].ToString(); + } + JObject message = new JObject(); + message.Add("greeting", new JValue($"Hello, {name}!")); + return (message); + } + } +} +``` + +Asynchronous example: + +```csharp +using System; +using Newtonsoft.Json.Linq; +using System.Threading.Tasks; + +namespace Apache.OpenWhisk.Example.Dotnet +{ + public class Hello + { + public async Task MainAsync(JObject args) + { + await Task.Delay(10); // Just do a delay to have an async/await process occur. + string name = "stranger"; + if (args.ContainsKey("name")) { + name = args["name"].ToString(); + } + JObject message = new JObject(); + message.Add("greeting", new JValue($"Hello, {name}!")); + return (message); + } + } +} +``` + +Publish the project as follows: + +```bash +dotnet publish -c Release -o out +``` + +Zip the published files as follows: + +```bash +cd out +zip -r -0 helloDotNet.zip * +``` + +You need to specify the name of the function handler using `--main` argument. +The value for `main` needs to be in the following format: +`{Assembly}::{Class Full Name}::{Method}`, e.q.: + ++ Synchronous: `Apache.OpenWhisk.Example.Dotnet::Apache.OpenWhisk.Example.Dotnet.Hello::Main` ++ Asynchronous: `Apache.OpenWhisk.Example.Dotnet::Apache.OpenWhisk.Example.Dotnet.Hello::MainAsync` + +## Create the .NET Action + +To use on a deployment of OpenWhisk that contains the runtime as a kind: + +Synchronous: + +```bash +wsk action update helloDotNet helloDotNet.zip --main Apache.OpenWhisk.Example.Dotnet::Apache.OpenWhisk.Example.Dotnet.Hello::Main --kind dotnet:8.0 +``` + +Asynchronous: + +```bash +wsk action update helloDotNet helloDotNet.zip --main Apache.OpenWhisk.Example.Dotnet::Apache.OpenWhisk.Example.Dotnet.Hello::MainAsync --kind dotnet:8.0 +``` + +## Invoke the .NET Action + +Action invocation is the same for .NET actions as it is for Swift and JavaScript actions: + +```bash +wsk action invoke --result helloDotNet --param name World +``` + +```json + { + "greeting": "Hello World!" + } +``` + +## Local Development + +```bash +./gradlew core:net8.0:distDocker +``` + +This will produce the image `whisk/action-dotnet-v8.0` + +Build and Push image + +```bash +docker login +./gradlew core:action-dotnet-v8.0:distDocker -PdockerImagePrefix=$prefix-user -PdockerRegistry=docker.io +``` + +Deploy OpenWhisk using ansible environment that contains the kind `dotnet:8.0` +Assuming you have OpenWhisk already deploy locally and `OPENWHISK_HOME` pointing to root directory of OpenWhisk core repository. + +Set `ROOTDIR` to the root directory of this repository. + +Redeploy OpenWhisk + +```bash +cd $OPENWHISK_HOME/ansible +ANSIBLE_CMD="ansible-playbook -i ${ROOTDIR}/ansible/environments/local" +$ANSIBLE_CMD setup.yml +$ANSIBLE_CMD couchdb.yml +$ANSIBLE_CMD initdb.yml +$ANSIBLE_CMD wipe.yml +$ANSIBLE_CMD openwhisk.yml +``` + +Or you can use `wskdev` and create a soft link to the target ansible environment, for example: + +```bash +ln -s ${ROOTDIR}/ansible/environments/local ${OPENWHISK_HOME}/ansible/environments/local-dotnet +wskdev fresh -t local-dotnet +``` + +### Testing + +Install dependencies from the root directory on $OPENWHISK_HOME repository + +```bash +pushd $OPENWHISK_HOME +./gradlew install +popd +``` + +Using gradle to run all tests + +```bash +./gradlew :tests:test +``` + +Using gradle to run some tests + +```bash +./gradlew :tests:test --tests Net8_0ActionContainerTests +``` + +Using IntelliJ: + +- Import project as gradle project. +- Make sure working directory is root of the project/repo + +#### Using Container Image To Test + +To use as docker action push to your own dockerhub account + +```bash +docker tag whisk/action-dotnet-v8.0 $user_prefix/action-dotnet-v8.0 +docker push $user_prefix/action-dotnet-v8.0 +``` + +Then create the action using your the image from dockerhub + +```bash +wsk action update helloDotNet helloDotNet.zip --main Apache.OpenWhisk.Example.Dotnet::Apache.OpenWhisk.Example.Dotnet.Hello::Main --docker $user_prefix/action-dotnet-v8.0 +``` + +The `$user_prefix` is usually your dockerhub user id. + +# License + +[Apache 2.0](../../LICENSE.txt) diff --git a/core/net8.0/build.gradle b/core/net8.0/build.gradle new file mode 100644 index 0000000..6a033fa --- /dev/null +++ b/core/net8.0/build.gradle @@ -0,0 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +ext.dockerImageName = 'action-dotnet-v8.0' + +apply from: '../../gradle/docker.gradle' diff --git a/core/net8.0/proxy/Apache.OpenWhisk.Runtime.Common/Apache.OpenWhisk.Runtime.Common.csproj b/core/net8.0/proxy/Apache.OpenWhisk.Runtime.Common/Apache.OpenWhisk.Runtime.Common.csproj new file mode 100644 index 0000000..044afb9 --- /dev/null +++ b/core/net8.0/proxy/Apache.OpenWhisk.Runtime.Common/Apache.OpenWhisk.Runtime.Common.csproj @@ -0,0 +1,33 @@ + + + + + net8.0 + + + + + + + + + 13.0.3 + + + + diff --git a/core/net8.0/proxy/Apache.OpenWhisk.Runtime.Common/HttpResponseExtension.cs b/core/net8.0/proxy/Apache.OpenWhisk.Runtime.Common/HttpResponseExtension.cs new file mode 100644 index 0000000..4f63bd0 --- /dev/null +++ b/core/net8.0/proxy/Apache.OpenWhisk.Runtime.Common/HttpResponseExtension.cs @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Apache.OpenWhisk.Runtime.Common +{ + public static class HttpResponseExtension + { + public static async Task WriteResponse(this HttpResponse response, int code, string content) + { + byte[] bytes = Encoding.UTF8.GetBytes(content); + response.ContentLength = bytes.Length; + response.StatusCode = code; + await response.WriteAsync(content); + } + + public static async Task WriteError(this HttpResponse response, string errorMessage) + { + JObject message = new JObject {{"error", new JValue(errorMessage)}}; + await WriteResponse(response, 502, JsonConvert.SerializeObject(message)); + } + + } +} diff --git a/core/net8.0/proxy/Apache.OpenWhisk.Runtime.Common/Init.cs b/core/net8.0/proxy/Apache.OpenWhisk.Runtime.Common/Init.cs new file mode 100644 index 0000000..0a1a054 --- /dev/null +++ b/core/net8.0/proxy/Apache.OpenWhisk.Runtime.Common/Init.cs @@ -0,0 +1,192 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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; +using System.IO.Compression; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.AspNetCore.Http; +using Newtonsoft.Json.Linq; + +namespace Apache.OpenWhisk.Runtime.Common +{ + public class Init + { + private readonly SemaphoreSlim _initSemaphoreSlim = new SemaphoreSlim(1, 1); + + public bool Initialized { get; private set; } + private Type Type { get; set; } + private MethodInfo Method { get; set; } + private ConstructorInfo Constructor { get; set; } + private bool AwaitableMethod { get; set; } + + public Init() + { + Initialized = false; + Type = null; + Method = null; + Constructor = null; + } + + public async Task HandleRequest(HttpContext httpContext) + { + await _initSemaphoreSlim.WaitAsync(); + try + { + if (Initialized) + { + await httpContext.Response.WriteError("Cannot initialize the action more than once."); + Console.Error.WriteLine("Cannot initialize the action more than once."); + return (new Run(Type, Method, Constructor, AwaitableMethod)); + } + + string body = await new StreamReader(httpContext.Request.Body).ReadToEndAsync(); + JObject inputObject = JObject.Parse(body); + if (!inputObject.ContainsKey("value")) + { + await httpContext.Response.WriteError("Missing main/no code to execute."); + return (null); + } + + JToken message = inputObject["value"]; + + if (message["main"] == null || message["binary"] == null || message["code"] == null) + { + await httpContext.Response.WriteError("Missing main/no code to execute."); + return (null); + } + + string main = message["main"].ToString(); + + bool binary = message["binary"].ToObject(); + + if (!binary) + { + await httpContext.Response.WriteError("code must be binary (zip file)."); + return (null); + } + + string[] mainParts = main.Split("::"); + if (mainParts.Length != 3) + { + await httpContext.Response.WriteError("main required format is \"Assembly::Type::Function\"."); + return (null); + } + + string tempPath = Path.Combine(Environment.CurrentDirectory, Guid.NewGuid().ToString()); + string base64Zip = message["code"].ToString(); + try + { + using (MemoryStream stream = new MemoryStream(Convert.FromBase64String(base64Zip))) + { + using (ZipArchive archive = new ZipArchive(stream)) + { + archive.ExtractToDirectory(tempPath); + } + } + } + catch (Exception) + { + await httpContext.Response.WriteError("Unable to decompress package."); + return (null); + } + + Environment.CurrentDirectory = tempPath; + + string assemblyFile = $"{mainParts[0]}.dll"; + + string assemblyPath = Path.Combine(tempPath, assemblyFile); + + if (!File.Exists(assemblyPath)) + { + await httpContext.Response.WriteError($"Unable to locate requested assembly (\"{assemblyFile}\")."); + return (null); + } + + try + { + // Export init arguments as environment variables + if (message["env"] != null && message["env"].HasValues) + { + Dictionary dictEnv = message["env"].ToObject>(); + foreach (KeyValuePair entry in dictEnv) { + // See https://docs.microsoft.com/en-us/dotnet/api/system.environment.setenvironmentvariable + // If entry.Value is null or the empty string, the variable is not set + Environment.SetEnvironmentVariable(entry.Key, entry.Value); + } + } + + Assembly assembly = Assembly.LoadFrom(assemblyPath); + Type = assembly.GetType(mainParts[1]); + if (Type == null) + { + await httpContext.Response.WriteError($"Unable to locate requested type (\"{mainParts[1]}\")."); + return (null); + } + Method = Type.GetMethod(mainParts[2]); + Constructor = Type.GetConstructor(Type.EmptyTypes); + } + catch (Exception ex) + { + Console.Error.WriteLine(ex.ToString()); + await httpContext.Response.WriteError(ex.Message +#if DEBUG + + ", " + ex.StackTrace +#endif + ); + return (null); + } + + if (Method == null) + { + await httpContext.Response.WriteError($"Unable to locate requested method (\"{mainParts[2]}\")."); + return (null); + } + + if (Constructor == null) + { + await httpContext.Response.WriteError($"Unable to locate appropriate constructor for (\"{mainParts[1]}\")."); + return (null); + } + + Initialized = true; + + AwaitableMethod = (Method.ReturnType.GetMethod(nameof(Task.GetAwaiter)) != null); + + return (new Run(Type, Method, Constructor, AwaitableMethod)); + } + catch (Exception ex) + { + Console.Error.WriteLine(ex.StackTrace); + await httpContext.Response.WriteError(ex.Message +#if DEBUG + + ", " + ex.StackTrace +#endif + ); + Startup.WriteLogMarkers(); + return (null); + } + finally + { + _initSemaphoreSlim.Release(); + } + } + } +} diff --git a/core/net8.0/proxy/Apache.OpenWhisk.Runtime.Common/Run.cs b/core/net8.0/proxy/Apache.OpenWhisk.Runtime.Common/Run.cs new file mode 100644 index 0000000..0ee99ce --- /dev/null +++ b/core/net8.0/proxy/Apache.OpenWhisk.Runtime.Common/Run.cs @@ -0,0 +1,130 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Newtonsoft.Json.Linq; + +namespace Apache.OpenWhisk.Runtime.Common +{ + public class Run + { + private readonly Type _type; + private readonly MethodInfo _method; + private readonly ConstructorInfo _constructor; + private readonly bool _awaitableMethod; + + public Run(Type type, MethodInfo method, ConstructorInfo constructor, bool awaitableMethod) + { + _type = type; + _method = method; + _constructor = constructor; + _awaitableMethod = awaitableMethod; + } + + public async Task HandleRequest(HttpContext httpContext) + { + if (_type == null || _method == null || _constructor == null) + { + await httpContext.Response.WriteError("Cannot invoke an uninitialized action."); + return; + } + + try + { + string body = await new StreamReader(httpContext.Request.Body).ReadToEndAsync(); + + JObject inputObject = string.IsNullOrEmpty(body) ? null : JObject.Parse(body); + + JObject valObject = null; + JArray valArray = null; + + if (inputObject != null) + { + valObject = inputObject["value"] as JObject; + foreach (JToken token in inputObject.Children()) + { + try + { + if (token.Path.Equals("value", StringComparison.InvariantCultureIgnoreCase)) + continue; + string envKey = $"__OW_{token.Path.ToUpperInvariant()}"; + string envVal = token.First.ToString(); + Environment.SetEnvironmentVariable(envKey, envVal); + //Console.WriteLine($"Set environment variable \"{envKey}\" to \"{envVal}\"."); + } + catch (Exception) + { + await Console.Error.WriteLineAsync( + $"Unable to set environment variable for the \"{token.Path}\" token."); + } + } + if (valObject == null) { + valArray = inputObject["value"] as JArray; + } + } + + object owObject = _constructor.Invoke(new object[] { }); + + try + { + JContainer output; + + if(_awaitableMethod) { + if (valObject != null) { + output = (JContainer) await (dynamic) _method.Invoke(owObject, new object[] {valObject}); + } else { + output = (JContainer) await (dynamic) _method.Invoke(owObject, new object[] {valArray}); + } + } + else { + if (valObject != null) { + output = (JContainer) _method.Invoke(owObject, new object[] {valObject}); + } else { + output = (JContainer) _method.Invoke(owObject, new object[] {valArray}); + } + } + + if (output == null) + { + await httpContext.Response.WriteError("The action returned null"); + Console.Error.WriteLine("The action returned null"); + return; + } + + await httpContext.Response.WriteResponse(200, output.ToString()); + } + catch (Exception ex) + { + Console.Error.WriteLine(ex.StackTrace); + await httpContext.Response.WriteError(ex.Message +#if DEBUG + + ", " + ex.StackTrace +#endif + ); + } + } + finally + { + Startup.WriteLogMarkers(); + } + } + } +} diff --git a/core/net8.0/proxy/Apache.OpenWhisk.Runtime.Common/Startup.cs b/core/net8.0/proxy/Apache.OpenWhisk.Runtime.Common/Startup.cs new file mode 100644 index 0000000..9851e54 --- /dev/null +++ b/core/net8.0/proxy/Apache.OpenWhisk.Runtime.Common/Startup.cs @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; + +namespace Apache.OpenWhisk.Runtime.Common +{ + public class Startup + { + public static void WriteLogMarkers() + { + Console.WriteLine("XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX"); + Console.Error.WriteLine("XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX"); + } + + public void Configure(IApplicationBuilder app) + { + PathString initPath = new PathString("/init"); + PathString runPath = new PathString("/run"); + Init init = new Init(); + Run run = null; + app.Run(async (httpContext) => + { + if (httpContext.Request.Path.Equals(initPath)) + { + run = await init.HandleRequest(httpContext); + + if (run != null) + await httpContext.Response.WriteResponse(200, "OK"); + + return; + } + + if (httpContext.Request.Path.Equals(runPath)) + { + if (!init.Initialized) + { + await httpContext.Response.WriteError("Cannot invoke an uninitialized action."); + return; + } + + if (run == null) + { + await httpContext.Response.WriteError("Cannot invoke an uninitialized action."); + return; + } + + await run.HandleRequest(httpContext); + } + } + ); + } + } +} diff --git a/core/net8.0/proxy/Apache.OpenWhisk.Runtime.Dotnet.Minimal/Apache.OpenWhisk.Runtime.Dotnet.Minimal.csproj b/core/net8.0/proxy/Apache.OpenWhisk.Runtime.Dotnet.Minimal/Apache.OpenWhisk.Runtime.Dotnet.Minimal.csproj new file mode 100644 index 0000000..75b4bda --- /dev/null +++ b/core/net8.0/proxy/Apache.OpenWhisk.Runtime.Dotnet.Minimal/Apache.OpenWhisk.Runtime.Dotnet.Minimal.csproj @@ -0,0 +1,28 @@ + + + + + Exe + net8.0 + + + + + + + diff --git a/core/net8.0/proxy/Apache.OpenWhisk.Runtime.Dotnet.Minimal/Program.cs b/core/net8.0/proxy/Apache.OpenWhisk.Runtime.Dotnet.Minimal/Program.cs new file mode 100644 index 0000000..6d3c1d0 --- /dev/null +++ b/core/net8.0/proxy/Apache.OpenWhisk.Runtime.Dotnet.Minimal/Program.cs @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 Apache.OpenWhisk.Runtime.Common; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Logging; + +namespace Apache.OpenWhisk.Runtime.Dotnet.Minimal +{ + class Program + { + static void Main(string[] args) + { + CreateWebHostBuilder(args).Build().Run(); + } + + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseKestrel(options => + { + options.Limits.MaxRequestBodySize = null; + }) + .ConfigureLogging((hostingContext, logging) => + { + logging.ClearProviders(); + }) + .SuppressStatusMessages(true) + .UseStartup(); + + } +} + diff --git a/core/net8.0/proxy/build.gradle b/core/net8.0/proxy/build.gradle new file mode 100644 index 0000000..6a5d94e --- /dev/null +++ b/core/net8.0/proxy/build.gradle @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +plugins { + id "net.karlmartens.dotnet" version "0.2.20" +} + +repositories { + mavenCentral() +} + +dotnet { + configuration 'Release' +} diff --git a/core/net8.0/proxy/gradle/wrapper/gradle-wrapper.jar b/core/net8.0/proxy/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..f6b961f Binary files /dev/null and b/core/net8.0/proxy/gradle/wrapper/gradle-wrapper.jar differ diff --git a/core/net8.0/proxy/gradle/wrapper/gradle-wrapper.properties b/core/net8.0/proxy/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..d475d27 --- /dev/null +++ b/core/net8.0/proxy/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,21 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.5.1-bin.zip diff --git a/core/net8.0/proxy/gradlew b/core/net8.0/proxy/gradlew new file mode 100644 index 0000000..d4ee78f --- /dev/null +++ b/core/net8.0/proxy/gradlew @@ -0,0 +1,188 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# 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. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='-Dfile.encoding=UTF-8' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/core/net8.0/proxy/gradlew.bat b/core/net8.0/proxy/gradlew.bat new file mode 100644 index 0000000..9991c50 --- /dev/null +++ b/core/net8.0/proxy/gradlew.bat @@ -0,0 +1,100 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem http://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/core/net8.0/proxy/openwhisk-runtime-dotnet.sln b/core/net8.0/proxy/openwhisk-runtime-dotnet.sln new file mode 100644 index 0000000..28357cb --- /dev/null +++ b/core/net8.0/proxy/openwhisk-runtime-dotnet.sln @@ -0,0 +1,39 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 + +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# + +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Apache.OpenWhisk.Runtime.Dotnet.Minimal", "Apache.OpenWhisk.Runtime.Dotnet.Minimal\Apache.OpenWhisk.Runtime.Dotnet.Minimal.csproj", "{780f85c2-dcf2-40f4-86bc-d4e1ac98a29a}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Apache.OpenWhisk.Runtime.Common", "Apache.OpenWhisk.Runtime.Common\Apache.OpenWhisk.Runtime.Common.csproj", "{512bd38c-2dd9-47c9-a825-25ec0120740b}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {780f85c2-dcf2-40f4-86bc-d4e1ac98a29a}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {780f85c2-dcf2-40f4-86bc-d4e1ac98a29a}.Debug|Any CPU.Build.0 = Debug|Any CPU + {780f85c2-dcf2-40f4-86bc-d4e1ac98a29a}.Release|Any CPU.ActiveCfg = Release|Any CPU + {780f85c2-dcf2-40f4-86bc-d4e1ac98a29a}.Release|Any CPU.Build.0 = Release|Any CPU + {512bd38c-2dd9-47c9-a825-25ec0120740b}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {512bd38c-2dd9-47c9-a825-25ec0120740b}.Debug|Any CPU.Build.0 = Debug|Any CPU + {512bd38c-2dd9-47c9-a825-25ec0120740b}.Release|Any CPU.ActiveCfg = Release|Any CPU + {512bd38c-2dd9-47c9-a825-25ec0120740b}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/settings.gradle b/settings.gradle index 9fb37cf..fec2001 100644 --- a/settings.gradle +++ b/settings.gradle @@ -17,10 +17,14 @@ include ':tests' include ':tests:net6.0' +include ':tests:net8.0' include ':core:net6.0' include ':core:net6.0:proxy' +include ':core:net8.0' +include ':core:net8.0:proxy' + rootProject.name = 'runtime-dotnet' gradle.ext.openwhisk = [ diff --git a/tests/build.gradle b/tests/build.gradle index e6726fd..54c0b57 100644 --- a/tests/build.gradle +++ b/tests/build.gradle @@ -53,4 +53,5 @@ tasks.withType(ScalaCompile) { compileTestScala { dependsOn ':tests:net6.0:prepare' + dependsOn ':tests:net8.0:prepare' } diff --git a/tests/net8.0/Apache.OpenWhisk.Tests.Dotnet/Apache.OpenWhisk.Tests.Dotnet.csproj b/tests/net8.0/Apache.OpenWhisk.Tests.Dotnet/Apache.OpenWhisk.Tests.Dotnet.csproj new file mode 100644 index 0000000..c0888c8 --- /dev/null +++ b/tests/net8.0/Apache.OpenWhisk.Tests.Dotnet/Apache.OpenWhisk.Tests.Dotnet.csproj @@ -0,0 +1,32 @@ + + + + + net8.0 + + + + + + + + + + + + diff --git a/tests/net8.0/build.gradle b/tests/net8.0/build.gradle new file mode 100644 index 0000000..40c3e26 --- /dev/null +++ b/tests/net8.0/build.gradle @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +plugins { + id "net.karlmartens.dotnet" version "0.2.20" +} + +repositories { + mavenCentral() +} + +dotnet { + configuration 'Release' +} + +task prepare_distro(dependsOn: distribution) { + doLast { + copy { + from tarTree(resources.gzip(file('build/dist/net8.0.tar.gz'))) + into file('build/dist/out') + } + delete file('build/dist/net8.0.tar.gz') + } +} + +task prepare_zip(type: Zip, dependsOn: prepare_distro) { + from file('build/dist/out') + include '*' + include '**/**' + archiveName 'nettests8.0.zip' + destinationDir(file('../src/test/resources')) +} + +task prepare(type: Delete, dependsOn: prepare_zip) { + delete getProject().getBuildDir().toPath().toFile() + followSymlinks = true +} diff --git a/tests/net8.0/openwhisk-tests-dotnet.sln b/tests/net8.0/openwhisk-tests-dotnet.sln new file mode 100644 index 0000000..e8b99c2 --- /dev/null +++ b/tests/net8.0/openwhisk-tests-dotnet.sln @@ -0,0 +1,33 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 + +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# + +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Apache.OpenWhisk.Tests.Dotnet", "Apache.OpenWhisk.Tests.Dotnet\Apache.OpenWhisk.Tests.Dotnet.csproj", "{af5b98b3-909d-4fca-a5af-f2f30cf1638f}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {af5b98b3-909d-4fca-a5af-f2f30cf1638f}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {af5b98b3-909d-4fca-a5af-f2f30cf1638f}.Debug|Any CPU.Build.0 = Debug|Any CPU + {af5b98b3-909d-4fca-a5af-f2f30cf1638f}.Release|Any CPU.ActiveCfg = Release|Any CPU + {af5b98b3-909d-4fca-a5af-f2f30cf1638f}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal