[sdk/go] Respect default parent in go aliases. (#8288)

* Respect default parent in go aliases.

* Update changelog

* Handle empty parrents correctly

* Allow specifying no parent

* clarify variable name

* Improve `Unparent` ergonomics

* Take t0yv0's suggestion

* Adopt @t0yv0's tests and doc comments.

* Make NoParent,Parent,ParentURN mutually exclusive
This commit is contained in:
Ian Wahbe 2021-10-28 17:25:31 -07:00 committed by GitHub
parent 470893a980
commit e38876f7af
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 164 additions and 28 deletions

View file

@ -1,5 +1,15 @@
### Improvements
- [cli] Reformat error message string in `sdk/go/common/diag/errors.go`
[#8284](https://github.com/pulumi/pulumi/pull/8284)
### Bug Fixes
- [sdk/go] - Respect implicit parents in alias resolution
[#8288](https://github.com/pulumi/pulumi/pull/8288)
- [sdk/dotnet] - Fix a race condition when detecting exceptions in stack creation
[#8294](https://github.com/pulumi/pulumi/pull/8294)
- Clarify error message string in `sdk/go/common/diag/errors.go`
[#8284](https://github.com/pulumi/pulumi/pull/8284)
@ -11,8 +21,6 @@
[#8275](https://github.com/pulumi/pulumi/pull/8275)
### Bug Fixes
- [codegen/go] - Interaction between the `plain` and `default` tags of a type.
[#8254](https://github.com/pulumi/pulumi/pull/8254)

View file

@ -1,4 +1,4 @@
// Copyright 2016-2020, Pulumi Corporation.
// Copyright 2016-2021, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -16,6 +16,7 @@ package pulumi
import (
"errors"
"fmt"
"strings"
)
@ -29,16 +30,35 @@ type Alias struct {
Name StringInput
// The previous type of the resource. If not provided, the current type of the resource is used.
Type StringInput
// The previous parent of the resource. If not provided, the current parent of the resource is used.
// The previous parent of the resource. If not provided, the current parent of the resource is used by default.
// This option is mutually exclusive to `ParentURN` and `NoParent`.
// Use `Alias { NoParent: pulumi.Bool(true) }` to avoid defaulting to the current parent.
Parent Resource
// The previous parent of the resource in URN format, mutually exclusive to 'Parent'
// The previous parent of the resource in URN format, mutually exclusive to `Parent` and `ParentURN`.
// To specify no original parent, use `Alias { NoParent: pulumi.Bool(true) }`.
ParentURN URNInput
// When true, indicates that the resource previously had no parent.
// This option is mutually exclusive to `Parent` and `ParentURN`.
NoParent BoolInput
// The name of the previous stack of the resource. If not provided, defaults to `context.GetStack()
Stack StringInput
// The previous project of the resource. If not provided, defaults to `context.GetProject()`.
Project StringInput
}
// More then one bool is set to true.
func multipleTrue(booleans ...bool) bool {
var found bool
for _, b := range booleans {
if b && found {
return true
} else if b {
found = true
}
}
return false
}
func (a Alias) collapseToURN(defaultName, defaultType string, defaultParent Resource,
defaultProject, defaultStack string) (URNOutput, error) {
@ -55,16 +75,27 @@ func (a Alias) collapseToURN(defaultName, defaultType string, defaultParent Reso
t = String(defaultType)
}
var parent StringInput
if a.Parent != nil && a.ParentURN != nil {
return URNOutput{}, errors.New("alias can specify either Parent or ParentURN but not both")
var parent StringInput = String("")
if defaultParent != nil {
parent = defaultParent.URN().ToStringOutput()
}
if multipleTrue(a.Parent != nil, a.ParentURN != nil, a.NoParent != nil) {
return URNOutput{}, errors.New("alias can specify Parent, ParentURN or NoParent but not more then one")
}
if a.Parent != nil {
parent = a.Parent.URN()
parent = a.Parent.URN().ToStringOutput()
}
if a.ParentURN != nil {
parent = a.ParentURN.ToURNOutput()
}
if a.NoParent != nil {
parent = All(a.NoParent.ToBoolOutput(), parent).ApplyT(func(a []interface{}) string {
if a[0].(bool) {
return ""
}
return a[1].(string)
}).(StringOutput)
}
project := a.Project
if project == nil {
@ -80,20 +111,27 @@ func (a Alias) collapseToURN(defaultName, defaultType string, defaultParent Reso
// CreateURN computes a URN from the combination of a resource name, resource type, and optional parent,
func CreateURN(name, t, parent, project, stack StringInput) URNOutput {
var parentPrefix StringInput
if parent != nil {
parentPrefix = parent.ToStringOutput().ApplyT(func(p string) string {
return p[0:strings.LastIndex(p, "::")] + "$"
}).(StringOutput)
} else {
parentPrefix = All(stack, project).ApplyT(func(a []interface{}) string {
return "urn:pulumi:" + a[0].(string) + "::" + a[1].(string) + "::"
}).(StringOutput)
if parent == nil {
parent = String("")
}
return All(parentPrefix, t, name).ApplyT(func(a []interface{}) URN {
return URN(a[0].(string) + a[1].(string) + "::" + a[2].(string))
createURN := func(parent, stack, project, t, name string) URN {
var parentPrefix string
if parent == "" {
parentPrefix = "urn:pulumi:" + stack + "::" + project + "::"
} else {
ix := strings.LastIndex(parent, "::")
if ix == -1 {
panic(fmt.Sprintf("Expected 'parent' string '%s' to contain '::'", parent))
}
parentPrefix = parent[0:ix] + "$"
}
return URN(parentPrefix + t + "::" + name)
}
// The explicit call to `ToStringOutput` is necessary because `URNOutput`
// conforms to `StringInput` so `parent.(string)` can fail without the
// explicit conversion.
return All(parent.ToStringOutput(), stack, project, t, name).ApplyT(func(a []interface{}) URN {
return createURN(a[0].(string), a[1].(string), a[2].(string), a[3].(string), a[4].(string))
}).(URNOutput)
}
@ -108,5 +146,5 @@ func inheritedChildAlias(childName, parentName, childType, project, stack string
return string(parentPrefix) + childName[len(parentName):]
}).(StringOutput)
}
return CreateURN(aliasName, String(childType), parentURN, String(project), String(stack))
return CreateURN(aliasName, String(childType), parentURN.ToStringOutput(), String(project), String(stack))
}

View file

@ -0,0 +1,84 @@
// Copyright 2016-2021, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pulumi
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
)
var aliasTestCases = []struct {
name string
alias func(t *testing.T) Alias
expectedURN string
}{
{
"plain",
func(*testing.T) Alias {
return Alias{
Type: String("kubernetes:storage.k8s.io/v1beta1:CSIDriver"),
}
},
"AnUrn$kubernetes:storage.k8s.io/v1beta1:CSIDriver::defName",
},
{
"noParent",
func(*testing.T) Alias {
return Alias{
Type: String("kubernetes:storage.k8s.io/v1beta1:CSIDriver"),
NoParent: Bool(true),
}
}, "urn:pulumi:defStack::defProject::kubernetes:storage.k8s.io/v1beta1:CSIDriver::defName",
},
{
"parent",
func(t *testing.T) Alias {
return Alias{
Type: String("kubernetes:storage.k8s.io/v1beta1:CSIDriver"),
Parent: newResource(t, URN("AParent::AParent"), ID("theParent")),
}
}, "AParent$kubernetes:storage.k8s.io/v1beta1:CSIDriver::defName",
},
{
"parentURN",
func(*testing.T) Alias {
return Alias{
Type: String("kubernetes:storage.k8s.io/v1beta1:CSIDriver"),
ParentURN: URN("AParent::AParent"),
}
}, "AParent$kubernetes:storage.k8s.io/v1beta1:CSIDriver::defName",
},
}
func TestAliasResolution(t *testing.T) {
for _, tt := range aliasTestCases {
t.Run(tt.name, func(t *testing.T) {
parent := newResource(t, URN("AnUrn::ASegment"), ID("hello"))
out, err := tt.alias(t).collapseToURN("defName", "defType", parent, "defProject", "defStack")
assert.NoError(t, err)
urn, _, _, err := out.awaitURN(context.Background())
assert.NoError(t, err)
assert.Equal(t, URN(tt.expectedURN), urn)
})
}
}
func newResource(t *testing.T, urn URN, id ID) Resource {
ctx, err := NewContext(context.Background(), RunInfo{})
assert.NoError(t, err)
return newSimpleCustomResource(ctx, urn, id)
}

View file

@ -551,6 +551,7 @@ func (ctx *Context) ReadResource(
}
options := merge(opts...)
aliasParent := options.Parent
if options.Parent == nil {
options.Parent = ctx.stack
}
@ -563,7 +564,7 @@ func (ctx *Context) ReadResource(
}
// Collapse aliases to URNs.
aliasURNs, err := ctx.collapseAliases(options.Aliases, t, name, options.Parent)
aliasURNs, err := ctx.collapseAliases(options.Aliases, t, name, aliasParent)
if err != nil {
return err
}
@ -736,6 +737,7 @@ func (ctx *Context) registerResource(
}
options := merge(opts...)
parent := options.Parent
if options.Parent == nil {
options.Parent = ctx.stack
}
@ -748,7 +750,7 @@ func (ctx *Context) registerResource(
}
// Collapse aliases to URNs.
aliasURNs, err := ctx.collapseAliases(options.Aliases, t, name, options.Parent)
aliasURNs, err := ctx.collapseAliases(options.Aliases, t, name, parent)
if err != nil {
return err
}

View file

@ -80,9 +80,13 @@ func NewFooComponent3(ctx *pulumi.Context,
return nil, err
}
alias := &pulumi.Alias{
Parent: childAliasParent,
var alias = &pulumi.Alias{}
if childAliasParent != nil {
alias.Parent = childAliasParent
} else {
alias.NoParent = pulumi.Bool(true)
}
aliasOpt := pulumi.Aliases([]pulumi.Alias{*alias})
parentOpt := pulumi.Parent(fooComp)
_, err = NewFooComponent2(ctx, name+"-child", aliasOpt, parentOpt)
@ -114,7 +118,7 @@ func main() {
return err
}
alias := &pulumi.Alias{
Parent: nil,
NoParent: pulumi.Bool(true),
}
aliasOpt := pulumi.Aliases([]pulumi.Alias{*alias})
parentOpt := pulumi.Parent(comp2)