This commit touches an intersection of a few different provider-oriented features that combined to cause a particularly severe bug that made it impossible for users to upgrade provider versions without seeing replacements with their resources. For some context, Pulumi models all providers as resources and places them in the snapshot like any other resource. Every resource has a reference to the provider that created it. If a Pulumi program does not specify a particular provider to use when performing a resource operation, the Pulumi engine injects one automatically; these are called "default providers" and are the most common ways that users end up with providers in their snapshot. Default providers can be identified by their name, which is always prefixed with "default". Recently, in an effort to make the Pulumi engine more flexible with provider versions, it was made possible for the engine to have multiple default providers active for a provider of a particular type, which was previously not possible. Because a provider is identified as a tuple of package name and version, it was difficult to find a name for these duplicate default providers that did not cause additional problems. The provider versioning PR gave these default providers a name that was derived from the version of the package. This proved to be a problem, because when users upgraded from one version of a package to another, this changed the name of their default provider which in turn caused all of their resources created using that provider (read: everything) to be replaced. To combat this, this PR introduces a rule that the engine will apply when diffing a resource to determine whether or not it needs to be replaced: "If a resource's provider changes, and both old and new providers are default providers whose properties do not require replacement, proceed as if there were no diff." This allows the engine to gracefully recognize and recover when a resource's default provider changes names, as long as the provider's config has not changed.
120 lines
4.1 KiB
Go
120 lines
4.1 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() != ""
|
|
}
|
|
|
|
// IsDefaultProvider returns true if this URN refers to a default Pulumi provider.
|
|
func IsDefaultProvider(urn resource.URN) bool {
|
|
return IsProviderType(urn.Type()) && strings.HasPrefix(urn.Name().String(), "default")
|
|
}
|
|
|
|
// MakeProviderType returns the provider type token for the given package.
|
|
func MakeProviderType(pkg tokens.Package) tokens.Type {
|
|
return tokens.Type("pulumi:providers:" + pkg)
|
|
}
|
|
|
|
// GetProviderPackage returns the provider package for the given type token.
|
|
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
|
|
}
|