Skip to content

Commit

Permalink
Tolerate plugins with warnings (#437)
Browse files Browse the repository at this point in the history
Found this while on my adventures with dotnet plugins. If your plugin
project has any warnings it's unusable in shimless mode (i.e.
`RunPlugin`) because msbuild would first print the warning then start
the plugin and print the port number. This lead to errors like:
```
error:  plugin [E:\VisualStudio\Projects\pulumi\jsonschema\Provider\pulumi-resource-Provider] wrote an invalid port to stdout: could not parse port: strconv.Atoi: parsing "E:\\VisualStudio\\Projects\\pulumi\\jsonschema\\Provider\\Provider.fsproj : warning NU1903: Package 'System.Text.Json' 6.0.2 has a known high severity vulnerability, GHSA-8g4q-xg66-9fp4": invalid syntax
```

When running shimless these warnings don't really matter. So this PR
rewrites `RunPlugin` to do the build separately, and then invoke `dotnet
run` with the `--no-build` flag. I would have preferred to _just_ use
`dotnet run` but there doesn't seem to be anyway to pass it flags to
silence the msbuild output from it.

This is tested by adding a warning to the testprovider project that
always triggers. As we use the testprovider project in shimless mode in
the integration tests this shows that a project with warnings can still
be used.
  • Loading branch information
Frassle authored Jan 27, 2025
1 parent a3579cc commit 9e965b9
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 9 deletions.
6 changes: 6 additions & 0 deletions .changes/unreleased/Improvements-437.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
component: runtime
kind: Improvements
body: Plugins with msbuild warnings can still be run
time: 2024-12-20T22:28:08.41871359Z
custom:
PR: "437"
9 changes: 4 additions & 5 deletions integration_tests/integration_dotnet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import (
"github.com/pulumi/pulumi/pkg/v3/testing/integration"
"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
ptesting "github.com/pulumi/pulumi/sdk/v3/go/common/testing"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -462,7 +461,7 @@ func TestAboutDotnet(t *testing.T) {
languagePluginPath, err := filepath.Abs("../pulumi-language-dotnet")
require.NoError(t, err)

e := ptesting.NewEnvironment(t)
e := newEnvironmentDotnet(t)
defer e.DeleteIfNotFailed()
e.ImportDirectory("about")

Expand Down Expand Up @@ -536,7 +535,7 @@ func TestProvider(t *testing.T) {
assert.Equal(t, []interface{}{float64(1), "goodbye", true}, stack.Outputs["echoC"])
},
PrePrepareProject: func(info *engine.Projinfo) error {
e := ptesting.NewEnvironment(t)
e := newEnvironmentDotnet(t)
e.CWD = info.Root
path := info.Proj.Plugins.Providers[0].Path
_, _ = e.RunCommand("pulumi", "package", "gen-sdk", path, "--language", "dotnet")
Expand Down Expand Up @@ -636,7 +635,7 @@ func TestDebuggerAttachDotnet(t *testing.T) {
languagePluginPath, err := filepath.Abs("../pulumi-language-dotnet")
require.NoError(t, err)

e := ptesting.NewEnvironment(t)
e := newEnvironmentDotnet(t)
defer e.DeleteIfNotFailed()
e.ImportDirectory("printf")

Expand Down Expand Up @@ -695,7 +694,7 @@ func TestParameterized(t *testing.T) {
Dir: filepath.Join("parameterized"),
LocalProviders: []integration.LocalDependency{{Package: "testprovider", Path: "testprovider"}},
PrePrepareProject: func(info *engine.Projinfo) error {
e := ptesting.NewEnvironment(t)
e := newEnvironmentDotnet(t)
e.CWD = info.Root
path := info.Proj.Plugins.Providers[0].Path
_, _ = e.RunCommand("pulumi", "package", "gen-sdk", path, "pkg", "--language", "dotnet")
Expand Down
9 changes: 9 additions & 0 deletions integration_tests/integration_util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (

"github.com/pulumi/pulumi/pkg/v3/testing/integration"
"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
ptesting "github.com/pulumi/pulumi/sdk/v3/go/common/testing"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/rpcutil"
pulumirpc "github.com/pulumi/pulumi/sdk/v3/proto/go"
Expand Down Expand Up @@ -138,6 +139,14 @@ func getProviderPath(providerDir string) string {
return fmt.Sprintf("PATH=%s", providerDir)
}

func newEnvironmentDotnet(t *testing.T) *ptesting.Environment {
languagePluginPath, err := filepath.Abs("../pulumi-language-dotnet")
assert.NoError(t, err)
e := ptesting.NewEnvironment(t)
e.Env = append(e.Env, getProviderPath(languagePluginPath))
return e
}

func testDotnetProgram(t *testing.T, options *integration.ProgramTestOptions) {
languagePluginPath, err := filepath.Abs("../pulumi-language-dotnet")
assert.NoError(t, err)
Expand Down
4 changes: 4 additions & 0 deletions integration_tests/testprovider/TestProvider.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,8 @@
<ItemGroup>
<ProjectReference Include="../../sdk/Pulumi/Pulumi.csproj" />
</ItemGroup>

<Target Name="Warning" AfterTargets="Build">
<Warning Text="Warning to test plugin run still reads the port number with warnings." Condition="'true'" />
</Target>
</Project>
40 changes: 36 additions & 4 deletions pulumi-language-dotnet/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -977,14 +977,46 @@ func (host *dotnetLanguageHost) RunPlugin(
// Self-contained executable: run it directly.
executable = host.binary
default:
// Run from source.
args = append(args, "run")

// Build from source and then run. We build separately so that we can elide the build output from the
// user unless there's an error. You would think you could pass something like `-v=q` to `dotnet run`
// to get the same effect, but it doesn't work.
project := req.Info.ProgramDirectory
if req.Info.EntryPoint != "" {
project = filepath.Join(project, req.Info.EntryPoint)
}
args = append(args, "--project", project, "--")

buildArgs := []string{"build", project}

if logging.V(5) {
commandStr := strings.Join(buildArgs, " ")
logging.V(5).Infoln("Language host launching process: ", executable, " ", commandStr)
}

cmd := exec.Command(executable, buildArgs...) // nolint: gas // intentionally running dynamic program name.
cmd.Dir = req.Pwd
cmd.Env = req.Env
var buildOutput bytes.Buffer
cmd.Stdout, cmd.Stderr = &buildOutput, &buildOutput
if err = cmd.Run(); err != nil {
// Build failed for some reason. Dump the output to the user so they can see what went wrong.
stderr.Write(buildOutput.Bytes())

if exiterr, ok := err.(*exec.ExitError); ok {
if status, stok := exiterr.Sys().(syscall.WaitStatus); stok {
err = errors.Errorf("Build exited with non-zero exit code: %d", status.ExitStatus())
} else {
err = errors.Wrapf(exiterr, "Build exited unexpectedly")
}
} else {
// Otherwise, we didn't even get to run the build. This ought to never happen unless there's
// a bug or system condition that prevented us from running the language exec. Issue a scarier error.
err = errors.Wrapf(err, "Problem building plugin program (could not run language executor)")
}
return err
}

// Now run from source without re-building.
args = append(args, "run", "--no-build", "--project", project, "--")
}

// Add on all the request args to start this plugin
Expand Down

0 comments on commit 9e965b9

Please sign in to comment.