pulumi/pkg/resource/deploy/providers/reference.go
Sean Gillespie 679f55c355
Validate type tokens before using them (#1904)
* Validate type tokens before using them

When registering or reading a resource, we take the type token given to
us from the language host and assume that it's valid, which resulted in
assertion failures in various places in the engine. This commit
validates the format of type tokens given to us from the language host
and issues an appropriate error if it's not valid.

Along the way, this commit also improves the way that fatal exceptions
are rendered in the Node language host.

* Pre-allocate an exception for ReadResource

* Fix integration test

* CR Feedback

This commit is a lower-impact change that fixes the bugs associated with
invalid types on component resources and only checks that a type is
valid on custom resources.

* CR Take 2: Fix up IsProviderType instead of fixing call sites

* Please gometalinter
2018-09-07 15:19:18 -07:00

114 lines
3.8 KiB
Go

// Copyright 2016-2018, 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 providers
import (
"strings"
"github.com/pkg/errors"
"github.com/pulumi/pulumi/pkg/resource"
"github.com/pulumi/pulumi/pkg/resource/plugin"
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/pulumi/pulumi/pkg/util/contract"
)
// A provider reference is (URN, ID) tuple that refers to a particular provider instance. A provider reference's
// string representation is <URN> "::" <ID>. The URN's type portion must be of the form "pulumi:providers:<pkg>".
// UnknownID is a distinguished token used to indicate that a provider's ID is not known (e.g. because we are
// performing a preview).
const UnknownID = plugin.UnknownStringValue
// IsProviderType returns true if the supplied type token refers to a Pulumi provider.
func IsProviderType(typ tokens.Type) bool {
// Tokens without a module member are definitely not provider types.
if !tokens.Token(typ).HasModuleMember() {
return false
}
return typ.Module() == "pulumi:providers" && typ.Name() != ""
}
// MakeProviderType returns the provider type token for the given package.
func MakeProviderType(pkg tokens.Package) tokens.Type {
return tokens.Type("pulumi:providers:" + pkg)
}
func getProviderPackage(typ tokens.Type) tokens.Package {
contract.Require(IsProviderType(typ), "typ")
return tokens.Package(typ.Name())
}
func validateURN(urn resource.URN) error {
typ := urn.Type()
if typ.Module() != "pulumi:providers" {
return errors.Errorf("invalid module in type: expected 'pulumi:providers', got '%v'", typ.Module())
}
if typ.Name() == "" {
return errors.New("provider URNs must specify a type name")
}
return nil
}
// Reference represents a reference to a particular provider.
type Reference struct {
urn resource.URN
id resource.ID
}
// URN returns the provider reference's URN.
func (r Reference) URN() resource.URN {
return r.urn
}
// ID returns the provider reference's ID.
func (r Reference) ID() resource.ID {
return r.id
}
// String returns the string representation of this provider reference.
func (r Reference) String() string {
return string(r.urn) + resource.URNNameDelimiter + string(r.id)
}
// NewReference creates a new reference for the given URN and ID.
func NewReference(urn resource.URN, id resource.ID) (Reference, error) {
if err := validateURN(urn); err != nil {
return Reference{}, err
}
return Reference{urn: urn, id: id}, nil
}
func mustNewReference(urn resource.URN, id resource.ID) Reference {
ref, err := NewReference(urn, id)
contract.Assert(err == nil)
return ref
}
// ParseReference parses the URN and ID from the string representation of a provider reference. If parsing was
// not possible, this function returns false.
func ParseReference(s string) (Reference, error) {
// If this is not a valid URN + ID, return false. Note that we don't try terribly hard to validate the URN portion
// of the reference.
lastSep := strings.LastIndex(s, resource.URNNameDelimiter)
if lastSep == -1 {
return Reference{}, errors.Errorf("expected '%v' in provider reference '%v'", resource.URNNameDelimiter, s)
}
urn, id := resource.URN(s[:lastSep]), resource.ID(s[lastSep+len(resource.URNNameDelimiter):])
if err := validateURN(urn); err != nil {
return Reference{}, err
}
return Reference{urn: urn, id: id}, nil
}