From 12e1b6c212df77c9071d0af73241ac6b0be14a4a Mon Sep 17 00:00:00 2001 From: Ian Wahbe Date: Fri, 15 Apr 2022 12:51:43 +0200 Subject: [PATCH 01/10] Return a concrete type when missing a key --- sdk/go/pulumi/templates/config-try.go.template | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/sdk/go/pulumi/templates/config-try.go.template b/sdk/go/pulumi/templates/config-try.go.template index 8e4e71a00fb2..dcf743511ad7 100644 --- a/sdk/go/pulumi/templates/config-try.go.template +++ b/sdk/go/pulumi/templates/config-try.go.template @@ -1,4 +1,4 @@ -// Copyright 2016-2018, Pulumi Corporation. +// Copyright 2016-2022, Pulumi Corporation. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -25,11 +25,18 @@ import ( "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) +type MissingVariable struct { + key string +} + +func (m MissingVariable) Error() string() { + return fmt.Errorf("missing required configuration variable '%s'; run `pulumi config` to set", m.key) +} + func try(ctx *pulumi.Context, key, use, insteadOf string) (string, error) { v, ok := get(ctx, key, use, insteadOf) if !ok { - return "", - fmt.Errorf("missing required configuration variable '%s'; run `pulumi config` to set", key) + return "", MissingVariable{key} } return v, nil } From 3d1269ab11220ac2791ea1872bfe53f78172fada Mon Sep 17 00:00:00 2001 From: Ian Wahbe Date: Fri, 15 Apr 2022 13:16:55 +0200 Subject: [PATCH 02/10] Add tests + fix implementation --- sdk/go/pulumi/config/config_test.go | 12 +++++++--- sdk/go/pulumi/config/try.go | 23 ++++++++++++++++--- .../pulumi/templates/config-try.go.template | 20 ++++++++++++---- 3 files changed, 44 insertions(+), 11 deletions(-) diff --git a/sdk/go/pulumi/config/config_test.go b/sdk/go/pulumi/config/config_test.go index b1e3509d10f6..7b64f470ea5b 100644 --- a/sdk/go/pulumi/config/config_test.go +++ b/sdk/go/pulumi/config/config_test.go @@ -16,6 +16,7 @@ package config import ( "context" + "errors" "fmt" "reflect" "testing" @@ -142,16 +143,21 @@ func TestConfig(t *testing.T) { testStruct = TestStruct{} // missing TryObject err = cfg.TryObject("missing", &testStruct) - assert.NotNil(t, err) + assert.Error(t, err) assert.Equal(t, emptyTestStruct, testStruct) + assert.True(t, errors.Is(err, ErrMissingVar)) testStruct = TestStruct{} // malformed TryObject err = cfg.TryObject("malobj", &testStruct) - assert.NotNil(t, err) + assert.Error(t, err) assert.Equal(t, emptyTestStruct, testStruct) + assert.False(t, errors.Is(err, ErrMissingVar)) testStruct = TestStruct{} _, err = cfg.Try("missing") - assert.NotNil(t, err) + assert.Error(t, err) + assert.Equal(t, err.Error(), + "missing required configuration variable 'testpkg:missing'; run `pulumi config` to set") + assert.True(t, errors.Is(err, ErrMissingVar)) } func TestSecretConfig(t *testing.T) { diff --git a/sdk/go/pulumi/config/try.go b/sdk/go/pulumi/config/try.go index 32ddca89a230..307a611df4fb 100644 --- a/sdk/go/pulumi/config/try.go +++ b/sdk/go/pulumi/config/try.go @@ -1,4 +1,4 @@ -// Copyright 2016-2018, Pulumi Corporation. +// Copyright 2016-2022, Pulumi Corporation. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -25,11 +25,28 @@ import ( "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) +var ErrMissingVar = missingVariable{} + +type missingVariable struct { + key string +} + +func (m missingVariable) Error() string { + if m.key == "" { + return "missing required configuration variable" + } + return fmt.Sprintf("missing required configuration variable '%s'; run `pulumi config` to set", m.key) +} + +func (m missingVariable) Is(target error) bool { + _, ok := target.(missingVariable) + return ok +} + func try(ctx *pulumi.Context, key, use, insteadOf string) (string, error) { v, ok := get(ctx, key, use, insteadOf) if !ok { - return "", - fmt.Errorf("missing required configuration variable '%s'; run `pulumi config` to set", key) + return "", missingVariable{key} } return v, nil } diff --git a/sdk/go/pulumi/templates/config-try.go.template b/sdk/go/pulumi/templates/config-try.go.template index dcf743511ad7..91e758043a42 100644 --- a/sdk/go/pulumi/templates/config-try.go.template +++ b/sdk/go/pulumi/templates/config-try.go.template @@ -25,18 +25,28 @@ import ( "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) -type MissingVariable struct { - key string +var ErrMissingVar = missingVariable{} + +type missingVariable struct { + key string +} + +func (m missingVariable) Error() string { + if m.key == "" { + return "missing required configuration variable" + } + return fmt.Sprintf("missing required configuration variable '%s'; run `pulumi config` to set", m.key) } -func (m MissingVariable) Error() string() { - return fmt.Errorf("missing required configuration variable '%s'; run `pulumi config` to set", m.key) +func (m missingVariable) Is(target error) bool { + _, ok := target.(missingVariable) + return ok } func try(ctx *pulumi.Context, key, use, insteadOf string) (string, error) { v, ok := get(ctx, key, use, insteadOf) if !ok { - return "", MissingVariable{key} + return "", missingVariable{key} } return v, nil } From 908186e73dfc4650bb6fb4aad6bf073132c89ff0 Mon Sep 17 00:00:00 2001 From: Ian Wahbe Date: Fri, 15 Apr 2022 14:13:32 +0200 Subject: [PATCH 03/10] Return an error for when parsing fails --- sdk/go/pulumi/config/config_test.go | 6 +++++- sdk/go/pulumi/config/try.go | 15 +++++++++------ sdk/go/pulumi/templates/config-try.go.template | 5 +++-- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/sdk/go/pulumi/config/config_test.go b/sdk/go/pulumi/config/config_test.go index 7b64f470ea5b..8b1369ede922 100644 --- a/sdk/go/pulumi/config/config_test.go +++ b/sdk/go/pulumi/config/config_test.go @@ -40,6 +40,7 @@ func TestConfig(t *testing.T) { "testpkg:sss": "a string value", "testpkg:bbb": "true", "testpkg:intint": "42", + "testpkg:badint": "4d2", "testpkg:fpfpfp": "99.963", "testpkg:obj": ` { @@ -123,7 +124,7 @@ func TestConfig(t *testing.T) { _ = cfg.Require("missing") }() - // Test Try, which returns an error for missing entries. + // Test Try, which returns an error for missing or invalid entries. k1, err := cfg.Try("sss") assert.Nil(t, err) assert.Equal(t, "a string value", k1) @@ -133,6 +134,9 @@ func TestConfig(t *testing.T) { k3, err := cfg.TryInt("intint") assert.Nil(t, err) assert.Equal(t, 42, k3) + invalidInt, err := cfg.TryInt("badint") + assert.Error(t, err) + assert.Zero(t, invalidInt) k4, err := cfg.TryFloat64("fpfpfp") assert.Nil(t, err) assert.Equal(t, 99.963, k4) diff --git a/sdk/go/pulumi/config/try.go b/sdk/go/pulumi/config/try.go index 307a611df4fb..0933d084fe64 100644 --- a/sdk/go/pulumi/config/try.go +++ b/sdk/go/pulumi/config/try.go @@ -75,10 +75,11 @@ func tryBool(ctx *pulumi.Context, key, use, insteadOf string) (bool, error) { if err != nil { return false, err } - return cast.ToBool(v), nil + return cast.ToBoolE(v) } -// TryBool loads an optional configuration value by its key, as a bool, or returns an error if it doesn't exist. +// TryBool loads an optional configuration value by its key, as a bool, +// or returns an error if it doesn't exist or can't be parsed. func TryBool(ctx *pulumi.Context, key string) (bool, error) { return tryBool(ctx, key, "TrySecretBool", "TryBool") } @@ -88,10 +89,11 @@ func tryFloat64(ctx *pulumi.Context, key, use, insteadOf string) (float64, error if err != nil { return 0, err } - return cast.ToFloat64(v), nil + return cast.ToFloat64E(v) } -// TryFloat64 loads an optional configuration value by its key, as a float64, or returns an error if it doesn't exist. +// TryFloat64 loads an optional configuration value by its key, as a float64, +// or returns an error if it doesn't exist or can't be parsed. func TryFloat64(ctx *pulumi.Context, key string) (float64, error) { return tryFloat64(ctx, key, "TrySecretFloat64", "TryFloat64") } @@ -101,10 +103,11 @@ func tryInt(ctx *pulumi.Context, key, use, insteadOf string) (int, error) { if err != nil { return 0, err } - return cast.ToInt(v), nil + return cast.ToIntE(v) } -// TryInt loads an optional configuration value by its key, as a int, or returns an error if it doesn't exist. +// TryInt loads an optional configuration value by its key, as a int, +// or returns an error if it doesn't exist or can't be parsed. func TryInt(ctx *pulumi.Context, key string) (int, error) { return tryInt(ctx, key, "TrySecretInt", "TryInt") } diff --git a/sdk/go/pulumi/templates/config-try.go.template b/sdk/go/pulumi/templates/config-try.go.template index 91e758043a42..9910e3ea2c4b 100644 --- a/sdk/go/pulumi/templates/config-try.go.template +++ b/sdk/go/pulumi/templates/config-try.go.template @@ -77,10 +77,11 @@ func try{{.Name}}(ctx *pulumi.Context, key, use, insteadOf string) ({{.Type}}, e if err != nil { return {{.DefaultConfig}}, err } - return cast.To{{.Name}}(v), nil + return cast.To{{.Name}}E(v) } -// Try{{.Name}} loads an optional configuration value by its key, as a {{.Type}}, or returns an error if it doesn't exist. +// Try{{.Name}} loads an optional configuration value by its key, as a {{.Type}}, +// or returns an error if it doesn't exist or can't be parsed. func Try{{.Name}}(ctx *pulumi.Context, key string) ({{.Type}}, error) { return try{{.Name}}(ctx, key, "TrySecret{{.Name}}", "Try{{.Name}}") } From 8dfebdf85783b0053bf85acaaae815b75882283f Mon Sep 17 00:00:00 2001 From: Ian Wahbe Date: Fri, 15 Apr 2022 17:32:20 +0200 Subject: [PATCH 04/10] Implement the same fix for errors --- sdk/go/pulumi/config/config_test.go | 4 ++++ sdk/go/pulumi/config/require.go | 20 +++++++++++++++---- .../templates/config-require.go.template | 8 ++++++-- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/sdk/go/pulumi/config/config_test.go b/sdk/go/pulumi/config/config_test.go index 8b1369ede922..b5a5037e2da7 100644 --- a/sdk/go/pulumi/config/config_test.go +++ b/sdk/go/pulumi/config/config_test.go @@ -98,6 +98,10 @@ func TestConfig(t *testing.T) { assert.Equal(t, "a string value", cfg.Require("sss")) assert.Equal(t, true, cfg.RequireBool("bbb")) assert.Equal(t, 42, cfg.RequireInt("intint")) + assert.PanicsWithValue(t, + "fatal: A failure has occurred: unable to parse required configuration variable"+ + " 'testpkg:badint'; unable to cast \"4d2\" of type string to int", + func() { cfg.RequireInt("badint") }) assert.Equal(t, 99.963, cfg.RequireFloat64("fpfpfp")) cfg.RequireObject("obj", &testStruct) assert.Equal(t, expectedTestStruct, testStruct) diff --git a/sdk/go/pulumi/config/require.go b/sdk/go/pulumi/config/require.go index 093f6aded5fd..4a451ac2894a 100644 --- a/sdk/go/pulumi/config/require.go +++ b/sdk/go/pulumi/config/require.go @@ -1,4 +1,4 @@ -// Copyright 2016-2018, Pulumi Corporation. +// Copyright 2016-2022, Pulumi Corporation. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -53,7 +53,11 @@ func RequireObject(ctx *pulumi.Context, key string, output interface{}) { func requireBool(ctx *pulumi.Context, key, use, insteadOf string) bool { v := require(ctx, key, use, insteadOf) - return cast.ToBool(v) + o, err := cast.ToBoolE(v) + if err != nil { + contract.Failf("unable to parse required configuration variable '%s'; %s", key, err.Error()) + } + return o } // RequireBool loads an optional configuration value by its key, as a bool, or panics if it doesn't exist. @@ -63,7 +67,11 @@ func RequireBool(ctx *pulumi.Context, key string) bool { func requireFloat64(ctx *pulumi.Context, key, use, insteadOf string) float64 { v := require(ctx, key, use, insteadOf) - return cast.ToFloat64(v) + o, err := cast.ToFloat64E(v) + if err != nil { + contract.Failf("unable to parse required configuration variable '%s'; %s", key, err.Error()) + } + return o } // RequireFloat64 loads an optional configuration value by its key, as a float64, or panics if it doesn't exist. @@ -73,7 +81,11 @@ func RequireFloat64(ctx *pulumi.Context, key string) float64 { func requireInt(ctx *pulumi.Context, key, use, insteadOf string) int { v := require(ctx, key, use, insteadOf) - return cast.ToInt(v) + o, err := cast.ToIntE(v) + if err != nil { + contract.Failf("unable to parse required configuration variable '%s'; %s", key, err.Error()) + } + return o } // RequireInt loads an optional configuration value by its key, as a int, or panics if it doesn't exist. diff --git a/sdk/go/pulumi/templates/config-require.go.template b/sdk/go/pulumi/templates/config-require.go.template index fa06e0bf3da4..c226536bc494 100644 --- a/sdk/go/pulumi/templates/config-require.go.template +++ b/sdk/go/pulumi/templates/config-require.go.template @@ -1,4 +1,4 @@ -// Copyright 2016-2018, Pulumi Corporation. +// Copyright 2016-2022, Pulumi Corporation. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -55,7 +55,11 @@ func RequireObject(ctx *pulumi.Context, key string, output interface{}) { {{if .GenerateConfig}} func require{{.Name}}(ctx *pulumi.Context, key, use, insteadOf string) {{.Type}} { v := require(ctx, key, use, insteadOf) - return cast.To{{.Name}}(v) + o, err := cast.To{{.Name}}E(v) + if err != nil { + contract.Failf("unable to parse required configuration variable '%s'; %s", key, err.Error()) + } + return o } // Require{{.Name}} loads an optional configuration value by its key, as a {{.Type}}, or panics if it doesn't exist. From 0924f6f14fb60afd4cefba1be75f96d8132b6faa Mon Sep 17 00:00:00 2001 From: Ian Wahbe Date: Fri, 15 Apr 2022 17:33:24 +0200 Subject: [PATCH 05/10] Update changelog --- CHANGELOG_PENDING.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 86298018e3ae..06b647d26a5d 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -25,3 +25,6 @@ - [cli] - StackReferences will now correctly use the service bulk decryption end point. [#9373](https://github.com/pulumi/pulumi/pull/9373) + +- [sdk/go] - Correctly handle present but invalid config values. + [#9407](https://github.com/pulumi/pulumi/pull/9407) From 5f329b0e479e9355a82de6ea88f9d79a13e24880 Mon Sep 17 00:00:00 2001 From: Ian Wahbe Date: Mon, 18 Apr 2022 11:50:48 +0200 Subject: [PATCH 06/10] Update CHANGELOG_PENDING.md --- CHANGELOG_PENDING.md | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 16799b3a910e..5a223e1ff36c 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -5,16 +5,9 @@ ### Bug Fixes -<<<<<<< HEAD -- [codegen/node] - Fix an issue with escaping deprecation messages. - [#9371](https://github.com/pulumi/pulumi/pull/9371) - -- [cli] - StackReferences will now correctly use the service bulk decryption end point. - [#9373](https://github.com/pulumi/pulumi/pull/9373) - -- [sdk/go] - Correctly handle present but invalid config values. - [#9407](https://github.com/pulumi/pulumi/pull/9407) -======= - [cli/plugin] - Dynamic provider binaries will now be found even if pulumi/bin is not on $PATH. [#9396](https://github.com/pulumi/pulumi/pull/9396) ->>>>>>> master + +- [sdk/go] - Fail appropriatly for `config.Try*` and `config.Require*` where the + key is present but of the wrong type. + [#9407](https://github.com/pulumi/pulumi/pull/9407) From 6de18488988975eac684199a1b13eabcd6088a1b Mon Sep 17 00:00:00 2001 From: Ian Wahbe Date: Mon, 18 Apr 2022 12:09:23 +0200 Subject: [PATCH 07/10] Remove contract.Failf from require --- sdk/go/pulumi/config/config_test.go | 2 +- sdk/go/pulumi/config/require.go | 16 ++++++++++------ .../pulumi/templates/config-require.go.template | 12 ++++++++---- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/sdk/go/pulumi/config/config_test.go b/sdk/go/pulumi/config/config_test.go index b5a5037e2da7..6ecab75948b5 100644 --- a/sdk/go/pulumi/config/config_test.go +++ b/sdk/go/pulumi/config/config_test.go @@ -99,7 +99,7 @@ func TestConfig(t *testing.T) { assert.Equal(t, true, cfg.RequireBool("bbb")) assert.Equal(t, 42, cfg.RequireInt("intint")) assert.PanicsWithValue(t, - "fatal: A failure has occurred: unable to parse required configuration variable"+ + "unable to parse required configuration variable"+ " 'testpkg:badint'; unable to cast \"4d2\" of type string to int", func() { cfg.RequireInt("badint") }) assert.Equal(t, 99.963, cfg.RequireFloat64("fpfpfp")) diff --git a/sdk/go/pulumi/config/require.go b/sdk/go/pulumi/config/require.go index 4a451ac2894a..3d81f40e3cde 100644 --- a/sdk/go/pulumi/config/require.go +++ b/sdk/go/pulumi/config/require.go @@ -18,17 +18,21 @@ package config import ( "encoding/json" + "fmt" "github.com/spf13/cast" - "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) +func failf(format string, a ...interface{}) { + panic(fmt.Sprintf(format, a...)) +} + func require(ctx *pulumi.Context, key, use, insteadOf string) string { v, ok := get(ctx, key, use, insteadOf) if !ok { - contract.Failf("missing required configuration variable '%s'; run `pulumi config` to set", key) + failf("missing required configuration variable '%s'; run `pulumi config` to set", key) } return v } @@ -41,7 +45,7 @@ func Require(ctx *pulumi.Context, key string) string { func requireObject(ctx *pulumi.Context, key string, output interface{}, use, insteadOf string) { v := require(ctx, key, use, insteadOf) if err := json.Unmarshal([]byte(v), output); err != nil { - contract.Failf("unable to unmarshall required configuration variable '%s'; %s", key, err.Error()) + failf("unable to unmarshall required configuration variable '%s'; %s", key, err.Error()) } } @@ -55,7 +59,7 @@ func requireBool(ctx *pulumi.Context, key, use, insteadOf string) bool { v := require(ctx, key, use, insteadOf) o, err := cast.ToBoolE(v) if err != nil { - contract.Failf("unable to parse required configuration variable '%s'; %s", key, err.Error()) + failf("unable to parse required configuration variable '%s'; %s", key, err.Error()) } return o } @@ -69,7 +73,7 @@ func requireFloat64(ctx *pulumi.Context, key, use, insteadOf string) float64 { v := require(ctx, key, use, insteadOf) o, err := cast.ToFloat64E(v) if err != nil { - contract.Failf("unable to parse required configuration variable '%s'; %s", key, err.Error()) + failf("unable to parse required configuration variable '%s'; %s", key, err.Error()) } return o } @@ -83,7 +87,7 @@ func requireInt(ctx *pulumi.Context, key, use, insteadOf string) int { v := require(ctx, key, use, insteadOf) o, err := cast.ToIntE(v) if err != nil { - contract.Failf("unable to parse required configuration variable '%s'; %s", key, err.Error()) + failf("unable to parse required configuration variable '%s'; %s", key, err.Error()) } return o } diff --git a/sdk/go/pulumi/templates/config-require.go.template b/sdk/go/pulumi/templates/config-require.go.template index c226536bc494..edd0997cdce8 100644 --- a/sdk/go/pulumi/templates/config-require.go.template +++ b/sdk/go/pulumi/templates/config-require.go.template @@ -18,17 +18,21 @@ package config import ( "encoding/json" + "fmt" "github.com/spf13/cast" - "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) +func failf(format string, a ...interface{}) { + panic(fmt.Sprintf(format, a...)) +} + func require(ctx *pulumi.Context, key, use, insteadOf string) string { v, ok := get(ctx, key, use, insteadOf) if !ok { - contract.Failf("missing required configuration variable '%s'; run `pulumi config` to set", key) + failf("missing required configuration variable '%s'; run `pulumi config` to set", key) } return v } @@ -41,7 +45,7 @@ func Require(ctx *pulumi.Context, key string) string { func requireObject(ctx *pulumi.Context, key string, output interface{}, use, insteadOf string) { v := require(ctx, key, use, insteadOf) if err := json.Unmarshal([]byte(v), output); err != nil { - contract.Failf("unable to unmarshall required configuration variable '%s'; %s", key, err.Error()) + failf("unable to unmarshall required configuration variable '%s'; %s", key, err.Error()) } } @@ -57,7 +61,7 @@ func require{{.Name}}(ctx *pulumi.Context, key, use, insteadOf string) {{.Type}} v := require(ctx, key, use, insteadOf) o, err := cast.To{{.Name}}E(v) if err != nil { - contract.Failf("unable to parse required configuration variable '%s'; %s", key, err.Error()) + failf("unable to parse required configuration variable '%s'; %s", key, err.Error()) } return o } From 1c768548c6fda7526952dba8f075346601b3d144 Mon Sep 17 00:00:00 2001 From: Ian Wahbe Date: Tue, 19 Apr 2022 11:15:54 +0200 Subject: [PATCH 08/10] Fix nit + lower conversion panic --- pkg/codegen/pcl/rewrite_convert.go | 71 +++++++++++-------- sdk/go/pulumi/config/require.go | 2 +- .../templates/config-require.go.template | 2 +- 3 files changed, 42 insertions(+), 33 deletions(-) diff --git a/pkg/codegen/pcl/rewrite_convert.go b/pkg/codegen/pcl/rewrite_convert.go index bb1279a28eb5..8d79c3feafe1 100644 --- a/pkg/codegen/pcl/rewrite_convert.go +++ b/pkg/codegen/pcl/rewrite_convert.go @@ -1,7 +1,6 @@ package pcl import ( - "fmt" "strings" "github.com/hashicorp/hcl/v2" @@ -288,10 +287,45 @@ func convertLiteralToString(from model.Expression) (string, bool) { return "", false } +// lowerConversion performs the main logic of LowerConversion. nil, false is +// returned if there is no conversion (safe or unsafe) between `from` and `to`. +// This can occur when a loosely typed program is converted, or if an other +// rewrite violated the type system. +func lowerConversion(from model.Expression, to model.Type) (model.Type, bool) { + switch to := to.(type) { + case *model.UnionType: + // Assignment: it just works + for _, to := range to.ElementTypes { + if to.AssignableFrom(from.Type()) { + return to, true + } + } + conversions := make([]model.ConversionKind, len(to.ElementTypes)) + for i, to := range to.ElementTypes { + conversions[i] = to.ConversionFrom(from.Type()) + if conversions[i] == model.SafeConversion { + // We found a safe conversion, and we will use it. We don't need + // to search for more conversions. + return to, true + } + } + + // Unsafe conversions: + for i, to := range to.ElementTypes { + if conversions[i] == model.UnsafeConversion { + return to, true + } + } + return nil, false + default: + return to, true + } +} + // LowerConversion lowers a conversion for a specific value, such that // converting `from` to a value of the returned type will produce valid code. -// The algorithm prioritizes safe conversions over unsafe conversions, and -// panics if a conversion could not be found. +// The algorithm prioritizes safe conversions over unsafe conversions. If no +// conversion can be found, nil, false is returned. // // This is useful because it cuts out conversion steps which the caller doesn't // need to worry about. For example: @@ -319,33 +353,8 @@ func convertLiteralToString(from model.Expression) (string, bool) { // since var(string) can be safely assigned to string, but unsafely assigned to // enum(string: "foo", "bar"). func LowerConversion(from model.Expression, to model.Type) model.Type { - switch to := to.(type) { - case *model.UnionType: - // Assignment: it just works - for _, to := range to.ElementTypes { - if to.AssignableFrom(from.Type()) { - return to - } - } - conversions := make([]model.ConversionKind, len(to.ElementTypes)) - for i, to := range to.ElementTypes { - conversions[i] = to.ConversionFrom(from.Type()) - if conversions[i] == model.SafeConversion { - // We found a safe conversion, and we will use it. We don't need - // to search for more conversions. - return to - } - } - - // Unsafe conversions: - for i, to := range to.ElementTypes { - if conversions[i] == model.UnsafeConversion { - return to - } - } - panic(fmt.Sprintf("Could not find a conversion from %s (%s) to type %s", - strings.TrimSpace(fmt.Sprintf("%s", from)), from.Type(), to)) - default: - return to + if t, ok := lowerConversion(from, to); ok { + return t } + return to } diff --git a/sdk/go/pulumi/config/require.go b/sdk/go/pulumi/config/require.go index 3d81f40e3cde..8c6ab2bd009d 100644 --- a/sdk/go/pulumi/config/require.go +++ b/sdk/go/pulumi/config/require.go @@ -26,7 +26,7 @@ import ( ) func failf(format string, a ...interface{}) { - panic(fmt.Sprintf(format, a...)) + panic(fmt.Errorf(format, a...)) } func require(ctx *pulumi.Context, key, use, insteadOf string) string { diff --git a/sdk/go/pulumi/templates/config-require.go.template b/sdk/go/pulumi/templates/config-require.go.template index edd0997cdce8..71dcce5cd38e 100644 --- a/sdk/go/pulumi/templates/config-require.go.template +++ b/sdk/go/pulumi/templates/config-require.go.template @@ -26,7 +26,7 @@ import ( ) func failf(format string, a ...interface{}) { - panic(fmt.Sprintf(format, a...)) + panic(fmt.Errorf(format, a...)) } func require(ctx *pulumi.Context, key, use, insteadOf string) string { From c15e62af0f85d3e0f73e812832b369c566578a83 Mon Sep 17 00:00:00 2001 From: Ian Wahbe Date: Tue, 19 Apr 2022 12:06:54 +0200 Subject: [PATCH 09/10] Fix test --- sdk/go/pulumi/config/config_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/go/pulumi/config/config_test.go b/sdk/go/pulumi/config/config_test.go index 6ecab75948b5..30136d525bc7 100644 --- a/sdk/go/pulumi/config/config_test.go +++ b/sdk/go/pulumi/config/config_test.go @@ -98,7 +98,7 @@ func TestConfig(t *testing.T) { assert.Equal(t, "a string value", cfg.Require("sss")) assert.Equal(t, true, cfg.RequireBool("bbb")) assert.Equal(t, 42, cfg.RequireInt("intint")) - assert.PanicsWithValue(t, + assert.PanicsWithError(t, "unable to parse required configuration variable"+ " 'testpkg:badint'; unable to cast \"4d2\" of type string to int", func() { cfg.RequireInt("badint") }) From 59a4786a5f98f853460878aee963ab33b4a700b7 Mon Sep 17 00:00:00 2001 From: Ian Wahbe Date: Tue, 19 Apr 2022 13:35:32 +0200 Subject: [PATCH 10/10] Link correctly --- pkg/codegen/nodejs/test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/codegen/nodejs/test.go b/pkg/codegen/nodejs/test.go index 154566662577..c349d7c1aced 100644 --- a/pkg/codegen/nodejs/test.go +++ b/pkg/codegen/nodejs/test.go @@ -54,7 +54,7 @@ func Check(t *testing.T, path string, dependencies codegen.StringSet, linkLocal err = os.WriteFile(filepath.Join(dir, "tsconfig.json"), tsConfigJSON, 0600) require.NoError(t, err) - typeCheckGeneratedPackage(t, dir, true) + typeCheckGeneratedPackage(t, dir, linkLocal) } func typeCheckGeneratedPackage(t *testing.T, pwd string, linkLocal bool) { @@ -63,7 +63,9 @@ func typeCheckGeneratedPackage(t *testing.T, pwd string, linkLocal bool) { // other places at the moment, and yarn does not run into the // ${VERSION} problem; use yarn for now. - test.RunCommand(t, "yarn_link", pwd, "yarn", "link", "@pulumi/pulumi") + if linkLocal { + test.RunCommand(t, "yarn_link", pwd, "yarn", "link", "@pulumi/pulumi") + } test.RunCommand(t, "yarn_install", pwd, "yarn", "install") tscOptions := &integration.ProgramTestOptions{ // Avoid Out of Memory error on CI: