github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/resource/stack/deployment.go (about)

     1  // Copyright 2016-2022, Pulumi Corporation.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package stack
    16  
    17  import (
    18  	"context"
    19  	"encoding/json"
    20  	"errors"
    21  	"fmt"
    22  	"io"
    23  	"io/ioutil"
    24  	"reflect"
    25  	"strings"
    26  
    27  	"github.com/pulumi/pulumi/pkg/v3/resource/deploy"
    28  	"github.com/pulumi/pulumi/pkg/v3/secrets"
    29  	"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
    30  	"github.com/pulumi/pulumi/sdk/v3/go/common/apitype/migrate"
    31  	"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
    32  	"github.com/pulumi/pulumi/sdk/v3/go/common/resource/config"
    33  	"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
    34  	"github.com/santhosh-tekuri/jsonschema/v5"
    35  )
    36  
    37  const (
    38  	// DeploymentSchemaVersionOldestSupported is the oldest deployment schema that we
    39  	// still support, i.e. we can produce a `deploy.Snapshot` from. This will generally
    40  	// need to be at least one less than the current schema version so that old deployments can
    41  	// be migrated to the current schema.
    42  	DeploymentSchemaVersionOldestSupported = 1
    43  
    44  	// computedValue is a magic number we emit for a value of a resource.Property value
    45  	// whenever we need to serialize a resource.Computed. (Since the real/actual value
    46  	// is not known.) This allows us to persist engine events and resource states that
    47  	// indicate a value will changed... but is unknown what it will change to.
    48  	computedValuePlaceholder = "04da6b54-80e4-46f7-96ec-b56ff0331ba9"
    49  )
    50  
    51  var (
    52  	// ErrDeploymentSchemaVersionTooOld is returned from `DeserializeDeployment` if the
    53  	// untyped deployment being deserialized is too old to understand.
    54  	ErrDeploymentSchemaVersionTooOld = fmt.Errorf("this stack's deployment is too old")
    55  
    56  	// ErrDeploymentSchemaVersionTooNew is returned from `DeserializeDeployment` if the
    57  	// untyped deployment being deserialized is too new to understand.
    58  	ErrDeploymentSchemaVersionTooNew = fmt.Errorf("this stack's deployment version is too new")
    59  )
    60  
    61  var deploymentSchema *jsonschema.Schema
    62  var resourceSchema *jsonschema.Schema
    63  var propertyValueSchema *jsonschema.Schema
    64  
    65  func init() {
    66  	compiler := jsonschema.NewCompiler()
    67  	compiler.LoadURL = func(s string) (io.ReadCloser, error) {
    68  		var schema string
    69  		switch s {
    70  		case apitype.DeploymentSchemaID:
    71  			schema = apitype.DeploymentSchema()
    72  		case apitype.ResourceSchemaID:
    73  			schema = apitype.ResourceSchema()
    74  		case apitype.PropertyValueSchemaID:
    75  			schema = apitype.PropertyValueSchema()
    76  		default:
    77  			return jsonschema.LoadURL(s)
    78  		}
    79  		return ioutil.NopCloser(strings.NewReader(schema)), nil
    80  	}
    81  	deploymentSchema = compiler.MustCompile(apitype.DeploymentSchemaID)
    82  	resourceSchema = compiler.MustCompile(apitype.ResourceSchemaID)
    83  	propertyValueSchema = compiler.MustCompile(apitype.PropertyValueSchemaID)
    84  }
    85  
    86  // ValidateUntypedDeployment validates a deployment against the Deployment JSON schema.
    87  func ValidateUntypedDeployment(deployment *apitype.UntypedDeployment) error {
    88  	bytes, err := json.Marshal(deployment)
    89  	if err != nil {
    90  		return err
    91  	}
    92  
    93  	var raw interface{}
    94  	if err := json.Unmarshal(bytes, &raw); err != nil {
    95  		return err
    96  	}
    97  
    98  	return deploymentSchema.Validate(raw)
    99  }
   100  
   101  // SerializeDeployment serializes an entire snapshot as a deploy record.
   102  func SerializeDeployment(snap *deploy.Snapshot, sm secrets.Manager, showSecrets bool) (*apitype.DeploymentV3, error) {
   103  	contract.Require(snap != nil, "snap")
   104  
   105  	// Capture the version information into a manifest.
   106  	manifest := snap.Manifest.Serialize()
   107  
   108  	// If a specific secrets manager was not provided, use the one in the snapshot, if present.
   109  	if sm == nil {
   110  		sm = snap.SecretsManager
   111  	}
   112  
   113  	var enc config.Encrypter
   114  	if sm != nil {
   115  		e, err := sm.Encrypter()
   116  		if err != nil {
   117  			return nil, fmt.Errorf("getting encrypter for deployment: %w", err)
   118  		}
   119  		enc = e
   120  	} else {
   121  		enc = config.NewPanicCrypter()
   122  	}
   123  
   124  	// Serialize all vertices and only include a vertex section if non-empty.
   125  	var resources []apitype.ResourceV3
   126  	for _, res := range snap.Resources {
   127  		sres, err := SerializeResource(res, enc, showSecrets)
   128  		if err != nil {
   129  			return nil, fmt.Errorf("serializing resources: %w", err)
   130  		}
   131  		resources = append(resources, sres)
   132  	}
   133  
   134  	var operations []apitype.OperationV2
   135  	for _, op := range snap.PendingOperations {
   136  		sop, err := SerializeOperation(op, enc, showSecrets)
   137  		if err != nil {
   138  			return nil, err
   139  		}
   140  		operations = append(operations, sop)
   141  	}
   142  
   143  	var secretsProvider *apitype.SecretsProvidersV1
   144  	if sm != nil {
   145  		secretsProvider = &apitype.SecretsProvidersV1{
   146  			Type: sm.Type(),
   147  		}
   148  		if state := sm.State(); state != nil {
   149  			rm, err := json.Marshal(state)
   150  			if err != nil {
   151  				return nil, err
   152  			}
   153  			secretsProvider.State = rm
   154  		}
   155  	}
   156  
   157  	return &apitype.DeploymentV3{
   158  		Manifest:          manifest,
   159  		Resources:         resources,
   160  		SecretsProviders:  secretsProvider,
   161  		PendingOperations: operations,
   162  	}, nil
   163  }
   164  
   165  // DeserializeUntypedDeployment deserializes an untyped deployment and produces a `deploy.Snapshot`
   166  // from it. DeserializeDeployment will return an error if the untyped deployment's version is
   167  // not within the range `DeploymentSchemaVersionCurrent` and `DeploymentSchemaVersionOldestSupported`.
   168  func DeserializeUntypedDeployment(
   169  	ctx context.Context,
   170  	deployment *apitype.UntypedDeployment,
   171  	secretsProv SecretsProvider) (*deploy.Snapshot, error) {
   172  
   173  	contract.Require(deployment != nil, "deployment")
   174  	switch {
   175  	case deployment.Version > apitype.DeploymentSchemaVersionCurrent:
   176  		return nil, ErrDeploymentSchemaVersionTooNew
   177  	case deployment.Version < DeploymentSchemaVersionOldestSupported:
   178  		return nil, ErrDeploymentSchemaVersionTooOld
   179  	}
   180  
   181  	var v3deployment apitype.DeploymentV3
   182  	switch deployment.Version {
   183  	case 1:
   184  		var v1deployment apitype.DeploymentV1
   185  		if err := json.Unmarshal([]byte(deployment.Deployment), &v1deployment); err != nil {
   186  			return nil, err
   187  		}
   188  		v2deployment := migrate.UpToDeploymentV2(v1deployment)
   189  		v3deployment = migrate.UpToDeploymentV3(v2deployment)
   190  	case 2:
   191  		var v2deployment apitype.DeploymentV2
   192  		if err := json.Unmarshal([]byte(deployment.Deployment), &v2deployment); err != nil {
   193  			return nil, err
   194  		}
   195  		v3deployment = migrate.UpToDeploymentV3(v2deployment)
   196  	case 3:
   197  		if err := json.Unmarshal([]byte(deployment.Deployment), &v3deployment); err != nil {
   198  			return nil, err
   199  		}
   200  	default:
   201  		contract.Failf("unrecognized version: %d", deployment.Version)
   202  	}
   203  
   204  	return DeserializeDeploymentV3(ctx, v3deployment, secretsProv)
   205  }
   206  
   207  // DeserializeDeploymentV3 deserializes a typed DeploymentV3 into a `deploy.Snapshot`.
   208  func DeserializeDeploymentV3(
   209  	ctx context.Context,
   210  	deployment apitype.DeploymentV3,
   211  	secretsProv SecretsProvider) (*deploy.Snapshot, error) {
   212  
   213  	// Unpack the versions.
   214  	manifest, err := deploy.DeserializeManifest(deployment.Manifest)
   215  	if err != nil {
   216  		return nil, err
   217  	}
   218  
   219  	var secretsManager secrets.Manager
   220  	if deployment.SecretsProviders != nil && deployment.SecretsProviders.Type != "" {
   221  		if secretsProv == nil {
   222  			return nil, errors.New("deployment uses a SecretsProvider but no SecretsProvider was provided")
   223  		}
   224  
   225  		sm, err := secretsProv.OfType(deployment.SecretsProviders.Type, deployment.SecretsProviders.State)
   226  		if err != nil {
   227  			return nil, err
   228  		}
   229  		secretsManager = sm
   230  	}
   231  
   232  	var dec config.Decrypter
   233  	var enc config.Encrypter
   234  	if secretsManager == nil {
   235  		dec = config.NewPanicCrypter()
   236  		enc = config.NewPanicCrypter()
   237  	} else {
   238  		d, err := secretsManager.Decrypter()
   239  		if err != nil {
   240  			return nil, err
   241  		}
   242  
   243  		// Do a first pass through state and collect all of the secrets that need decrypting.
   244  		// We will collect all secrets and decrypt them all at once, rather than just-in-time.
   245  		// We do this to avoid serial calls to the decryption endpoint which can result in long
   246  		// wait times in stacks with a large number of secrets.
   247  		var ciphertexts []string
   248  		for _, res := range deployment.Resources {
   249  			collectCiphertexts(&ciphertexts, res.Inputs)
   250  			collectCiphertexts(&ciphertexts, res.Outputs)
   251  		}
   252  
   253  		// Decrypt the collected secrets and create a decrypter that will use the result as a cache.
   254  		cache, err := d.BulkDecrypt(ctx, ciphertexts)
   255  		if err != nil {
   256  			return nil, err
   257  		}
   258  		dec = newMapDecrypter(d, cache)
   259  
   260  		e, err := secretsManager.Encrypter()
   261  		if err != nil {
   262  			return nil, err
   263  		}
   264  		enc = e
   265  	}
   266  
   267  	// For every serialized resource vertex, create a ResourceDeployment out of it.
   268  	var resources []*resource.State
   269  	for _, res := range deployment.Resources {
   270  		desres, err := DeserializeResource(res, dec, enc)
   271  		if err != nil {
   272  			return nil, err
   273  		}
   274  		resources = append(resources, desres)
   275  	}
   276  
   277  	var ops []resource.Operation
   278  	for _, op := range deployment.PendingOperations {
   279  		desop, err := DeserializeOperation(op, dec, enc)
   280  		if err != nil {
   281  			return nil, err
   282  		}
   283  		ops = append(ops, desop)
   284  	}
   285  
   286  	return deploy.NewSnapshot(*manifest, secretsManager, resources, ops), nil
   287  }
   288  
   289  // SerializeResource turns a resource into a structure suitable for serialization.
   290  func SerializeResource(res *resource.State, enc config.Encrypter, showSecrets bool) (apitype.ResourceV3, error) {
   291  	contract.Assert(res != nil)
   292  	contract.Assertf(string(res.URN) != "", "Unexpected empty resource resource.URN")
   293  
   294  	// Serialize all input and output properties recursively, and add them if non-empty.
   295  	var inputs map[string]interface{}
   296  	if inp := res.Inputs; inp != nil {
   297  		sinp, err := SerializeProperties(inp, enc, showSecrets)
   298  		if err != nil {
   299  			return apitype.ResourceV3{}, err
   300  		}
   301  		inputs = sinp
   302  	}
   303  	var outputs map[string]interface{}
   304  	if outp := res.Outputs; outp != nil {
   305  		soutp, err := SerializeProperties(outp, enc, showSecrets)
   306  		if err != nil {
   307  			return apitype.ResourceV3{}, err
   308  		}
   309  		outputs = soutp
   310  	}
   311  
   312  	v3Resource := apitype.ResourceV3{
   313  		URN:                     res.URN,
   314  		Custom:                  res.Custom,
   315  		Delete:                  res.Delete,
   316  		ID:                      res.ID,
   317  		Type:                    res.Type,
   318  		Parent:                  res.Parent,
   319  		Inputs:                  inputs,
   320  		Outputs:                 outputs,
   321  		Protect:                 res.Protect,
   322  		External:                res.External,
   323  		Dependencies:            res.Dependencies,
   324  		InitErrors:              res.InitErrors,
   325  		Provider:                res.Provider,
   326  		PropertyDependencies:    res.PropertyDependencies,
   327  		PendingReplacement:      res.PendingReplacement,
   328  		AdditionalSecretOutputs: res.AdditionalSecretOutputs,
   329  		Aliases:                 res.Aliases,
   330  		ImportID:                res.ImportID,
   331  		RetainOnDelete:          res.RetainOnDelete,
   332  		DeletedWith:             res.DeletedWith,
   333  	}
   334  
   335  	if res.CustomTimeouts.IsNotEmpty() {
   336  		v3Resource.CustomTimeouts = &res.CustomTimeouts
   337  	}
   338  
   339  	return v3Resource, nil
   340  }
   341  
   342  func SerializeOperation(op resource.Operation, enc config.Encrypter, showSecrets bool) (apitype.OperationV2, error) {
   343  	res, err := SerializeResource(op.Resource, enc, showSecrets)
   344  	if err != nil {
   345  		return apitype.OperationV2{}, fmt.Errorf("serializing resource: %w", err)
   346  	}
   347  	return apitype.OperationV2{
   348  		Resource: res,
   349  		Type:     apitype.OperationType(op.Type),
   350  	}, nil
   351  }
   352  
   353  // SerializeProperties serializes a resource property bag so that it's suitable for serialization.
   354  func SerializeProperties(props resource.PropertyMap, enc config.Encrypter,
   355  	showSecrets bool) (map[string]interface{}, error) {
   356  	dst := make(map[string]interface{})
   357  	for _, k := range props.StableKeys() {
   358  		v, err := SerializePropertyValue(props[k], enc, showSecrets)
   359  		if err != nil {
   360  			return nil, err
   361  		}
   362  		dst[string(k)] = v
   363  	}
   364  	return dst, nil
   365  }
   366  
   367  // SerializePropertyValue serializes a resource property value so that it's suitable for serialization.
   368  func SerializePropertyValue(prop resource.PropertyValue, enc config.Encrypter,
   369  	showSecrets bool) (interface{}, error) {
   370  	ctx := context.TODO()
   371  
   372  	// Serialize nulls as nil.
   373  	if prop.IsNull() {
   374  		return nil, nil
   375  	}
   376  
   377  	// A computed value marks something that will be determined at a later time. (e.g. the result of
   378  	// a computation that we don't perform during a preview operation.) We serialize a magic constant
   379  	// to record its existence.
   380  	if prop.IsComputed() || prop.IsOutput() {
   381  		return computedValuePlaceholder, nil
   382  	}
   383  
   384  	// For arrays, make sure to recurse.
   385  	if prop.IsArray() {
   386  		srcarr := prop.ArrayValue()
   387  		dstarr := make([]interface{}, len(srcarr))
   388  		for i, elem := range prop.ArrayValue() {
   389  			selem, err := SerializePropertyValue(elem, enc, showSecrets)
   390  			if err != nil {
   391  				return nil, err
   392  			}
   393  			dstarr[i] = selem
   394  		}
   395  		return dstarr, nil
   396  	}
   397  
   398  	// Also for objects, recurse and use naked properties.
   399  	if prop.IsObject() {
   400  		return SerializeProperties(prop.ObjectValue(), enc, showSecrets)
   401  	}
   402  
   403  	// For assets, we need to serialize them a little carefully, so we can recover them afterwards.
   404  	if prop.IsAsset() {
   405  		return prop.AssetValue().Serialize(), nil
   406  	} else if prop.IsArchive() {
   407  		return prop.ArchiveValue().Serialize(), nil
   408  	}
   409  
   410  	// We serialize resource references using a map-based representation similar to assets, archives, and secrets.
   411  	if prop.IsResourceReference() {
   412  		ref := prop.ResourceReferenceValue()
   413  		serialized := map[string]interface{}{
   414  			resource.SigKey:  resource.ResourceReferenceSig,
   415  			"urn":            string(ref.URN),
   416  			"packageVersion": ref.PackageVersion,
   417  		}
   418  		if id, hasID := ref.IDString(); hasID {
   419  			serialized["id"] = id
   420  		}
   421  		return serialized, nil
   422  	}
   423  
   424  	if prop.IsSecret() {
   425  		// Since we are going to encrypt property value, we can elide encrypting sub-elements. We'll mark them as
   426  		// "secret" so we retain that information when deserializing the overall structure, but there is no
   427  		// need to double encrypt everything.
   428  		value, err := SerializePropertyValue(prop.SecretValue().Element, config.NopEncrypter, showSecrets)
   429  		if err != nil {
   430  			return nil, err
   431  		}
   432  		bytes, err := json.Marshal(value)
   433  		if err != nil {
   434  			return nil, fmt.Errorf("encoding serialized property value: %w", err)
   435  		}
   436  		plaintext := string(bytes)
   437  
   438  		// If the encrypter is a cachingCrypter, call through its encryptSecret method, which will look for a matching
   439  		// *resource.Secret + plaintext in its cache in order to avoid re-encrypting the value.
   440  		var ciphertext string
   441  		if cachingCrypter, ok := enc.(*cachingCrypter); ok {
   442  			ciphertext, err = cachingCrypter.encryptSecret(prop.SecretValue(), plaintext)
   443  		} else {
   444  			ciphertext, err = enc.EncryptValue(ctx, plaintext)
   445  		}
   446  		if err != nil {
   447  			return nil, fmt.Errorf("failed to encrypt secret value: %w", err)
   448  		}
   449  		contract.AssertNoErrorf(err, "marshalling underlying secret value to JSON")
   450  
   451  		secret := apitype.SecretV1{
   452  			Sig: resource.SecretSig,
   453  		}
   454  
   455  		if showSecrets {
   456  			secret.Plaintext = plaintext
   457  		} else {
   458  			secret.Ciphertext = ciphertext
   459  		}
   460  
   461  		return secret, nil
   462  	}
   463  
   464  	// All others are returned as-is.
   465  	return prop.V, nil
   466  }
   467  
   468  // collectCiphertexts collects encrypted secrets from resource properties.
   469  func collectCiphertexts(ciphertexts *[]string, prop interface{}) {
   470  	switch prop := prop.(type) {
   471  	case []interface{}:
   472  		for _, v := range prop {
   473  			collectCiphertexts(ciphertexts, v)
   474  		}
   475  	case map[string]interface{}:
   476  		if prop[resource.SigKey] == resource.SecretSig {
   477  			if ciphertext, cipherOk := prop["ciphertext"].(string); cipherOk {
   478  				*ciphertexts = append(*ciphertexts, ciphertext)
   479  			}
   480  		} else {
   481  			for _, v := range prop {
   482  				collectCiphertexts(ciphertexts, v)
   483  			}
   484  		}
   485  	}
   486  }
   487  
   488  // DeserializeResource turns a serialized resource back into its usual form.
   489  func DeserializeResource(res apitype.ResourceV3, dec config.Decrypter, enc config.Encrypter) (*resource.State, error) {
   490  	// Deserialize the resource properties, if they exist.
   491  	inputs, err := DeserializeProperties(res.Inputs, dec, enc)
   492  	if err != nil {
   493  		return nil, err
   494  	}
   495  	outputs, err := DeserializeProperties(res.Outputs, dec, enc)
   496  	if err != nil {
   497  		return nil, err
   498  	}
   499  
   500  	if res.URN == "" {
   501  		return nil, fmt.Errorf("resource missing required 'urn' field")
   502  	}
   503  
   504  	if res.Type == "" {
   505  		return nil, fmt.Errorf("resource '%s' missing required 'type' field", res.URN)
   506  	}
   507  
   508  	if !res.Custom && res.ID != "" {
   509  		return nil, fmt.Errorf("resource '%s' has 'custom' false but non-empty ID", res.URN)
   510  	}
   511  
   512  	return resource.NewState(
   513  		res.Type, res.URN, res.Custom, res.Delete, res.ID,
   514  		inputs, outputs, res.Parent, res.Protect, res.External, res.Dependencies, res.InitErrors, res.Provider,
   515  		res.PropertyDependencies, res.PendingReplacement, res.AdditionalSecretOutputs, res.Aliases, res.CustomTimeouts,
   516  		res.ImportID, res.RetainOnDelete, res.DeletedWith), nil
   517  }
   518  
   519  func DeserializeOperation(op apitype.OperationV2, dec config.Decrypter,
   520  	enc config.Encrypter) (resource.Operation, error) {
   521  	res, err := DeserializeResource(op.Resource, dec, enc)
   522  	if err != nil {
   523  		return resource.Operation{}, err
   524  	}
   525  	return resource.NewOperation(res, resource.OperationType(op.Type)), nil
   526  }
   527  
   528  // DeserializeProperties deserializes an entire map of deploy properties into a resource property map.
   529  func DeserializeProperties(props map[string]interface{}, dec config.Decrypter,
   530  	enc config.Encrypter) (resource.PropertyMap, error) {
   531  	result := make(resource.PropertyMap)
   532  	for k, prop := range props {
   533  		desprop, err := DeserializePropertyValue(prop, dec, enc)
   534  		if err != nil {
   535  			return nil, err
   536  		}
   537  		result[resource.PropertyKey(k)] = desprop
   538  	}
   539  	return result, nil
   540  }
   541  
   542  // DeserializePropertyValue deserializes a single deploy property into a resource property value.
   543  func DeserializePropertyValue(v interface{}, dec config.Decrypter,
   544  	enc config.Encrypter) (resource.PropertyValue, error) {
   545  	ctx := context.TODO()
   546  	if v != nil {
   547  		switch w := v.(type) {
   548  		case bool:
   549  			return resource.NewBoolProperty(w), nil
   550  		case float64:
   551  			return resource.NewNumberProperty(w), nil
   552  		case string:
   553  			if w == computedValuePlaceholder {
   554  				return resource.MakeComputed(resource.NewStringProperty("")), nil
   555  			}
   556  			return resource.NewStringProperty(w), nil
   557  		case []interface{}:
   558  			var arr []resource.PropertyValue
   559  			for _, elem := range w {
   560  				ev, err := DeserializePropertyValue(elem, dec, enc)
   561  				if err != nil {
   562  					return resource.PropertyValue{}, err
   563  				}
   564  				arr = append(arr, ev)
   565  			}
   566  			return resource.NewArrayProperty(arr), nil
   567  		case map[string]interface{}:
   568  			obj, err := DeserializeProperties(w, dec, enc)
   569  			if err != nil {
   570  				return resource.PropertyValue{}, err
   571  			}
   572  
   573  			// This could be an asset or archive; if so, recover its type.
   574  			objmap := obj.Mappable()
   575  			if sig, hasSig := objmap[resource.SigKey]; hasSig {
   576  				switch sig {
   577  				case resource.AssetSig:
   578  					asset, isasset, err := resource.DeserializeAsset(objmap)
   579  					if err != nil {
   580  						return resource.PropertyValue{}, err
   581  					}
   582  					contract.Assert(isasset)
   583  					return resource.NewAssetProperty(asset), nil
   584  				case resource.ArchiveSig:
   585  					archive, isarchive, err := resource.DeserializeArchive(objmap)
   586  					if err != nil {
   587  						return resource.PropertyValue{}, err
   588  					}
   589  					contract.Assert(isarchive)
   590  					return resource.NewArchiveProperty(archive), nil
   591  				case resource.SecretSig:
   592  					ciphertext, cipherOk := objmap["ciphertext"].(string)
   593  					plaintext, plainOk := objmap["plaintext"].(string)
   594  					if (!cipherOk && !plainOk) || (plainOk && cipherOk) {
   595  						return resource.PropertyValue{}, errors.New(
   596  							"malformed secret value: one of `ciphertext` or `plaintext` must be supplied")
   597  					}
   598  
   599  					if plainOk {
   600  						encryptedText, err := enc.EncryptValue(ctx, plaintext)
   601  						if err != nil {
   602  							return resource.PropertyValue{}, fmt.Errorf("encrypting secret value: %w", err)
   603  						}
   604  						ciphertext = encryptedText
   605  
   606  					} else {
   607  						unencryptedText, err := dec.DecryptValue(ctx, ciphertext)
   608  						if err != nil {
   609  							return resource.PropertyValue{}, fmt.Errorf("error decrypting secret value: %s", err.Error())
   610  						}
   611  						plaintext = unencryptedText
   612  					}
   613  
   614  					var elem interface{}
   615  
   616  					if err := json.Unmarshal([]byte(plaintext), &elem); err != nil {
   617  						return resource.PropertyValue{}, err
   618  					}
   619  					ev, err := DeserializePropertyValue(elem, config.NopDecrypter, enc)
   620  					if err != nil {
   621  						return resource.PropertyValue{}, err
   622  					}
   623  					prop := resource.MakeSecret(ev)
   624  					// If the decrypter is a cachingCrypter, insert the plain- and ciphertext into the cache with the
   625  					// new *resource.Secret as the key.
   626  					if cachingCrypter, ok := dec.(*cachingCrypter); ok {
   627  						cachingCrypter.insert(prop.SecretValue(), plaintext, ciphertext)
   628  					}
   629  					return prop, nil
   630  				case resource.ResourceReferenceSig:
   631  					var packageVersion string
   632  					if packageVersionV, ok := objmap["packageVersion"]; ok {
   633  						packageVersion, ok = packageVersionV.(string)
   634  						if !ok {
   635  							return resource.PropertyValue{},
   636  								errors.New("malformed resource reference: packageVersion must be a string")
   637  						}
   638  					}
   639  
   640  					urnStr, ok := objmap["urn"].(string)
   641  					if !ok {
   642  						return resource.PropertyValue{}, errors.New("malformed resource reference: missing urn")
   643  					}
   644  					urn := resource.URN(urnStr)
   645  
   646  					// deserializeID handles two cases, one of which arose from a bug in a refactoring of resource.ResourceReference.
   647  					// This bug caused the raw ID PropertyValue to be serialized as a map[string]interface{}. In the normal case, the
   648  					// ID is serialized as a string.
   649  					deserializeID := func() (string, bool, error) {
   650  						idV, ok := objmap["id"]
   651  						if !ok {
   652  							return "", false, nil
   653  						}
   654  
   655  						switch idV := idV.(type) {
   656  						case string:
   657  							return idV, true, nil
   658  						case map[string]interface{}:
   659  							switch v := idV["V"].(type) {
   660  							case nil:
   661  								// This happens for component resource references, which do not have an associated ID.
   662  								return "", false, nil
   663  							case string:
   664  								// This happens for custom resource references, which do have an associated ID.
   665  								return v, true, nil
   666  							case map[string]interface{}:
   667  								// This happens for custom resource references with an unknown ID. In this case, the ID should be
   668  								// deserialized as the empty string.
   669  								return "", true, nil
   670  							}
   671  						}
   672  						return "", false, errors.New("malformed resource reference: id must be a string")
   673  					}
   674  
   675  					id, hasID, err := deserializeID()
   676  					if err != nil {
   677  						return resource.PropertyValue{}, err
   678  					}
   679  					if hasID {
   680  						return resource.MakeCustomResourceReference(urn, resource.ID(id), packageVersion), nil
   681  					}
   682  					return resource.MakeComponentResourceReference(urn, packageVersion), nil
   683  				default:
   684  					return resource.PropertyValue{}, fmt.Errorf("unrecognized signature '%v' in property map", sig)
   685  				}
   686  			}
   687  
   688  			// Otherwise, it's just a weakly typed object map.
   689  			return resource.NewObjectProperty(obj), nil
   690  		default:
   691  			contract.Failf("Unrecognized property type %T: %v", v, reflect.ValueOf(v))
   692  		}
   693  	}
   694  
   695  	return resource.NewNullProperty(), nil
   696  }