Added support for running C# code in a dotnet core environment
This commit is contained in:
@@ -0,0 +1,9 @@
|
|||||||
|
FROM microsoft/dotnet:1.1.0-runtime
|
||||||
|
|
||||||
|
WORKDIR /fission-workdir
|
||||||
|
COPY out .
|
||||||
|
EXPOSE 8888
|
||||||
|
|
||||||
|
ENTRYPOINT ["dotnet"]
|
||||||
|
|
||||||
|
CMD ["fission-dotnet.dll"]
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
using Fission.DotNetCore.Compiler;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Nancy;
|
||||||
|
using System.IO;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Fission.DotNetCore
|
||||||
|
{
|
||||||
|
public class ExecutorModule : NancyModule
|
||||||
|
{
|
||||||
|
private const string CODE_PATH = "/userfunc/user";
|
||||||
|
|
||||||
|
private static Function _userFunc;
|
||||||
|
|
||||||
|
public ExecutorModule()
|
||||||
|
{
|
||||||
|
Post("/specialize", args => Specialize());
|
||||||
|
Get("/", _ => Run());
|
||||||
|
Post("/", _ => Run());
|
||||||
|
Put("/", _ => Run());
|
||||||
|
Head("/", _ => Run());
|
||||||
|
Options("/", _ => Run());
|
||||||
|
Delete("/", _ => Run());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private object Specialize()
|
||||||
|
{
|
||||||
|
var errors = new List<string>();
|
||||||
|
if (File.Exists(CODE_PATH))
|
||||||
|
{
|
||||||
|
var code = File.ReadAllText(CODE_PATH);
|
||||||
|
_userFunc = FissionCompiler.Compile(code, out errors);
|
||||||
|
if (_userFunc == null)
|
||||||
|
{
|
||||||
|
var errstr = string.Join(Environment.NewLine, errors);
|
||||||
|
Console.WriteLine(errstr);
|
||||||
|
var response = (Response)errstr;
|
||||||
|
response.StatusCode = HttpStatusCode.InternalServerError;
|
||||||
|
return response;
|
||||||
|
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var response = (Response)"Unable to locate code";
|
||||||
|
response.StatusCode = HttpStatusCode.InternalServerError;
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private object Run()
|
||||||
|
{
|
||||||
|
if (_userFunc == null)
|
||||||
|
{
|
||||||
|
var response = (Response)"Generic container: no requests supported";
|
||||||
|
response.StatusCode = HttpStatusCode.InternalServerError;
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
var args = ((DynamicDictionary)Request.Query).ToDictionary();
|
||||||
|
return _userFunc.Invoke(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.Loader;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
|
using Microsoft.CodeAnalysis.Emit;
|
||||||
|
|
||||||
|
namespace Fission.DotNetCore.Compiler
|
||||||
|
{
|
||||||
|
//adapted from this article http://www.tugberkugurlu.com/archive/compiling-c-sharp-code-into-memory-and-executing-it-with-roslyn
|
||||||
|
class FissionCompiler
|
||||||
|
{
|
||||||
|
public static Function Compile(string code, out List<string> errors) {
|
||||||
|
errors = new List<string>();
|
||||||
|
|
||||||
|
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(code);
|
||||||
|
|
||||||
|
string assemblyName = Path.GetRandomFileName();
|
||||||
|
MetadataReference[] references = new MetadataReference[]
|
||||||
|
{
|
||||||
|
MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location)
|
||||||
|
};
|
||||||
|
CSharpCompilation compilation = CSharpCompilation.Create(
|
||||||
|
assemblyName,
|
||||||
|
syntaxTrees: new[] { syntaxTree },
|
||||||
|
references: references,
|
||||||
|
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
|
||||||
|
|
||||||
|
using (var ms = new MemoryStream())
|
||||||
|
{
|
||||||
|
EmitResult result = compilation.Emit(ms);
|
||||||
|
|
||||||
|
if (!result.Success)
|
||||||
|
{
|
||||||
|
IEnumerable<Diagnostic> failures = result.Diagnostics.Where(diagnostic =>
|
||||||
|
diagnostic.IsWarningAsError ||
|
||||||
|
diagnostic.Severity == DiagnosticSeverity.Error);
|
||||||
|
|
||||||
|
foreach (Diagnostic diagnostic in failures)
|
||||||
|
{
|
||||||
|
errors.Add($"{diagnostic.Id}: {diagnostic.GetMessage()}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ms.Seek(0, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
Assembly assembly = AssemblyLoadContext.Default.LoadFromStream(ms);
|
||||||
|
var type = assembly.GetType("Fission");
|
||||||
|
var info = type.GetMember("Run").First() as MethodInfo;
|
||||||
|
return new Function(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Fission.DotNetCore.Compiler
|
||||||
|
{
|
||||||
|
class Function
|
||||||
|
{
|
||||||
|
private readonly MethodInfo _info;
|
||||||
|
public Function(MethodInfo info)
|
||||||
|
{
|
||||||
|
if (info == null) throw new ArgumentNullException(nameof(info));
|
||||||
|
_info = info;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Invoke(Dictionary<string, object> args)
|
||||||
|
{
|
||||||
|
return _info.Invoke(null, new[] { args });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+30
@@ -0,0 +1,30 @@
|
|||||||
|
using System.IO;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Nancy.Owin;
|
||||||
|
|
||||||
|
namespace Fission.DotNetCore
|
||||||
|
{
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
var host = new WebHostBuilder()
|
||||||
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
|
.UseKestrel()
|
||||||
|
.UseUrls("http://*:8888")
|
||||||
|
.UseStartup<Startup>()
|
||||||
|
.Build();
|
||||||
|
host.Run();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
public class Startup
|
||||||
|
{
|
||||||
|
public void Configure(IApplicationBuilder app)
|
||||||
|
{
|
||||||
|
app.UseOwin(x => x.UseNancy());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
# Fission: dotnet C# Environment
|
||||||
|
|
||||||
|
This is a simple dotnet C# environment for Fission.
|
||||||
|
|
||||||
|
It's a Docker image containing the dotnet 1.1.0 runtime. The image
|
||||||
|
uses Kestrel with Nancy to host the internal web server and uses
|
||||||
|
Roslyn to compile the uploaded code.
|
||||||
|
|
||||||
|
The image supports compiling and running code with types defined in
|
||||||
|
mscorlib and does not at present support other library references.
|
||||||
|
One workaround for this would be to add the references to this project's
|
||||||
|
project.json file and rebuild the container.
|
||||||
|
|
||||||
|
The environment works via convention where you create a C# class
|
||||||
|
called Fission which has a static method named Run taking a single
|
||||||
|
parameter, a dictionary containing any querystring parameters.
|
||||||
|
|
||||||
|
Example of simplest possible class to be executed:
|
||||||
|
|
||||||
|
```
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
public class Fission {
|
||||||
|
public static string Run(Dictionary<string, object> args) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Please see examples below.
|
||||||
|
|
||||||
|
## Rebuilding and pushing the image
|
||||||
|
|
||||||
|
To rebuild the image you need either a computer with dotnet 1.1.0
|
||||||
|
installed or else you will have to map the source directory into a
|
||||||
|
container containing the dotnet 1.1.0 environment.
|
||||||
|
|
||||||
|
### Locally installed Dotnet 1.1.0
|
||||||
|
|
||||||
|
Simply move to the source directory in a terminal and run the ./build.sh script.
|
||||||
|
|
||||||
|
The script will restore dependencies, compile a release build and
|
||||||
|
and build the container. If you need to change the name of the container
|
||||||
|
simply change it in the script.
|
||||||
|
|
||||||
|
After the build finishes push the new image to a Docker registry using the
|
||||||
|
standard procedure.
|
||||||
|
|
||||||
|
### Build in a container
|
||||||
|
|
||||||
|
Move to the directory containing the source and start the Docker container
|
||||||
|
with dotnet and mount the current directory to a build location:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run -it --rm -v $PWD:/build microsoft/dotnet
|
||||||
|
```
|
||||||
|
|
||||||
|
Move to the build directory inside the container and restore the packages:
|
||||||
|
|
||||||
|
```
|
||||||
|
cd /build
|
||||||
|
dotnet restore
|
||||||
|
log : Restoring packages for /source/project.json...
|
||||||
|
log : Installing System.Net.WebSockets 4.0.0.
|
||||||
|
log : Installing runtime.native.System.IO.Compression 4.1.0.
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
Compile and publish a release build of the source to the 'out' folder:
|
||||||
|
|
||||||
|
```
|
||||||
|
dotnet publish -c Release -o out
|
||||||
|
Publishing source for .NETCoreApp,Version
|
||||||
|
...
|
||||||
|
```
|
||||||
|
Exit the build container and build the Docker container on the local host:
|
||||||
|
|
||||||
|
```
|
||||||
|
exit
|
||||||
|
docker build -t USER/dotnet-env .
|
||||||
|
```
|
||||||
|
After the build finishes push the new image to a Docker registry using the
|
||||||
|
standard procedure.
|
||||||
|
|
||||||
|
## Echo example
|
||||||
|
|
||||||
|
### Setup fission environment
|
||||||
|
First you need to setup the fission according to your cluster setup as
|
||||||
|
specified here: https://github.com/fission/fission
|
||||||
|
|
||||||
|
|
||||||
|
### Create the class to run
|
||||||
|
|
||||||
|
Secondly you need to create a file /tmp/func.cs containing the following code:
|
||||||
|
|
||||||
|
```
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
public class Fission {
|
||||||
|
public static string Run(Dictionary<string, object> args) {
|
||||||
|
return (string)args["text"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### Run the example
|
||||||
|
|
||||||
|
Lastly to run the example:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ fission env create --name dotnet --image fission/dotnet-env
|
||||||
|
|
||||||
|
$ fission function create --name echo --env dotnet --code /tmp/func.cs
|
||||||
|
|
||||||
|
$ fission route create --method GET --url /echo --function echo
|
||||||
|
|
||||||
|
$ curl http://$FISSION_ROUTER/echo?text=hello%20world!
|
||||||
|
hello world
|
||||||
|
```
|
||||||
|
|
||||||
|
## Addition service example
|
||||||
|
|
||||||
|
### Setup fission environment
|
||||||
|
First you need to setup the fission according to your cluster setup as
|
||||||
|
specified here: https://github.com/fission/fission
|
||||||
|
|
||||||
|
|
||||||
|
### Create the class to run
|
||||||
|
|
||||||
|
Secondly you need to create a file /tmp/func.cs containing the following code:
|
||||||
|
|
||||||
|
```
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
public class Fission {
|
||||||
|
public static string Run(Dictionary<string, object> args) {
|
||||||
|
var x = Convert.ToInt32(args["x"]);
|
||||||
|
var y = Convert.ToInt32(args["y"]);
|
||||||
|
return (x+y).ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### Run the example
|
||||||
|
|
||||||
|
Lastly to run the example:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ fission env create --name dotnet --image fission/dotnet-env
|
||||||
|
|
||||||
|
$ fission function create --name addition --env dotnet --code /tmp/func.cs
|
||||||
|
|
||||||
|
$ fission route create --method GET --url /add --function addition
|
||||||
|
|
||||||
|
$ curl "http://$FISSION_ROUTER/add?x=30&y=12"
|
||||||
|
42
|
||||||
|
```
|
||||||
Executable
+3
@@ -0,0 +1,3 @@
|
|||||||
|
dotnet restore
|
||||||
|
dotnet publish -c Release -o out
|
||||||
|
docker build -t fission/dotnet-env .
|
||||||
Executable
+24
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"version": "1.0.0-*",
|
||||||
|
"buildOptions": {
|
||||||
|
"emitEntryPoint": true,
|
||||||
|
"outputName": "fission-dotnet"
|
||||||
|
},
|
||||||
|
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.NETCore.App": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"type": "platform"
|
||||||
|
},
|
||||||
|
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
|
||||||
|
"Microsoft.AspNetCore.Owin": "1.0.0",
|
||||||
|
"Nancy": "2.0.0-barneyrubble",
|
||||||
|
"Microsoft.CodeAnalysis.CSharp": "1.3.0-*",
|
||||||
|
"System.Runtime.Loader": "4.0.0-*",
|
||||||
|
},
|
||||||
|
"frameworks": {
|
||||||
|
"netcoreapp1.0": {
|
||||||
|
"imports": "dnxcore50"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user