Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: allow dotnet to emit better errors for component construct #361

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using Pulumi;

class Component : Pulumi.ComponentResource
{
public Component(string name, ComponentArgs args, ComponentResourceOptions? opts = null)
: base("test:index:Test", name, args, opts, remote: true)
{
}

[Output("passwordResult")]
public Output<string> PasswordResult { get; private set; } = default!;

[Output("complexResult")]
public Output<ComplexType> ComplexResult { get; set; } = default!;
}

class ComponentArgs : ResourceArgs
{
[Input("passwordLength")]
public Input<int> PasswordLength { get; set; } = null!;

[Input("complex")]
public Input<ComplexTypeArgs> Complex { get; set; } = null!;
}

public sealed class ComplexTypeArgs : global::Pulumi.ResourceArgs
{
[Input("name", required: true)]
public string Name { get; set; } = null!;

[Input("intValue", required: true)]
public int IntValue { get; set; }
}

[OutputType]
public sealed class ComplexType
{
[Output("name")]
public string Name { get; set; }

[Output("intValue")]
public int IntValue { get; set; }

[OutputConstructor]
public ComplexType(string name, int intValue)
{
Name = name;
IntValue = intValue;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Threading.Tasks;
using FluentAssertions;
using Pulumi;
using Pulumi.Utilities;

using Pulumi.IntegrationTests.Utils;

class Program
{
static async Task<int> Main(string[] args)
{
var returnCode = await Deployment.RunAsync(async () =>
{
var component10 = new Component("component10", new ComponentArgs());
});
return returnCode;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\sdk\Pulumi\Pulumi.csproj"/>
</ItemGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
name: provider_construct
runtime: dotnet

plugins:
providers:
- name: test
path: ../testcomponent-dotnet
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;
using System.Text;
using System.Threading.Tasks;
using Pulumi;

class ComponentArgs : ResourceArgs
{
}

class Component : ComponentResource
{
public Component(string name, ComponentArgs args, ComponentResourceOptions? opts = null)
: base("test:index:Test", name, args, opts)
{
throw new InputPropertiesException(
"failing for a reason",
new[]
{
new PropertyError("foo", "the failure reason"),
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Threading;
using System.Threading.Tasks;
using TestProvider;

class Program
{
public static Task Main(string []args) =>
Pulumi.Experimental.Provider.Provider.Serve(args, "0.0.1", host => new TestProviderImpl(), CancellationToken.None);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
runtime: dotnet
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<AssemblyName>pulumi-resource-test</AssemblyName>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\sdk\Pulumi\Pulumi.csproj"/>
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Pulumi.Experimental.Provider;
using Pulumi.IntegrationTests.Utils;

namespace TestProvider;

public class TestProviderImpl : ComponentResourceProviderBase
{
public override Task<ConstructResponse> Construct(ConstructRequest request, CancellationToken ct)
{
return request.Type switch
{
"test:index:Test" => Construct<ComponentArgs, Component>(request,
(name, args, options) => Task.FromResult(new Component(name, args, options))),
_ => throw new NotImplementedException()
};
}
}
37 changes: 37 additions & 0 deletions integration_tests/integration_dotnet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package integration_tests

import (
"bytes"
"encoding/json"
"fmt"
"io"
Expand Down Expand Up @@ -666,3 +667,39 @@ outer:

wg.Wait()
}

// Test failures returned from construct.
func TestConstructFailuresDotnet(t *testing.T) {
const testDir = "construct_component_failures"

tests := []struct {
componentDir string
}{
{
componentDir: "testcomponent-dotnet",
},
}
for _, test := range tests {
test := test
t.Run(test.componentDir, func(t *testing.T) {
stderr := &bytes.Buffer{}
expectedError := `error: testcomponent:index:Component resource 'component' has a problem: failing for a reason:
- property foo with value '{bar}' has a problem: the failure reason`

localProvider := integration.LocalDependency{
Package: "testcomponent", Path: filepath.Join(testDir, test.componentDir),
}
testDotnetProgram(t, &integration.ProgramTestOptions{
Dir: filepath.Join(testDir, "dotnet"),
LocalProviders: []integration.LocalDependency{localProvider},
Quick: true,
Stderr: stderr,
ExpectFailure: true,
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
output := stderr.String()
assert.Contains(t, output, expectedError)
},
})
})
}
}
13 changes: 13 additions & 0 deletions proto/pulumi/errors.proto
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,16 @@ message ErrorCause {
string stackTrace = 2;
}

// An error that can be returned from a component provider and includes details of the
// error, which can be multiple properties.
message InputPropertiesError {
// A single invalid input property.
message PropertyError {
// The path to the property that is invalid.
string property_path = 1;
// The reason the property is invalid.
string reason = 2;
}
// The list of invalid input properties.
repeated PropertyError errors = 1;
}
14 changes: 7 additions & 7 deletions proto/pulumi/language.proto
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ message AboutResponse {

message GetProgramDependenciesRequest {
string project = 1 [deprecated = true]; // the project name, the engine always sets this to "deprecated" now.
string pwd = 2 [deprecated = true]; // the program's working directory.
string program = 3 [deprecated = true]; // the path to the program.
string pwd = 2 [deprecated = true]; // the program's working directory. Deprecated, use info.program_directory instead.
string program = 3 [deprecated = true]; // the path to the program. Deprecated, use info.entry_point instead.
bool transitiveDependencies = 4; // if transitive dependencies should be included in the result.
ProgramInfo info = 5; // the program info to use to calculate dependencies.
}
Expand All @@ -108,8 +108,8 @@ message GetProgramDependenciesResponse {

message GetRequiredPluginsRequest {
string project = 1 [deprecated = true]; // the project name, the engine always sets this to "deprecated" now.
string pwd = 2 [deprecated = true]; // the program's working directory.
string program = 3 [deprecated = true]; // the path to the program.
string pwd = 2 [deprecated = true]; // the program's working directory. Deprecated, use info.program_directory instead.
string program = 3 [deprecated = true]; // the path to the program. Deprecated, use info.entry_point instead.
ProgramInfo info = 4; // the program info to use to calculate plugins.
}

Expand All @@ -122,7 +122,7 @@ message RunRequest {
string project = 1; // the project name.
string stack = 2; // the name of the stack being deployed into.
string pwd = 3; // the program's working directory.
string program = 4 [deprecated = true]; // the path to the program to execute.
string program = 4 [deprecated = true]; // the path to the program to execute. Deprecated, use info.entry_point instead.
repeated string args = 5; // any arguments to pass to the program.
map<string, string> config = 6; // the configuration variables to apply before running.
bool dryRun = 7; // true if we're only doing a dryrun (preview).
Expand Down Expand Up @@ -150,7 +150,7 @@ message RunResponse {
}

message InstallDependenciesRequest {
string directory = 1 [deprecated = true]; // the program's working directory.
string directory = 1 [deprecated = true]; // the program's working directory. Deprecated, use info.program_directory instead.
bool is_terminal = 2; // if we are running in a terminal and should use ANSI codes
ProgramInfo info = 3; // the program info to use to execute the plugin.
bool use_language_version_tools = 4; // if we should use language version tools like pyenv or
Expand Down Expand Up @@ -192,7 +192,7 @@ message RuntimeOptionsResponse {

message RunPluginRequest{
string pwd = 1; // the program's working directory.
string program = 2 [deprecated = true]; // the path to the program to execute.
string program = 2 [deprecated = true]; // the path to the program to execute. Deprecated, use info.entry_point instead.
repeated string args = 3; // any arguments to pass to the program.
repeated string env = 4; // any environment variables to set as part of the program.
ProgramInfo info = 5; // the program info to use to execute the plugin.
Expand Down
20 changes: 20 additions & 0 deletions proto/pulumi/provider.proto
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,14 @@ service ResourceProvider {
rpc CheckConfig(CheckRequest) returns (CheckResponse) {}
// DiffConfig checks the impact a hypothetical change to this provider's configuration will have on the provider.
rpc DiffConfig(DiffRequest) returns (DiffResponse) {}

// Configure configures the resource provider with "globals" that control its behavior.
//
// :::{warning}
// ConfigureRequest.args may include secrets. Because ConfigureRequest is sent before
// ConfigureResponse can specify acceptSecrets: false, providers *must* handle secrets from
// ConfigureRequest.args.
// :::
rpc Configure(ConfigureRequest) returns (ConfigureResponse) {}

// Invoke dynamically executes a built-in function in the provider.
Expand Down Expand Up @@ -249,6 +256,9 @@ message CheckRequest {
reserved "sequenceNumber";

bytes randomSeed = 5; // a deterministically random hash, primarily intended for global unique naming.

string name = 6; // the Pulumi name for this resource.
string type = 7; // the Pulumi type for this resource.
}

message CheckResponse {
Expand All @@ -271,6 +281,8 @@ message DiffRequest {

repeated string ignoreChanges = 5; // a set of property paths that should be treated as unchanged.
google.protobuf.Struct old_inputs = 6; // the old input values of the resource to diff.
string name = 7; // the Pulumi name for this resource.
string type = 8; // the Pulumi type for this resource.
}

message PropertyDiff {
Expand Down Expand Up @@ -342,6 +354,8 @@ message CreateRequest {
google.protobuf.Struct properties = 2; // the provider inputs to set during creation.
double timeout = 3; // the create request timeout represented in seconds.
bool preview = 4; // true if this is a preview and the provider should not actually create the resource.
string name = 5; // the Pulumi name for this resource.
string type = 6; // the Pulumi type for this resource.
}

message CreateResponse {
Expand All @@ -356,6 +370,8 @@ message ReadRequest {
string urn = 2; // the Pulumi URN for this resource.
google.protobuf.Struct properties = 3; // the current state (sufficiently complete to identify the resource).
google.protobuf.Struct inputs = 4; // the current inputs, if any (only populated during refresh).
string name = 5; // the Pulumi name for this resource.
string type = 6; // the Pulumi type for this resource.
}

message ReadResponse {
Expand All @@ -378,6 +394,8 @@ message UpdateRequest {
repeated string ignoreChanges = 6; // a set of property paths that should be treated as unchanged.
bool preview = 7; // true if this is a preview and the provider should not actually create the resource.
google.protobuf.Struct old_inputs = 8; // the old input values of the resource to diff.
string name = 9; // the Pulumi name for this resource.
string type = 10; // the Pulumi type for this resource.
}

message UpdateResponse {
Expand All @@ -390,6 +408,8 @@ message DeleteRequest {
google.protobuf.Struct properties = 3; // the current properties on the resource.
double timeout = 4; // the delete request timeout represented in seconds.
google.protobuf.Struct old_inputs = 5; // the old input values of the resource to delete.
string name = 6; // the Pulumi name for this resource.
string type = 7; // the Pulumi type for this resource.
}

message ConstructRequest {
Expand Down
5 changes: 5 additions & 0 deletions proto/pulumi/testing/language.proto
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ message PrepareLanguageTestsRequest {
string core_sdk_directory = 5;
string core_sdk_version = 6;
repeated Replacement snapshot_edits = 7;

// a JSON string that will be inserted into every schema loaded (for both GeneratePackage and GenerateProject) in
// the "Languages[language_plugin_name]" field. This can be used to test language specific options such as
// inputTypes in python.
string language_info = 8;
}

message PrepareLanguageTestsResponse {
Expand Down
Loading
Loading