Remove Pulumi.Host and CSI, in favor of dotnet run

This feels more natrual, even if the code you end up writing to
describe your application has a little more ceremony. We also don't
have to worry about all the crazy things we likely would have had to
worry about if we continued down the CSI path, or some path where we
had a middle stage that reflection loaded an actual binary and invoked
into it (I had a fear in the back of my mind that at some point we'd
actually have to start using AssemblyLoadContext).

This model is pretty easy to internalize, as well.

The major change from the language plugin point of view is that
instead of passing command line arguments to the executor, we just set
a bunch of `PULUMI_XXX` env-vars, which parts of our system know how
to use.

Next up, Output tracking.
This commit is contained in:
Matt Ellis 2018-06-20 14:16:02 -07:00
parent ea7ce22746
commit 9cdcd77015
15 changed files with 120 additions and 246 deletions

View file

@ -1,28 +0,0 @@
{
// Use IntelliSense to find out which attributes exist for C# debugging
// Use hover for the description of the existing attributes
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
"version": "0.2.0",
"configurations": [
{
"name": ".NET Core Launch (console)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/bin/Debug/netcoreapp2.0/DummyHost.dll",
"args": [],
"cwd": "${workspaceFolder}",
// For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window
"console": "internalConsole",
"stopAtEntry": false,
"internalConsoleOptions": "openOnSessionStart"
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach",
"processId": "${command:pickProcess}"
}
]
}

View file

@ -1,27 +0,0 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/pulumi-language-dotnet-exec.csproj"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"-r",
"linux-x64",
"${workspaceFolder}/pulumi-language-dotnet-exec.csproj"
],
"problemMatcher": "$msCompile"
}
]
}

View file

@ -1,63 +0,0 @@
using Grpc.Core;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;
using Microsoft.CodeAnalysis.Scripting.Hosting;
using Mono.Options;
using Pulumi;
using Pulumirpc;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Runtime.InteropServices;
namespace Pulumi.Host
{
class Program
{
static void Main(string[] args)
{
string monitor = "";
string engine = "";
string project = "";
string stack = "";
string pwd = "";
string dryRun = "";
int parallel = 1;
string tracing = "";
OptionSet o = new OptionSet {
{"monitor=", "", m => monitor = m },
{"engine=", "", e => engine = e},
{"project=", "", p => project = p},
{"stack=", "", s => stack = s },
{"pwd=", "", wd => pwd = wd},
{"dry_run=", dry => dryRun = dry},
{"parallel=", (int n) => parallel = n},
{"tracing=", t => tracing = t},
};
List<string> extra = o.Parse(args);
Channel engineChannel = new Channel(engine, ChannelCredentials.Insecure);
Channel monitorChannel = new Channel(monitor, ChannelCredentials.Insecure);
Runtime.Initialize(new Runtime.Settings(new Engine.EngineClient(engineChannel),
new ResourceMonitor.ResourceMonitorClient(monitorChannel),
stack, project, parallel, true));
Console.WriteLine($"Running with \U0001F379 on {RuntimeInformation.FrameworkDescription} on {RuntimeInformation.OSDescription}");
Script<object> script = CSharpScript.Create(File.OpenRead("main.csx"));
script.Compile();
Runtime.RunInStack(() => {
script.RunAsync().Wait();
});
engineChannel.ShutdownAsync().Wait();
monitorChannel.ShutdownAsync().Wait();
}
}
}

View file

@ -1,3 +0,0 @@
#!/bin/bash
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
dotnet "${DIR}/pulumi-language-dotnet-exec.dll" "$@"

View file

@ -1,25 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<Content Include="pulumi-language-dotnet-exec" CopyToPublishDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Pulumirpc\Pulumirpc.csproj" />
<ProjectReference Include="..\Pulumi\Pulumi.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Grpc.Core" Version="1.10.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="2.6.1" />
<PackageReference Include="Mono.Options" Version="5.3.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.1" />
<PackageReference Include="System.Threading.ThreadPool" Version="4.3.0" />
</ItemGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
</Project>

View file

