github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/resource/deploy/deploytest/resourcemonitor.go (about)

     1  // Copyright 2016-2018, 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 deploytest
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  
    21  	"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
    22  	"github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin"
    23  	"github.com/pulumi/pulumi/sdk/v3/go/common/tokens"
    24  	"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
    25  	"github.com/pulumi/pulumi/sdk/v3/go/common/util/rpcutil"
    26  	pulumirpc "github.com/pulumi/pulumi/sdk/v3/proto/go"
    27  	"google.golang.org/grpc"
    28  )
    29  
    30  type ResourceMonitor struct {
    31  	conn   *grpc.ClientConn
    32  	resmon pulumirpc.ResourceMonitorClient
    33  
    34  	supportsSecrets            bool
    35  	supportsResourceReferences bool
    36  }
    37  
    38  func dialMonitor(ctx context.Context, endpoint string) (*ResourceMonitor, error) {
    39  	// Connect to the resource monitor and create an appropriate client.
    40  	conn, err := grpc.Dial(
    41  		endpoint,
    42  		grpc.WithInsecure(),
    43  		rpcutil.GrpcChannelOptions(),
    44  	)
    45  	if err != nil {
    46  		return nil, fmt.Errorf("could not connect to resource monitor: %w", err)
    47  	}
    48  	resmon := pulumirpc.NewResourceMonitorClient(conn)
    49  
    50  	// Check feature support.
    51  	supportsSecrets, err := supportsFeature(ctx, resmon, "secrets")
    52  	if err != nil {
    53  		contract.IgnoreError(conn.Close())
    54  		return nil, err
    55  	}
    56  	supportsResourceReferences, err := supportsFeature(ctx, resmon, "resourceReferences")
    57  	if err != nil {
    58  		contract.IgnoreError(conn.Close())
    59  		return nil, err
    60  	}
    61  
    62  	// Fire up a resource monitor client and return.
    63  	return &ResourceMonitor{
    64  		conn:                       conn,
    65  		resmon:                     resmon,
    66  		supportsSecrets:            supportsSecrets,
    67  		supportsResourceReferences: supportsResourceReferences,
    68  	}, nil
    69  }
    70  
    71  func supportsFeature(ctx context.Context, resmon pulumirpc.ResourceMonitorClient, id string) (bool, error) {
    72  	resp, err := resmon.SupportsFeature(ctx, &pulumirpc.SupportsFeatureRequest{Id: id})
    73  	if err != nil {
    74  		return false, err
    75  	}
    76  	return resp.GetHasSupport(), nil
    77  }
    78  
    79  func (rm *ResourceMonitor) Close() error {
    80  	return rm.conn.Close()
    81  }
    82  
    83  func NewResourceMonitor(resmon pulumirpc.ResourceMonitorClient) *ResourceMonitor {
    84  	return &ResourceMonitor{resmon: resmon}
    85  }
    86  
    87  type ResourceOptions struct {
    88  	Parent                  resource.URN
    89  	Protect                 bool
    90  	Dependencies            []resource.URN
    91  	Provider                string
    92  	Inputs                  resource.PropertyMap
    93  	PropertyDeps            map[resource.PropertyKey][]resource.URN
    94  	DeleteBeforeReplace     *bool
    95  	Version                 string
    96  	PluginDownloadURL       string
    97  	IgnoreChanges           []string
    98  	ReplaceOnChanges        []string
    99  	AliasURNs               []resource.URN
   100  	Aliases                 []resource.Alias
   101  	ImportID                resource.ID
   102  	CustomTimeouts          *resource.CustomTimeouts
   103  	RetainOnDelete          bool
   104  	DeletedWith             resource.URN
   105  	SupportsPartialValues   *bool
   106  	Remote                  bool
   107  	Providers               map[string]string
   108  	AdditionalSecretOutputs []resource.PropertyKey
   109  
   110  	DisableSecrets            bool
   111  	DisableResourceReferences bool
   112  }
   113  
   114  func (rm *ResourceMonitor) RegisterResource(t tokens.Type, name string, custom bool,
   115  	options ...ResourceOptions) (resource.URN, resource.ID, resource.PropertyMap, error) {
   116  
   117  	var opts ResourceOptions
   118  	if len(options) > 0 {
   119  		opts = options[0]
   120  	}
   121  	if opts.Inputs == nil {
   122  		opts.Inputs = resource.PropertyMap{}
   123  	}
   124  
   125  	// marshal inputs
   126  	ins, err := plugin.MarshalProperties(opts.Inputs, plugin.MarshalOptions{
   127  		KeepUnknowns:  true,
   128  		KeepSecrets:   rm.supportsSecrets,
   129  		KeepResources: rm.supportsResourceReferences,
   130  	})
   131  	if err != nil {
   132  		return "", "", nil, err
   133  	}
   134  
   135  	// marshal dependencies
   136  	deps := []string{}
   137  	for _, d := range opts.Dependencies {
   138  		deps = append(deps, string(d))
   139  	}
   140  
   141  	// marshal aliases
   142  	aliasStrings := []string{}
   143  	for _, a := range opts.AliasURNs {
   144  		aliasStrings = append(aliasStrings, string(a))
   145  	}
   146  
   147  	aliasObjects := []*pulumirpc.Alias{}
   148  	for _, a := range opts.Aliases {
   149  		var obj *pulumirpc.Alias
   150  		if a.URN == "" {
   151  			alias := &pulumirpc.Alias_Spec{
   152  				Name:    a.Name,
   153  				Type:    a.Type,
   154  				Project: a.Project,
   155  				Stack:   a.Stack,
   156  			}
   157  			if a.NoParent() {
   158  				alias.Parent = &pulumirpc.Alias_Spec_NoParent{NoParent: a.NoParent()}
   159  			} else if a.Parent != "" {
   160  				alias.Parent = &pulumirpc.Alias_Spec_ParentUrn{ParentUrn: string(a.Parent)}
   161  			}
   162  			obj = &pulumirpc.Alias{Alias: &pulumirpc.Alias_Spec_{Spec: alias}}
   163  		} else {
   164  			obj = &pulumirpc.Alias{Alias: &pulumirpc.Alias_Urn{Urn: string(a.URN)}}
   165  		}
   166  		aliasObjects = append(aliasObjects, obj)
   167  	}
   168  
   169  	inputDeps := make(map[string]*pulumirpc.RegisterResourceRequest_PropertyDependencies)
   170  	for pk, pd := range opts.PropertyDeps {
   171  		pdeps := []string{}
   172  		for _, d := range pd {
   173  			pdeps = append(pdeps, string(d))
   174  		}
   175  		inputDeps[string(pk)] = &pulumirpc.RegisterResourceRequest_PropertyDependencies{
   176  			Urns: pdeps,
   177  		}
   178  	}
   179  
   180  	var timeouts pulumirpc.RegisterResourceRequest_CustomTimeouts
   181  	if opts.CustomTimeouts != nil {
   182  		timeouts.Create = prepareTestTimeout(opts.CustomTimeouts.Create)
   183  		timeouts.Update = prepareTestTimeout(opts.CustomTimeouts.Update)
   184  		timeouts.Delete = prepareTestTimeout(opts.CustomTimeouts.Delete)
   185  	}
   186  
   187  	deleteBeforeReplace := false
   188  	if opts.DeleteBeforeReplace != nil {
   189  		deleteBeforeReplace = *opts.DeleteBeforeReplace
   190  	}
   191  	supportsPartialValues := true
   192  	if opts.SupportsPartialValues != nil {
   193  		supportsPartialValues = *opts.SupportsPartialValues
   194  	}
   195  	additionalSecretOutputs := make([]string, len(opts.AdditionalSecretOutputs))
   196  	for i, v := range opts.AdditionalSecretOutputs {
   197  		additionalSecretOutputs[i] = string(v)
   198  	}
   199  	requestInput := &pulumirpc.RegisterResourceRequest{
   200  		Type:                       string(t),
   201  		Name:                       name,
   202  		Custom:                     custom,
   203  		Parent:                     string(opts.Parent),
   204  		Protect:                    opts.Protect,
   205  		Dependencies:               deps,
   206  		Provider:                   opts.Provider,
   207  		Object:                     ins,
   208  		PropertyDependencies:       inputDeps,
   209  		DeleteBeforeReplace:        deleteBeforeReplace,
   210  		DeleteBeforeReplaceDefined: opts.DeleteBeforeReplace != nil,
   211  		IgnoreChanges:              opts.IgnoreChanges,
   212  		AcceptSecrets:              !opts.DisableSecrets,
   213  		AcceptResources:            !opts.DisableResourceReferences,
   214  		Version:                    opts.Version,
   215  		AliasURNs:                  aliasStrings,
   216  		ImportId:                   string(opts.ImportID),
   217  		CustomTimeouts:             &timeouts,
   218  		SupportsPartialValues:      supportsPartialValues,
   219  		Remote:                     opts.Remote,
   220  		ReplaceOnChanges:           opts.ReplaceOnChanges,
   221  		Providers:                  opts.Providers,
   222  		PluginDownloadURL:          opts.PluginDownloadURL,
   223  		RetainOnDelete:             opts.RetainOnDelete,
   224  		AdditionalSecretOutputs:    additionalSecretOutputs,
   225  		Aliases:                    aliasObjects,
   226  		DeletedWith:                string(opts.DeletedWith),
   227  	}
   228  
   229  	// submit request
   230  	resp, err := rm.resmon.RegisterResource(context.Background(), requestInput)
   231  	if err != nil {
   232  		return "", "", nil, err
   233  	}
   234  	// unmarshal outputs
   235  	//
   236  	// Note that `KeepSecrets` and `KeepResources` are set to `true` so the caller can detect secrets and resource refs
   237  	// that are erroneously returned (e.g. secrets/resource refs that are returned even though the caller has not set
   238  	// `AcceptSecrets` or `AcceptResources` to `true` above).
   239  	outs, err := plugin.UnmarshalProperties(resp.Object, plugin.MarshalOptions{
   240  		KeepUnknowns:  true,
   241  		KeepSecrets:   true,
   242  		KeepResources: true,
   243  	})
   244  	if err != nil {
   245  		return "", "", nil, err
   246  	}
   247  
   248  	return resource.URN(resp.Urn), resource.ID(resp.Id), outs, nil
   249  }
   250  
   251  func (rm *ResourceMonitor) RegisterResourceOutputs(urn resource.URN, outputs resource.PropertyMap) error {
   252  	// marshal outputs
   253  	outs, err := plugin.MarshalProperties(outputs, plugin.MarshalOptions{
   254  		KeepUnknowns: true,
   255  	})
   256  	if err != nil {
   257  		return err
   258  	}
   259  
   260  	// submit request
   261  	_, err = rm.resmon.RegisterResourceOutputs(context.Background(), &pulumirpc.RegisterResourceOutputsRequest{
   262  		Urn:     string(urn),
   263  		Outputs: outs,
   264  	})
   265  	return err
   266  }
   267  
   268  func (rm *ResourceMonitor) ReadResource(t tokens.Type, name string, id resource.ID, parent resource.URN,
   269  	inputs resource.PropertyMap, provider string, version string) (resource.URN, resource.PropertyMap, error) {
   270  
   271  	// marshal inputs
   272  	ins, err := plugin.MarshalProperties(inputs, plugin.MarshalOptions{
   273  		KeepUnknowns:  true,
   274  		KeepResources: true,
   275  	})
   276  	if err != nil {
   277  		return "", nil, err
   278  	}
   279  
   280  	// submit request
   281  	resp, err := rm.resmon.ReadResource(context.Background(), &pulumirpc.ReadResourceRequest{
   282  		Type:       string(t),
   283  		Name:       name,
   284  		Id:         string(id),
   285  		Parent:     string(parent),
   286  		Provider:   provider,
   287  		Properties: ins,
   288  		Version:    version,
   289  	})
   290  	if err != nil {
   291  		return "", nil, err
   292  	}
   293  
   294  	// unmarshal outputs
   295  	outs, err := plugin.UnmarshalProperties(resp.Properties, plugin.MarshalOptions{
   296  		KeepUnknowns:  true,
   297  		KeepResources: true,
   298  	})
   299  	if err != nil {
   300  		return "", nil, err
   301  	}
   302  
   303  	return resource.URN(resp.Urn), outs, nil
   304  }
   305  
   306  func (rm *ResourceMonitor) Invoke(tok tokens.ModuleMember, inputs resource.PropertyMap,
   307  	provider string, version string) (resource.PropertyMap, []*pulumirpc.CheckFailure, error) {
   308  
   309  	// marshal inputs
   310  	ins, err := plugin.MarshalProperties(inputs, plugin.MarshalOptions{
   311  		KeepUnknowns:  true,
   312  		KeepResources: true,
   313  	})
   314  	if err != nil {
   315  		return nil, nil, err
   316  	}
   317  
   318  	// submit request
   319  	resp, err := rm.resmon.Invoke(context.Background(), &pulumirpc.ResourceInvokeRequest{
   320  		Tok:      string(tok),
   321  		Provider: provider,
   322  		Args:     ins,
   323  		Version:  version,
   324  	})
   325  	if err != nil {
   326  		return nil, nil, err
   327  	}
   328  
   329  	// handle failures
   330  	if len(resp.Failures) != 0 {
   331  		return nil, resp.Failures, nil
   332  	}
   333  
   334  	// unmarshal outputs
   335  	outs, err := plugin.UnmarshalProperties(resp.Return, plugin.MarshalOptions{
   336  		KeepUnknowns:  true,
   337  		KeepResources: true,
   338  	})
   339  	if err != nil {
   340  		return nil, nil, err
   341  	}
   342  
   343  	return outs, nil, nil
   344  }
   345  
   346  func (rm *ResourceMonitor) Call(tok tokens.ModuleMember, inputs resource.PropertyMap,
   347  	provider string, version string) (resource.PropertyMap, map[resource.PropertyKey][]resource.URN,
   348  	[]*pulumirpc.CheckFailure, error) {
   349  
   350  	// marshal inputs
   351  	ins, err := plugin.MarshalProperties(inputs, plugin.MarshalOptions{
   352  		KeepUnknowns:  true,
   353  		KeepResources: true,
   354  	})
   355  	if err != nil {
   356  		return nil, nil, nil, err
   357  	}
   358  
   359  	// submit request
   360  	resp, err := rm.resmon.Call(context.Background(), &pulumirpc.CallRequest{
   361  		Tok:      string(tok),
   362  		Provider: provider,
   363  		Args:     ins,
   364  		Version:  version,
   365  	})
   366  	if err != nil {
   367  		return nil, nil, nil, err
   368  	}
   369  
   370  	// handle failures
   371  	if len(resp.Failures) != 0 {
   372  		return nil, nil, resp.Failures, nil
   373  	}
   374  
   375  	// unmarshal outputs
   376  	outs, err := plugin.UnmarshalProperties(resp.Return, plugin.MarshalOptions{
   377  		KeepUnknowns:  true,
   378  		KeepResources: true,
   379  	})
   380  	if err != nil {
   381  		return nil, nil, nil, err
   382  	}
   383  
   384  	// unmarshal return deps
   385  	deps := make(map[resource.PropertyKey][]resource.URN)
   386  	for _, p := range resp.ReturnDependencies {
   387  		var urns []resource.URN
   388  		for _, urn := range p.Urns {
   389  			urns = append(urns, resource.URN(urn))
   390  		}
   391  	}
   392  
   393  	return outs, deps, nil, nil
   394  }
   395  
   396  func prepareTestTimeout(timeout float64) string {
   397  	mins := int(timeout) / 60
   398  
   399  	return fmt.Sprintf("%dm", mins)
   400  }