github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/command/e2etest/unmanaged_test.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package e2etest 5 6 import ( 7 "context" 8 "encoding/json" 9 "io/ioutil" 10 "path/filepath" 11 "strings" 12 "sync" 13 "testing" 14 15 "github.com/hashicorp/go-hclog" 16 "github.com/hashicorp/go-plugin" 17 "github.com/terramate-io/tf/e2e" 18 "github.com/terramate-io/tf/grpcwrap" 19 tfplugin5 "github.com/terramate-io/tf/plugin" 20 tfplugin "github.com/terramate-io/tf/plugin6" 21 simple5 "github.com/terramate-io/tf/provider-simple" 22 simple "github.com/terramate-io/tf/provider-simple-v6" 23 proto5 "github.com/terramate-io/tf/tfplugin5" 24 proto "github.com/terramate-io/tf/tfplugin6" 25 ) 26 27 // The tests in this file are for the "unmanaged provider workflow", which 28 // includes variants of the following sequence, with different details: 29 // terraform init 30 // terraform plan 31 // terraform apply 32 // 33 // These tests are run against an in-process server, and checked to make sure 34 // they're not trying to control the lifecycle of the binary. They are not 35 // checked for correctness of the operations themselves. 36 37 type reattachConfig struct { 38 Protocol string 39 ProtocolVersion int 40 Pid int 41 Test bool 42 Addr reattachConfigAddr 43 } 44 45 type reattachConfigAddr struct { 46 Network string 47 String string 48 } 49 50 type providerServer struct { 51 sync.Mutex 52 proto.ProviderServer 53 planResourceChangeCalled bool 54 applyResourceChangeCalled bool 55 } 56 57 func (p *providerServer) PlanResourceChange(ctx context.Context, req *proto.PlanResourceChange_Request) (*proto.PlanResourceChange_Response, error) { 58 p.Lock() 59 defer p.Unlock() 60 61 p.planResourceChangeCalled = true 62 return p.ProviderServer.PlanResourceChange(ctx, req) 63 } 64 65 func (p *providerServer) ApplyResourceChange(ctx context.Context, req *proto.ApplyResourceChange_Request) (*proto.ApplyResourceChange_Response, error) { 66 p.Lock() 67 defer p.Unlock() 68 69 p.applyResourceChangeCalled = true 70 return p.ProviderServer.ApplyResourceChange(ctx, req) 71 } 72 73 func (p *providerServer) PlanResourceChangeCalled() bool { 74 p.Lock() 75 defer p.Unlock() 76 77 return p.planResourceChangeCalled 78 } 79 func (p *providerServer) ResetPlanResourceChangeCalled() { 80 p.Lock() 81 defer p.Unlock() 82 83 p.planResourceChangeCalled = false 84 } 85 86 func (p *providerServer) ApplyResourceChangeCalled() bool { 87 p.Lock() 88 defer p.Unlock() 89 90 return p.applyResourceChangeCalled 91 } 92 func (p *providerServer) ResetApplyResourceChangeCalled() { 93 p.Lock() 94 defer p.Unlock() 95 96 p.applyResourceChangeCalled = false 97 } 98 99 type providerServer5 struct { 100 sync.Mutex 101 proto5.ProviderServer 102 planResourceChangeCalled bool 103 applyResourceChangeCalled bool 104 } 105 106 func (p *providerServer5) PlanResourceChange(ctx context.Context, req *proto5.PlanResourceChange_Request) (*proto5.PlanResourceChange_Response, error) { 107 p.Lock() 108 defer p.Unlock() 109 110 p.planResourceChangeCalled = true 111 return p.ProviderServer.PlanResourceChange(ctx, req) 112 } 113 114 func (p *providerServer5) ApplyResourceChange(ctx context.Context, req *proto5.ApplyResourceChange_Request) (*proto5.ApplyResourceChange_Response, error) { 115 p.Lock() 116 defer p.Unlock() 117 118 p.applyResourceChangeCalled = true 119 return p.ProviderServer.ApplyResourceChange(ctx, req) 120 } 121 122 func (p *providerServer5) PlanResourceChangeCalled() bool { 123 p.Lock() 124 defer p.Unlock() 125 126 return p.planResourceChangeCalled 127 } 128 func (p *providerServer5) ResetPlanResourceChangeCalled() { 129 p.Lock() 130 defer p.Unlock() 131 132 p.planResourceChangeCalled = false 133 } 134 135 func (p *providerServer5) ApplyResourceChangeCalled() bool { 136 p.Lock() 137 defer p.Unlock() 138 139 return p.applyResourceChangeCalled 140 } 141 func (p *providerServer5) ResetApplyResourceChangeCalled() { 142 p.Lock() 143 defer p.Unlock() 144 145 p.applyResourceChangeCalled = false 146 } 147 148 func TestUnmanagedSeparatePlan(t *testing.T) { 149 t.Parallel() 150 151 fixturePath := filepath.Join("testdata", "test-provider") 152 tf := e2e.NewBinary(t, terraformBin, fixturePath) 153 154 reattachCh := make(chan *plugin.ReattachConfig) 155 closeCh := make(chan struct{}) 156 provider := &providerServer{ 157 ProviderServer: grpcwrap.Provider6(simple.Provider()), 158 } 159 ctx, cancel := context.WithCancel(context.Background()) 160 defer cancel() 161 go plugin.Serve(&plugin.ServeConfig{ 162 Logger: hclog.New(&hclog.LoggerOptions{ 163 Name: "plugintest", 164 Level: hclog.Trace, 165 Output: ioutil.Discard, 166 }), 167 Test: &plugin.ServeTestConfig{ 168 Context: ctx, 169 ReattachConfigCh: reattachCh, 170 CloseCh: closeCh, 171 }, 172 GRPCServer: plugin.DefaultGRPCServer, 173 VersionedPlugins: map[int]plugin.PluginSet{ 174 6: { 175 "provider": &tfplugin.GRPCProviderPlugin{ 176 GRPCProvider: func() proto.ProviderServer { 177 return provider 178 }, 179 }, 180 }, 181 }, 182 }) 183 config := <-reattachCh 184 if config == nil { 185 t.Fatalf("no reattach config received") 186 } 187 reattachStr, err := json.Marshal(map[string]reattachConfig{ 188 "hashicorp/test": { 189 Protocol: string(config.Protocol), 190 ProtocolVersion: 6, 191 Pid: config.Pid, 192 Test: true, 193 Addr: reattachConfigAddr{ 194 Network: config.Addr.Network(), 195 String: config.Addr.String(), 196 }, 197 }, 198 }) 199 if err != nil { 200 t.Fatal(err) 201 } 202 203 tf.AddEnv("TF_REATTACH_PROVIDERS=" + string(reattachStr)) 204 205 //// INIT 206 stdout, stderr, err := tf.Run("init") 207 if err != nil { 208 t.Fatalf("unexpected init error: %s\nstderr:\n%s", err, stderr) 209 } 210 211 // Make sure we didn't download the binary 212 if strings.Contains(stdout, "Installing hashicorp/test v") { 213 t.Errorf("test provider download message is present in init output:\n%s", stdout) 214 } 215 if tf.FileExists(filepath.Join(".terraform", "plugins", "registry.terraform.io", "hashicorp", "test")) { 216 t.Errorf("test provider binary found in .terraform dir") 217 } 218 219 //// PLAN 220 _, stderr, err = tf.Run("plan", "-out=tfplan") 221 if err != nil { 222 t.Fatalf("unexpected plan error: %s\nstderr:\n%s", err, stderr) 223 } 224 225 if !provider.PlanResourceChangeCalled() { 226 t.Error("PlanResourceChange not called on un-managed provider") 227 } 228 229 //// APPLY 230 _, stderr, err = tf.Run("apply", "tfplan") 231 if err != nil { 232 t.Fatalf("unexpected apply error: %s\nstderr:\n%s", err, stderr) 233 } 234 235 if !provider.ApplyResourceChangeCalled() { 236 t.Error("ApplyResourceChange not called on un-managed provider") 237 } 238 provider.ResetApplyResourceChangeCalled() 239 240 //// DESTROY 241 _, stderr, err = tf.Run("destroy", "-auto-approve") 242 if err != nil { 243 t.Fatalf("unexpected destroy error: %s\nstderr:\n%s", err, stderr) 244 } 245 246 if !provider.ApplyResourceChangeCalled() { 247 t.Error("ApplyResourceChange (destroy) not called on in-process provider") 248 } 249 cancel() 250 <-closeCh 251 } 252 253 func TestUnmanagedSeparatePlan_proto5(t *testing.T) { 254 t.Parallel() 255 256 fixturePath := filepath.Join("testdata", "test-provider") 257 tf := e2e.NewBinary(t, terraformBin, fixturePath) 258 259 reattachCh := make(chan *plugin.ReattachConfig) 260 closeCh := make(chan struct{}) 261 provider := &providerServer5{ 262 ProviderServer: grpcwrap.Provider(simple5.Provider()), 263 } 264 ctx, cancel := context.WithCancel(context.Background()) 265 defer cancel() 266 go plugin.Serve(&plugin.ServeConfig{ 267 Logger: hclog.New(&hclog.LoggerOptions{ 268 Name: "plugintest", 269 Level: hclog.Trace, 270 Output: ioutil.Discard, 271 }), 272 Test: &plugin.ServeTestConfig{ 273 Context: ctx, 274 ReattachConfigCh: reattachCh, 275 CloseCh: closeCh, 276 }, 277 GRPCServer: plugin.DefaultGRPCServer, 278 VersionedPlugins: map[int]plugin.PluginSet{ 279 5: { 280 "provider": &tfplugin5.GRPCProviderPlugin{ 281 GRPCProvider: func() proto5.ProviderServer { 282 return provider 283 }, 284 }, 285 }, 286 }, 287 }) 288 config := <-reattachCh 289 if config == nil { 290 t.Fatalf("no reattach config received") 291 } 292 reattachStr, err := json.Marshal(map[string]reattachConfig{ 293 "hashicorp/test": { 294 Protocol: string(config.Protocol), 295 ProtocolVersion: 5, 296 Pid: config.Pid, 297 Test: true, 298 Addr: reattachConfigAddr{ 299 Network: config.Addr.Network(), 300 String: config.Addr.String(), 301 }, 302 }, 303 }) 304 if err != nil { 305 t.Fatal(err) 306 } 307 308 tf.AddEnv("TF_REATTACH_PROVIDERS=" + string(reattachStr)) 309 310 //// INIT 311 stdout, stderr, err := tf.Run("init") 312 if err != nil { 313 t.Fatalf("unexpected init error: %s\nstderr:\n%s", err, stderr) 314 } 315 316 // Make sure we didn't download the binary 317 if strings.Contains(stdout, "Installing hashicorp/test v") { 318 t.Errorf("test provider download message is present in init output:\n%s", stdout) 319 } 320 if tf.FileExists(filepath.Join(".terraform", "plugins", "registry.terraform.io", "hashicorp", "test")) { 321 t.Errorf("test provider binary found in .terraform dir") 322 } 323 324 //// PLAN 325 _, stderr, err = tf.Run("plan", "-out=tfplan") 326 if err != nil { 327 t.Fatalf("unexpected plan error: %s\nstderr:\n%s", err, stderr) 328 } 329 330 if !provider.PlanResourceChangeCalled() { 331 t.Error("PlanResourceChange not called on un-managed provider") 332 } 333 334 //// APPLY 335 _, stderr, err = tf.Run("apply", "tfplan") 336 if err != nil { 337 t.Fatalf("unexpected apply error: %s\nstderr:\n%s", err, stderr) 338 } 339 340 if !provider.ApplyResourceChangeCalled() { 341 t.Error("ApplyResourceChange not called on un-managed provider") 342 } 343 provider.ResetApplyResourceChangeCalled() 344 345 //// DESTROY 346 _, stderr, err = tf.Run("destroy", "-auto-approve") 347 if err != nil { 348 t.Fatalf("unexpected destroy error: %s\nstderr:\n%s", err, stderr) 349 } 350 351 if !provider.ApplyResourceChangeCalled() { 352 t.Error("ApplyResourceChange (destroy) not called on in-process provider") 353 } 354 cancel() 355 <-closeCh 356 }