@ -0,0 +1,40 @@
using Grpc.Core;
using Pulumi;
using Pulumirpc;
using System;
using System.IO;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
namespace Pulumi {
public static class Deployment {
// TODO(ellismg): Perhaps we should have another overload Run<T>(Func<T> f) and we use reflection over the T
// to get all public fields and properties of type Input<T> and set them as outputs?
public static void Run(Action a) {
// TODO(ellismg): any one of these could be null, and we need to guard against that for ones that must
// be set (I don't know the set off the top of my head. I think that everything except tracing is
// required. Also, they could be bad values (e.g. parallel may not be something that can be `bool.Parsed`
// and we'd like to fail in a nicer manner.
string monitor = Environment.GetEnvironmentVariable("PULUMI_MONITOR");
string engine = Environment.GetEnvironmentVariable("PULUMI_ENGINE");
string project = Environment.GetEnvironmentVariable("PULUMI_PROJECT");
string stack = Environment.GetEnvironmentVariable("PULUMI_STACK");
string pwd = Environment.GetEnvironmentVariable("PULUMI_PWD");
string dryRun = Environment.GetEnvironmentVariable("PULUMI_DRY_RUN");
string parallel = Environment.GetEnvironmentVariable("PULUMI_PARALLEL");
string tracing = Environment.GetEnvironmentVariable("PULUMI_TRACING");
Channel engineChannel = new Channel(engine, ChannelCredentials.Insecure);
Channel monitorChannel = new Channel(monitor, ChannelCredentials.Insecure);
Runtime.Initialize(new Runtime.Settings(new Engine.EngineClient(engineChannel),
new ResourceMonitor.ResourceMonitorClient(monitorChannel),
stack, project, int.Parse(parallel), bool.Parse(dryRun)));
Console.WriteLine($"Running with \U0001F379 on {RuntimeInformation.FrameworkDescription} on {RuntimeInformation.OSDescription}");
Runtime.RunInStack(a);
}
}
}

View file

@ -1,4 +1,5 @@
using System;
using System.Threading.Tasks;
using Pulumirpc;
namespace Pulumi
@ -29,7 +30,7 @@ namespace Pulumi
public static void RunInStack(Action run)
{
Root = new ComponentResource("pulumi:pulumi:Stack", $"{Runtime.Project}-{Runtime.Stack}", null, ResourceOptions.None);
run();
Task.Run(run).Wait();
}
public class Settings

View file

@ -3,7 +3,6 @@ set -eou pipefail
IFS="\n\t"
GOBIN=/opt/pulumi/bin go install ./cmd/pulumi-language-dotnet
dotnet publish Pulumi.Host/pulumi-language-dotnet-exec.csproj
export PATH=/opt/pulumi/bin:$(go env GOPATH)/src/github.com/pulumi/pulumi/sdk/dotnet/Pulumi.Host/bin/Debug/netcoreapp2.0/publish:$PATH
cd examples
export PATH=/opt/pulumi/bin:$PATH
cd examples/bucket
pulumi update --diff --yes

View file

