github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/resource/edit/operations_test.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 edit
    16  
    17  import (
    18  	"testing"
    19  	"time"
    20  
    21  	"github.com/pulumi/pulumi/pkg/v3/secrets/b64"
    22  
    23  	"github.com/pulumi/pulumi/pkg/v3/resource/deploy"
    24  	"github.com/pulumi/pulumi/pkg/v3/resource/deploy/providers"
    25  	"github.com/pulumi/pulumi/pkg/v3/version"
    26  	"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
    27  	"github.com/pulumi/pulumi/sdk/v3/go/common/tokens"
    28  
    29  	"github.com/stretchr/testify/assert"
    30  	"github.com/stretchr/testify/require"
    31  )
    32  
    33  func NewResource(name string, provider *resource.State, deps ...resource.URN) *resource.State {
    34  	prov := ""
    35  	if provider != nil {
    36  		p, err := providers.NewReference(provider.URN, provider.ID)
    37  		if err != nil {
    38  			panic(err)
    39  		}
    40  		prov = p.String()
    41  	}
    42  
    43  	t := tokens.Type("a:b:c")
    44  	return &resource.State{
    45  		Type:         t,
    46  		URN:          resource.NewURN("test", "test", "", t, tokens.QName(name)),
    47  		Inputs:       resource.PropertyMap{},
    48  		Outputs:      resource.PropertyMap{},
    49  		Dependencies: deps,
    50  		Provider:     prov,
    51  	}
    52  }
    53  
    54  func NewProviderResource(pkg, name, id string, deps ...resource.URN) *resource.State {
    55  	t := providers.MakeProviderType(tokens.Package(pkg))
    56  	return &resource.State{
    57  		Type:         t,
    58  		URN:          resource.NewURN("test", "test", "", t, tokens.QName(name)),
    59  		ID:           resource.ID(id),
    60  		Inputs:       resource.PropertyMap{},
    61  		Outputs:      resource.PropertyMap{},
    62  		Dependencies: deps,
    63  	}
    64  }
    65  
    66  func NewSnapshot(resources []*resource.State) *deploy.Snapshot {
    67  	return deploy.NewSnapshot(deploy.Manifest{
    68  		Time:    time.Now(),
    69  		Version: version.Version,
    70  		Plugins: nil,
    71  	}, b64.NewBase64SecretsManager(), resources, nil)
    72  }
    73  
    74  func TestDeletion(t *testing.T) {
    75  	t.Parallel()
    76  
    77  	pA := NewProviderResource("a", "p1", "0")
    78  	a := NewResource("a", pA)
    79  	b := NewResource("b", pA)
    80  	c := NewResource("c", pA)
    81  	snap := NewSnapshot([]*resource.State{
    82  		pA,
    83  		a,
    84  		b,
    85  		c,
    86  	})
    87  
    88  	err := DeleteResource(snap, b, nil, false)
    89  	assert.NoError(t, err)
    90  	assert.Len(t, snap.Resources, 3)
    91  	assert.Equal(t, []*resource.State{pA, a, c}, snap.Resources)
    92  }
    93  
    94  func TestDeletingDependencies(t *testing.T) {
    95  	t.Parallel()
    96  
    97  	pA := NewProviderResource("a", "p1", "0")
    98  	a := NewResource("a", pA)
    99  	b := NewResource("b", pA)
   100  	c := NewResource("c", pA, a.URN)
   101  	d := NewResource("d", pA, c.URN)
   102  	snap := NewSnapshot([]*resource.State{
   103  		pA, a, b, c, d,
   104  	})
   105  
   106  	err := DeleteResource(snap, a, nil, true)
   107  	require.NoError(t, err)
   108  
   109  	assert.Equal(t, snap.Resources, []*resource.State{pA, b})
   110  }
   111  
   112  func TestFailedDeletionProviderDependency(t *testing.T) {
   113  	t.Parallel()
   114  
   115  	pA := NewProviderResource("a", "p1", "0")
   116  	a := NewResource("a", pA)
   117  	b := NewResource("b", pA)
   118  	c := NewResource("c", pA)
   119  	snap := NewSnapshot([]*resource.State{
   120  		pA,
   121  		a,
   122  		b,
   123  		c,
   124  	})
   125  
   126  	err := DeleteResource(snap, pA, nil, false)
   127  	assert.Error(t, err)
   128  	depErr, ok := err.(ResourceHasDependenciesError)
   129  	if !assert.True(t, ok) {
   130  		t.FailNow()
   131  	}
   132  
   133  	assert.Contains(t, depErr.Dependencies, a)
   134  	assert.Contains(t, depErr.Dependencies, b)
   135  	assert.Contains(t, depErr.Dependencies, c)
   136  	assert.Len(t, snap.Resources, 4)
   137  	assert.Equal(t, []*resource.State{pA, a, b, c}, snap.Resources)
   138  }
   139  
   140  func TestFailedDeletionRegularDependency(t *testing.T) {
   141  	t.Parallel()
   142  
   143  	pA := NewProviderResource("a", "p1", "0")
   144  	a := NewResource("a", pA)
   145  	b := NewResource("b", pA, a.URN)
   146  	c := NewResource("c", pA)
   147  	snap := NewSnapshot([]*resource.State{
   148  		pA,
   149  		a,
   150  		b,
   151  		c,
   152  	})
   153  
   154  	err := DeleteResource(snap, a, nil, false)
   155  	assert.Error(t, err)
   156  	depErr, ok := err.(ResourceHasDependenciesError)
   157  	if !assert.True(t, ok) {
   158  		t.FailNow()
   159  	}
   160  
   161  	assert.NotContains(t, depErr.Dependencies, pA)
   162  	assert.NotContains(t, depErr.Dependencies, a)
   163  	assert.Contains(t, depErr.Dependencies, b)
   164  	assert.NotContains(t, depErr.Dependencies, c)
   165  	assert.Len(t, snap.Resources, 4)
   166  	assert.Equal(t, []*resource.State{pA, a, b, c}, snap.Resources)
   167  }
   168  
   169  func TestFailedDeletionProtected(t *testing.T) {
   170  	t.Parallel()
   171  
   172  	pA := NewProviderResource("a", "p1", "0")
   173  	a := NewResource("a", pA)
   174  	a.Protect = true
   175  	snap := NewSnapshot([]*resource.State{
   176  		pA,
   177  		a,
   178  	})
   179  
   180  	err := DeleteResource(snap, a, nil, false)
   181  	assert.Error(t, err)
   182  	_, ok := err.(ResourceProtectedError)
   183  	assert.True(t, ok)
   184  }
   185  
   186  func TestDeleteProtected(t *testing.T) {
   187  	t.Parallel()
   188  
   189  	tests := []struct {
   190  		name string
   191  		test func(t *testing.T, pA, a, b, c *resource.State, snap *deploy.Snapshot)
   192  	}{
   193  		{
   194  			"root-protected",
   195  			func(t *testing.T, pA, a, b, c *resource.State, snap *deploy.Snapshot) {
   196  				a.Protect = true
   197  				protectedCount := 0
   198  				err := DeleteResource(snap, a, func(s *resource.State) error {
   199  					s.Protect = false
   200  					protectedCount++
   201  					return nil
   202  				}, false)
   203  				assert.NoError(t, err)
   204  				assert.Equal(t, protectedCount, 1)
   205  				assert.Equal(t, snap.Resources, []*resource.State{pA, b, c})
   206  			},
   207  		},
   208  		{
   209  			"root-and-branch",
   210  			func(t *testing.T, pA, a, b, c *resource.State, snap *deploy.Snapshot) {
   211  				a.Protect = true
   212  				b.Protect = true
   213  				c.Protect = true
   214  				protectedCount := 0
   215  				err := DeleteResource(snap, b, func(s *resource.State) error {
   216  					s.Protect = false
   217  					protectedCount++
   218  					return nil
   219  				}, true)
   220  				assert.NoError(t, err)
   221  				// 2 because we only plan to delete b and c. a is protected but not
   222  				// scheduled for deletion, so we don't call the onProtect handler.
   223  				assert.Equal(t, protectedCount, 2)
   224  				assert.Equal(t, snap.Resources, []*resource.State{pA, a})
   225  
   226  			},
   227  		},
   228  		{
   229  			"branch",
   230  			func(t *testing.T, pA, a, b, c *resource.State, snap *deploy.Snapshot) {
   231  				b.Protect = true
   232  				c.Protect = true
   233  				protectedCount := 0
   234  				err := DeleteResource(snap, c, func(s *resource.State) error {
   235  					s.Protect = false
   236  					protectedCount++
   237  					return nil
   238  				}, false)
   239  				assert.NoError(t, err)
   240  				assert.Equal(t, protectedCount, 1)
   241  				assert.Equal(t, snap.Resources, []*resource.State{pA, a, b})
   242  			},
   243  		},
   244  		{
   245  			"no-permission-root",
   246  			func(t *testing.T, pA, a, b, c *resource.State, snap *deploy.Snapshot) {
   247  				c.Protect = true
   248  				err := DeleteResource(snap, c, nil, false).(ResourceProtectedError)
   249  				assert.Equal(t, ResourceProtectedError{
   250  					Condemned: c,
   251  				}, err)
   252  			},
   253  		},
   254  		{
   255  			"no-permission-branch",
   256  			func(t *testing.T, pA, a, b, c *resource.State, snap *deploy.Snapshot) {
   257  				c.Protect = true
   258  				err := DeleteResource(snap, b, nil, true).(ResourceProtectedError)
   259  				assert.Equal(t, ResourceProtectedError{
   260  					Condemned: c,
   261  				}, err)
   262  			},
   263  		},
   264  	}
   265  	for _, tt := range tests {
   266  		tt := tt
   267  		t.Run(tt.name, func(t *testing.T) {
   268  			t.Parallel()
   269  			pA := NewProviderResource("a", "p1", "0")
   270  			a := NewResource("a", pA)
   271  			b := NewResource("b", pA)
   272  			c := NewResource("c", pA, b.URN)
   273  			snap := NewSnapshot([]*resource.State{
   274  				pA,
   275  				a,
   276  				b,
   277  				c,
   278  			})
   279  
   280  			tt.test(t, pA, a, b, c, snap)
   281  		})
   282  	}
   283  }
   284  
   285  func TestFailedDeletionParentDependency(t *testing.T) {
   286  	t.Parallel()
   287  
   288  	pA := NewProviderResource("a", "p1", "0")
   289  	a := NewResource("a", pA)
   290  	b := NewResource("b", pA)
   291  	b.Parent = a.URN
   292  	c := NewResource("c", pA)
   293  	c.Parent = a.URN
   294  	snap := NewSnapshot([]*resource.State{
   295  		pA,
   296  		a,
   297  		b,
   298  		c,
   299  	})
   300  
   301  	err := DeleteResource(snap, a, nil, false)
   302  	assert.Error(t, err)
   303  	depErr, ok := err.(ResourceHasDependenciesError)
   304  	if !assert.True(t, ok) {
   305  		t.FailNow()
   306  	}
   307  
   308  	assert.NotContains(t, depErr.Dependencies, pA)
   309  	assert.NotContains(t, depErr.Dependencies, a)
   310  	assert.Contains(t, depErr.Dependencies, b)
   311  	assert.Contains(t, depErr.Dependencies, c)
   312  	assert.Len(t, snap.Resources, 4)
   313  	assert.Equal(t, []*resource.State{pA, a, b, c}, snap.Resources)
   314  }
   315  
   316  func TestUnprotectResource(t *testing.T) {
   317  	t.Parallel()
   318  
   319  	pA := NewProviderResource("a", "p1", "0")
   320  	a := NewResource("a", pA)
   321  	a.Protect = true
   322  	b := NewResource("b", pA)
   323  	c := NewResource("c", pA)
   324  	snap := NewSnapshot([]*resource.State{
   325  		pA,
   326  		a,
   327  		b,
   328  		c,
   329  	})
   330  
   331  	err := UnprotectResource(snap, a)
   332  	assert.NoError(t, err)
   333  	assert.Len(t, snap.Resources, 4)
   334  	assert.Equal(t, []*resource.State{pA, a, b, c}, snap.Resources)
   335  	assert.False(t, a.Protect)
   336  }
   337  
   338  func TestLocateResourceNotFound(t *testing.T) {
   339  	t.Parallel()
   340  
   341  	pA := NewProviderResource("a", "p1", "0")
   342  	a := NewResource("a", pA)
   343  	b := NewResource("b", pA)
   344  	c := NewResource("c", pA)
   345  	snap := NewSnapshot([]*resource.State{
   346  		pA,
   347  		a,
   348  		b,
   349  		c,
   350  	})
   351  
   352  	ty := tokens.Type("a:b:c")
   353  	urn := resource.NewURN("test", "test", "", ty, "not-present")
   354  	resList := LocateResource(snap, urn)
   355  	assert.Nil(t, resList)
   356  }
   357  
   358  func TestLocateResourceAmbiguous(t *testing.T) {
   359  	t.Parallel()
   360  
   361  	pA := NewProviderResource("a", "p1", "0")
   362  	a := NewResource("a", pA)
   363  	b := NewResource("b", pA)
   364  	aPending := NewResource("a", pA)
   365  	aPending.Delete = true
   366  	snap := NewSnapshot([]*resource.State{
   367  		pA,
   368  		a,
   369  		b,
   370  		aPending,
   371  	})
   372  
   373  	resList := LocateResource(snap, a.URN)
   374  	assert.Len(t, resList, 2)
   375  	assert.Contains(t, resList, a)
   376  	assert.Contains(t, resList, aPending)
   377  	assert.NotContains(t, resList, pA)
   378  	assert.NotContains(t, resList, b)
   379  }
   380  
   381  func TestLocateResourceExact(t *testing.T) {
   382  	t.Parallel()
   383  
   384  	pA := NewProviderResource("a", "p1", "0")
   385  	a := NewResource("a", pA)
   386  	b := NewResource("b", pA)
   387  	c := NewResource("c", pA)
   388  	snap := NewSnapshot([]*resource.State{
   389  		pA,
   390  		a,
   391  		b,
   392  		c,
   393  	})
   394  
   395  	resList := LocateResource(snap, a.URN)
   396  	assert.Len(t, resList, 1)
   397  	assert.Contains(t, resList, a)
   398  }
   399  
   400  func TestRenameStack(t *testing.T) {
   401  	t.Parallel()
   402  
   403  	pA := NewProviderResource("a", "p1", "0")
   404  	a := NewResource("a", pA)
   405  	b := NewResource("b", pA)
   406  	c := NewResource("c", pA)
   407  	snap := NewSnapshot([]*resource.State{
   408  		pA,
   409  		a,
   410  		b,
   411  		c,
   412  	})
   413  
   414  	// Baseline. Can locate resource A.
   415  	resList := LocateResource(snap, a.URN)
   416  	assert.Len(t, resList, 1)
   417  	assert.Contains(t, resList, a)
   418  	if t.Failed() {
   419  		t.Fatal("Unable to find expected resource in initial snapshot.")
   420  	}
   421  	baselineResourceURN := resList[0].URN
   422  
   423  	// The stack name and project are hard-coded in NewResource(...)
   424  	assert.EqualValues(t, "test", baselineResourceURN.Stack())
   425  	assert.EqualValues(t, "test", baselineResourceURN.Project())
   426  
   427  	// Rename just the stack.
   428  	//nolint:paralleltest // uses shared stack
   429  	t.Run("JustTheStack", func(t *testing.T) {
   430  		err := RenameStack(snap, tokens.Name("new-stack"), tokens.PackageName(""))
   431  		if err != nil {
   432  			t.Fatalf("Error renaming stack: %v", err)
   433  		}
   434  
   435  		// Confirm the previous resource by URN isn't found.
   436  		assert.Len(t, LocateResource(snap, baselineResourceURN), 0)
   437  
   438  		// Confirm the resource has been renamed.
   439  		updatedResourceURN := resource.NewURN(
   440  			tokens.QName("new-stack"),
   441  			"test", // project name stayed the same
   442  			"" /*parent type*/, baselineResourceURN.Type(),
   443  			baselineResourceURN.Name())
   444  		assert.Len(t, LocateResource(snap, updatedResourceURN), 1)
   445  	})
   446  
   447  	// Rename the stack and project.
   448  	//nolint:paralleltest // uses shared stack
   449  	t.Run("StackAndProject", func(t *testing.T) {
   450  		err := RenameStack(snap, tokens.Name("new-stack2"), tokens.PackageName("new-project"))
   451  		if err != nil {
   452  			t.Fatalf("Error renaming stack: %v", err)
   453  		}
   454  
   455  		// Lookup the resource by URN, with both stack and project updated.
   456  		updatedResourceURN := resource.NewURN(
   457  			tokens.QName("new-stack2"),
   458  			"new-project",
   459  			"" /*parent type*/, baselineResourceURN.Type(),
   460  			baselineResourceURN.Name())
   461  		assert.Len(t, LocateResource(snap, updatedResourceURN), 1)
   462  	})
   463  }