@ -18,7 +18,6 @@ import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"syscall"
@ -33,14 +32,6 @@ import (
"google.golang.org/grpc"
)
const (
// By convention, the executor is the name of the current program (pulumi-language-dotnet) plus this suffix.
dotnetExecSuffix = "-exec" // the exec shim for Pulumi to run Python programs.
// The runtime expects the config object to be saved to this environment variable.
pulumiConfigVar = "PULUMI_CONFIG"
)
// Launches the language host RPC endpoint, which in turn fires up an RPC server implementing the
// LanguageRuntimeServer RPC endpoint.
func main() {
@ -59,16 +50,9 @@ func main() {
cmdutil.InitTracing("pulumi-language-dotnet", "pulumi-language-dotnet", tracing)
var dotnetExec string
if givenExecutor == "" {
// The -exec binary is the same name as the current language host, except that we must trim off
// the file extension (if any) and then append -exec to it.
bin := filepath.Base(os.Args[0])
if ext := filepath.Ext(bin); ext != "" {
bin = bin[:len(bin)-len(ext)]
}
bin += dotnetExecSuffix
pathExec, err := exec.LookPath(bin)
pathExec, err := exec.LookPath("dotnet")
if err != nil {
err = errors.Wrapf(err, "could not find `%s` on the $PATH", bin)
err = errors.Wrap(err, "could not find `dotnet` on the $PATH")
cmdutil.Exit(err)
}
@ -131,13 +115,18 @@ func (host *dotnetLanguageHost) GetRequiredPlugins(ctx context.Context,
// RPC endpoint for LanguageRuntimeServer::Run
func (host *dotnetLanguageHost) Run(ctx context.Context, req *pulumirpc.RunRequest) (*pulumirpc.RunResponse, error) {
args := host.constructArguments(req)
config, err := host.constructConfig(req)
if err != nil {
err = errors.Wrap(err, "failed to serialize configuration")
return nil, err
}
args := []string{"run"}
if req.GetProgram() != "" {
args = append(args, req.GetProgram())
}
if glog.V(5) {
commandStr := strings.Join(args, " ")
glog.V(5).Infoln("Language host launching process: ", host.exec, commandStr)
@ -148,9 +137,7 @@ func (host *dotnetLanguageHost) Run(ctx context.Context, req *pulumirpc.RunReque
cmd := exec.Command(host.exec, args...) // nolint: gas, intentionally running dynamic program name.
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if config != "" {
cmd.Env = append(os.Environ(), pulumiConfigVar+"="+config)
}
cmd.Env = host.constructEnv(req, config)
if err := cmd.Run(); err != nil {
if exiterr, ok := err.(*exec.ExitError); ok {
// If the program ran, but exited with a non-zero error code. This will happen often, since user
@ -172,36 +159,26 @@ func (host *dotnetLanguageHost) Run(ctx context.Context, req *pulumirpc.RunReque
return &pulumirpc.RunResponse{Error: errResult}, nil
}
// constructArguments constructs a command-line for `pulumi-language-dotnet`
// by enumerating all of the optional and non-optional arguments present
// in a RunRequest.
func (host *dotnetLanguageHost) constructArguments(req *pulumirpc.RunRequest) []string {
var args []string
maybeAppendArg := func(k, v string) {
func (host *dotnetLanguageHost) constructEnv(req *pulumirpc.RunRequest, config string) []string {
env := os.Environ()
maybeAppendEnv := func(k, v string) {
if v != "" {
args = append(args, "--"+k, v)
env = append(env, strings.ToUpper("PULUMI_"+k)+"="+v)
}
}
maybeAppendArg("monitor", req.GetMonitorAddress())
maybeAppendArg("engine", host.engineAddress)
maybeAppendArg("project", req.GetProject())
maybeAppendArg("stack", req.GetStack())
maybeAppendArg("pwd", req.GetPwd())
maybeAppendArg("dry_run", fmt.Sprintf("%v", req.GetDryRun()))
maybeAppendArg("parallel", fmt.Sprint(req.GetParallel()))
maybeAppendArg("tracing", host.tracing)
// If no program is specified, just default to the current directory (which will invoke "__main__.py").
if req.GetProgram() == "" {
args = append(args, ".")
} else {
args = append(args, req.GetProgram())
}
args = append(args, req.GetArgs()...)
return args
maybeAppendEnv("monitor", req.GetMonitorAddress())
maybeAppendEnv("engine", host.engineAddress)
maybeAppendEnv("project", req.GetProject())
maybeAppendEnv("stack", req.GetStack())
maybeAppendEnv("pwd", req.GetPwd())
maybeAppendEnv("dry_run", fmt.Sprintf("%v", req.GetDryRun()))
maybeAppendEnv("parallel", fmt.Sprint(req.GetParallel()))
maybeAppendEnv("tracing", host.tracing)
maybeAppendEnv("config", config)
return env
}
// constructConfig json-serializes the configuration data given as part of a RunRequest.

View file

@ -5,8 +5,6 @@ VisualStudioVersion = 15.0.26124.0
MinimumVisualStudioVersion = 15.0.26124.0
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pulumi", "Pulumi\Pulumi.csproj", "{0A2BFED8-13F3-43A4-A38B-B5D1651203EB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "pulumi-language-dotnet-exec", "Pulumi.Host\pulumi-language-dotnet-exec.csproj", "{966A7C80-0122-423E-9A73-7AD4D1CDCFBE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pulumirpc", "Pulumirpc\Pulumirpc.csproj", "{2B15CCF1-2D54-4E35-B42D-8747193FC70F}"
EndProject
Global
@ -34,18 +32,6 @@ Global
{0A2BFED8-13F3-43A4-A38B-B5D1651203EB}.Release|x64.Build.0 = Release|Any CPU
{0A2BFED8-13F3-43A4-A38B-B5D1651203EB}.Release|x86.ActiveCfg = Release|Any CPU
{0A2BFED8-13F3-43A4-A38B-B5D1651203EB}.Release|x86.Build.0 = Release|Any CPU
{966A7C80-0122-423E-9A73-7AD4D1CDCFBE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{966A7C80-0122-423E-9A73-7AD4D1CDCFBE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{966A7C80-0122-423E-9A73-7AD4D1CDCFBE}.Debug|x64.ActiveCfg = Debug|Any CPU
{966A7C80-0122-423E-9A73-7AD4D1CDCFBE}.Debug|x64.Build.0 = Debug|Any CPU
{966A7C80-0122-423E-9A73-7AD4D1CDCFBE}.Debug|x86.ActiveCfg = Debug|Any CPU
{966A7C80-0122-423E-9A73-7AD4D1CDCFBE}.Debug|x86.Build.0 = Debug|Any CPU
{966A7C80-0122-423E-9A73-7AD4D1CDCFBE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{966A7C80-0122-423E-9A73-7AD4D1CDCFBE}.Release|Any CPU.Build.0 = Release|Any CPU
{966A7C80-0122-423E-9A73-7AD4D1CDCFBE}.Release|x64.ActiveCfg = Release|Any CPU
{966A7C80-0122-423E-9A73-7AD4D1CDCFBE}.Release|x64.Build.0 = Release|Any CPU
{966A7C80-0122-423E-9A73-7AD4D1CDCFBE}.Release|x86.ActiveCfg = Release|Any CPU
{966A7C80-0122-423E-9A73-7AD4D1CDCFBE}.Release|x86.Build.0 = Release|Any CPU
{2B15CCF1-2D54-4E35-B42D-8747193FC70F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2B15CCF1-2D54-4E35-B42D-8747193FC70F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2B15CCF1-2D54-4E35-B42D-8747193FC70F}.Debug|x64.ActiveCfg = Debug|Any CPU

View file

@ -0,0 +1,40 @@
using AWS.S3;
using Pulumi;
using System;
using System.Text;
class Program
{
static void Main(string[] args)
{
Deployment.Run(async () => {
Config config = new Config("hello-dotnet");
// Create the bucket, and make it readable.
var bucket = new Bucket(config["name"], new BucketArgs {
Acl = "public-read"
}
);
// Add some content. We can use contentBase64 for now, but next we'll want to build out the Assets pipeline so we
// can do a natural thing.
var content = new BucketObject($"{config["name"]}-content", new BucketObjectArgs {
Acl = "public-read",
Bucket = bucket,
ContentBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes("Made with \u2764, Pulumi, and .NET")),
ContentType = "text/plain; charset=utf8",
Key = "hello.txt"
}
);
// In addition to the logging here being nice, it actually forces us to block until the Tasks that represent the RPC
// calls to create the resources complete.
//
// TODO(ellismg): We need to come up with a solution here. We probably want to track all the pending tasks generated
// by Pulumi during execution and await them to complete in the host itself...
Console.WriteLine($"Bucket ID id : {await bucket.Id}");
Console.WriteLine($"Content ID id : {await content.Id}");
Console.WriteLine($"https://{await bucket.BucketDomainName}/hello.txt");
});
}
}

View file

@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../../Pulumi/Pulumi.csproj" />
</ItemGroup>
</Project>

View file

@ -1,35 +0,0 @@
#r "/home/matell/go/src/github.com/pulumi/pulumi/sdk/dotnet/Pulumi/bin/Debug/netstandard2.0/Pulumi.dll"
using AWS.S3;
using Pulumi;
using System;
using System.Text;
using System.Collections.Generic;
Config config = new Config("hello-dotnet");
// Create the bucket, and make it readable.
var bucket = new Bucket(config["name"], new BucketArgs {
Acl = "public-read"
}
);
// Add some content. We can use contentBase64 for now, but next we'll want to build out the Assets pipeline so we
// can do a natural thing.
var content = new BucketObject($"{config["name"]}-content", new BucketObjectArgs {
Acl = "public-read",
Bucket = bucket,
ContentBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes("Made with \u2764, Pulumi, and .NET")),
ContentType = "text/plain; charset=utf8",
Key = "hello.txt"
}
);
// In addition to the logging here being nice, it actually forces us to block until the Tasks that represent the RPC
// calls to create the resources complete.
//
// TODO(ellismg): We need to come up with a solution here. We probably want to track all the pending tasks generated
// by Pulumi during execution and await them to complete in the host itself...
Console.WriteLine($"Bucket ID id : {bucket.Id.Result}");
Console.WriteLine($"Content ID id : {content.Id.Result}");
Console.WriteLine($"https://{bucket.BucketDomainName.Result}/hello.txt");