sigs.k8s.io/external-dns@v0.14.1/registry/dynamodb_test.go (about) 1 /* 2 Copyright 2023 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package registry 18 19 import ( 20 "context" 21 "strings" 22 "testing" 23 "time" 24 25 "github.com/aws/aws-sdk-go/aws" 26 "github.com/aws/aws-sdk-go/aws/request" 27 "github.com/aws/aws-sdk-go/service/dynamodb" 28 "github.com/stretchr/testify/assert" 29 "github.com/stretchr/testify/require" 30 "k8s.io/apimachinery/pkg/util/sets" 31 "sigs.k8s.io/external-dns/endpoint" 32 "sigs.k8s.io/external-dns/internal/testutils" 33 "sigs.k8s.io/external-dns/plan" 34 "sigs.k8s.io/external-dns/provider" 35 "sigs.k8s.io/external-dns/provider/inmemory" 36 ) 37 38 func TestDynamoDBRegistryNew(t *testing.T) { 39 api, p := newDynamoDBAPIStub(t, nil) 40 41 _, err := NewDynamoDBRegistry(p, "test-owner", api, "test-table", "", "", "", []string{}, []string{}, []byte(""), time.Hour) 42 require.NoError(t, err) 43 44 _, err = NewDynamoDBRegistry(p, "test-owner", api, "test-table", "testPrefix", "", "", []string{}, []string{}, []byte(""), time.Hour) 45 require.NoError(t, err) 46 47 _, err = NewDynamoDBRegistry(p, "test-owner", api, "test-table", "", "testSuffix", "", []string{}, []string{}, []byte(""), time.Hour) 48 require.NoError(t, err) 49 50 _, err = NewDynamoDBRegistry(p, "test-owner", api, "test-table", "", "", "testWildcard", []string{}, []string{}, []byte(""), time.Hour) 51 require.NoError(t, err) 52 53 _, err = NewDynamoDBRegistry(p, "test-owner", api, "test-table", "", "", "testWildcard", []string{}, []string{}, []byte(";k&l)nUC/33:{?d{3)54+,AD?]SX%yh^"), time.Hour) 54 require.NoError(t, err) 55 56 _, err = NewDynamoDBRegistry(p, "", api, "test-table", "", "", "", []string{}, []string{}, []byte(""), time.Hour) 57 require.EqualError(t, err, "owner id cannot be empty") 58 59 _, err = NewDynamoDBRegistry(p, "test-owner", api, "", "", "", "", []string{}, []string{}, []byte(""), time.Hour) 60 require.EqualError(t, err, "table cannot be empty") 61 62 _, err = NewDynamoDBRegistry(p, "test-owner", api, "test-table", "", "", "", []string{}, []string{}, []byte(";k&l)nUC/33:{?d{3)54+,AD?]SX%yh^x"), time.Hour) 63 require.EqualError(t, err, "the AES Encryption key must have a length of 32 bytes") 64 65 _, err = NewDynamoDBRegistry(p, "test-owner", api, "test-table", "testPrefix", "testSuffix", "", []string{}, []string{}, []byte(""), time.Hour) 66 require.EqualError(t, err, "txt-prefix and txt-suffix are mutually exclusive") 67 } 68 69 func TestDynamoDBRegistryRecordsBadTable(t *testing.T) { 70 for _, tc := range []struct { 71 name string 72 setup func(desc *dynamodb.TableDescription) 73 expected string 74 }{ 75 { 76 name: "missing attribute k", 77 setup: func(desc *dynamodb.TableDescription) { 78 desc.AttributeDefinitions[0].AttributeName = aws.String("wrong") 79 }, 80 expected: "table \"test-table\" must have attribute \"k\" of type \"S\"", 81 }, 82 { 83 name: "wrong attribute type", 84 setup: func(desc *dynamodb.TableDescription) { 85 desc.AttributeDefinitions[0].AttributeType = aws.String("SS") 86 }, 87 expected: "table \"test-table\" attribute \"k\" must have type \"S\"", 88 }, 89 { 90 name: "wrong key", 91 setup: func(desc *dynamodb.TableDescription) { 92 desc.KeySchema[0].AttributeName = aws.String("wrong") 93 }, 94 expected: "table \"test-table\" must have hash key \"k\"", 95 }, 96 { 97 name: "has range key", 98 setup: func(desc *dynamodb.TableDescription) { 99 desc.AttributeDefinitions = append(desc.AttributeDefinitions, &dynamodb.AttributeDefinition{ 100 AttributeName: aws.String("o"), 101 AttributeType: aws.String("S"), 102 }) 103 desc.KeySchema = append(desc.KeySchema, &dynamodb.KeySchemaElement{ 104 AttributeName: aws.String("o"), 105 KeyType: aws.String("RANGE"), 106 }) 107 }, 108 expected: "table \"test-table\" must not have a range key", 109 }, 110 } { 111 t.Run(tc.name, func(t *testing.T) { 112 api, p := newDynamoDBAPIStub(t, nil) 113 tc.setup(&api.tableDescription) 114 115 r, _ := NewDynamoDBRegistry(p, "test-owner", api, "test-table", "", "", "", []string{}, []string{}, nil, time.Hour) 116 117 _, err := r.Records(context.Background()) 118 assert.EqualError(t, err, tc.expected) 119 }) 120 } 121 } 122 123 func TestDynamoDBRegistryRecords(t *testing.T) { 124 api, p := newDynamoDBAPIStub(t, nil) 125 126 ctx := context.Background() 127 expectedRecords := []*endpoint.Endpoint{ 128 { 129 DNSName: "foo.test-zone.example.org", 130 Targets: endpoint.Targets{"foo.loadbalancer.com"}, 131 RecordType: endpoint.RecordTypeCNAME, 132 Labels: map[string]string{ 133 endpoint.OwnerLabelKey: "", 134 }, 135 }, 136 { 137 DNSName: "bar.test-zone.example.org", 138 Targets: endpoint.Targets{"my-domain.com"}, 139 RecordType: endpoint.RecordTypeCNAME, 140 Labels: map[string]string{ 141 endpoint.OwnerLabelKey: "test-owner", 142 endpoint.ResourceLabelKey: "ingress/default/my-ingress", 143 }, 144 }, 145 { 146 DNSName: "baz.test-zone.example.org", 147 Targets: endpoint.Targets{"1.1.1.1"}, 148 RecordType: endpoint.RecordTypeA, 149 SetIdentifier: "set-1", 150 Labels: map[string]string{ 151 endpoint.OwnerLabelKey: "test-owner", 152 endpoint.ResourceLabelKey: "ingress/default/my-ingress", 153 }, 154 }, 155 { 156 DNSName: "baz.test-zone.example.org", 157 Targets: endpoint.Targets{"2.2.2.2"}, 158 RecordType: endpoint.RecordTypeA, 159 SetIdentifier: "set-2", 160 Labels: map[string]string{ 161 endpoint.OwnerLabelKey: "test-owner", 162 endpoint.ResourceLabelKey: "ingress/default/other-ingress", 163 }, 164 }, 165 { 166 DNSName: "migrate.test-zone.example.org", 167 Targets: endpoint.Targets{"3.3.3.3"}, 168 RecordType: endpoint.RecordTypeA, 169 SetIdentifier: "set-3", 170 Labels: map[string]string{ 171 endpoint.OwnerLabelKey: "test-owner", 172 endpoint.ResourceLabelKey: "ingress/default/other-ingress", 173 }, 174 ProviderSpecific: endpoint.ProviderSpecific{ 175 { 176 Name: dynamodbAttributeMigrate, 177 Value: "true", 178 }, 179 }, 180 }, 181 { 182 DNSName: "txt.orphaned.test-zone.example.org", 183 Targets: endpoint.Targets{"\"heritage=external-dns,external-dns/owner=test-owner,external-dns/resource=ingress/default/other-ingress\""}, 184 RecordType: endpoint.RecordTypeTXT, 185 SetIdentifier: "set-3", 186 Labels: map[string]string{ 187 endpoint.OwnerLabelKey: "test-owner", 188 }, 189 }, 190 { 191 DNSName: "txt.baz.test-zone.example.org", 192 Targets: endpoint.Targets{"\"heritage=external-dns,external-dns/owner=test-owner,external-dns/resource=ingress/default/other-ingress\""}, 193 RecordType: endpoint.RecordTypeTXT, 194 SetIdentifier: "set-2", 195 Labels: map[string]string{ 196 endpoint.OwnerLabelKey: "test-owner", 197 }, 198 }, 199 } 200 201 r, _ := NewDynamoDBRegistry(p, "test-owner", api, "test-table", "txt.", "", "", []string{}, []string{}, nil, time.Hour) 202 _ = p.(*wrappedProvider).Provider.ApplyChanges(context.Background(), &plan.Changes{ 203 Create: []*endpoint.Endpoint{ 204 endpoint.NewEndpoint("migrate.test-zone.example.org", endpoint.RecordTypeA, "3.3.3.3").WithSetIdentifier("set-3"), 205 endpoint.NewEndpoint("txt.migrate.test-zone.example.org", endpoint.RecordTypeTXT, "\"heritage=external-dns,external-dns/owner=test-owner,external-dns/resource=ingress/default/other-ingress\"").WithSetIdentifier("set-3"), 206 endpoint.NewEndpoint("txt.orphaned.test-zone.example.org", endpoint.RecordTypeTXT, "\"heritage=external-dns,external-dns/owner=test-owner,external-dns/resource=ingress/default/other-ingress\"").WithSetIdentifier("set-3"), 207 endpoint.NewEndpoint("txt.baz.test-zone.example.org", endpoint.RecordTypeTXT, "\"heritage=external-dns,external-dns/owner=test-owner,external-dns/resource=ingress/default/other-ingress\"").WithSetIdentifier("set-2"), 208 }, 209 }) 210 211 records, err := r.Records(ctx) 212 require.Nil(t, err) 213 214 assert.True(t, testutils.SameEndpoints(records, expectedRecords)) 215 } 216 217 func TestDynamoDBRegistryApplyChanges(t *testing.T) { 218 for _, tc := range []struct { 219 name string 220 maxBatchSize uint8 221 stubConfig DynamoDBStubConfig 222 addRecords []*endpoint.Endpoint 223 changes plan.Changes 224 expectedError string 225 expectedRecords []*endpoint.Endpoint 226 }{ 227 { 228 name: "create", 229 changes: plan.Changes{ 230 Create: []*endpoint.Endpoint{ 231 { 232 DNSName: "new.test-zone.example.org", 233 Targets: endpoint.Targets{"new.loadbalancer.com"}, 234 RecordType: endpoint.RecordTypeCNAME, 235 SetIdentifier: "set-new", 236 Labels: map[string]string{ 237 endpoint.ResourceLabelKey: "ingress/default/new-ingress", 238 }, 239 }, 240 }, 241 }, 242 stubConfig: DynamoDBStubConfig{ 243 ExpectInsert: map[string]map[string]string{ 244 "new.test-zone.example.org#CNAME#set-new": {endpoint.ResourceLabelKey: "ingress/default/new-ingress"}, 245 }, 246 ExpectDelete: sets.New("quux.test-zone.example.org#A#set-2"), 247 }, 248 expectedRecords: []*endpoint.Endpoint{ 249 { 250 DNSName: "foo.test-zone.example.org", 251 Targets: endpoint.Targets{"foo.loadbalancer.com"}, 252 RecordType: endpoint.RecordTypeCNAME, 253 Labels: map[string]string{ 254 endpoint.OwnerLabelKey: "", 255 }, 256 }, 257 { 258 DNSName: "bar.test-zone.example.org", 259 Targets: endpoint.Targets{"my-domain.com"}, 260 RecordType: endpoint.RecordTypeCNAME, 261 Labels: map[string]string{ 262 endpoint.OwnerLabelKey: "test-owner", 263 endpoint.ResourceLabelKey: "ingress/default/my-ingress", 264 }, 265 }, 266 { 267 DNSName: "baz.test-zone.example.org", 268 Targets: endpoint.Targets{"1.1.1.1"}, 269 RecordType: endpoint.RecordTypeA, 270 SetIdentifier: "set-1", 271 Labels: map[string]string{ 272 endpoint.OwnerLabelKey: "test-owner", 273 endpoint.ResourceLabelKey: "ingress/default/my-ingress", 274 }, 275 }, 276 { 277 DNSName: "baz.test-zone.example.org", 278 Targets: endpoint.Targets{"2.2.2.2"}, 279 RecordType: endpoint.RecordTypeA, 280 SetIdentifier: "set-2", 281 Labels: map[string]string{ 282 endpoint.OwnerLabelKey: "test-owner", 283 endpoint.ResourceLabelKey: "ingress/default/other-ingress", 284 }, 285 }, 286 { 287 DNSName: "new.test-zone.example.org", 288 Targets: endpoint.Targets{"new.loadbalancer.com"}, 289 RecordType: endpoint.RecordTypeCNAME, 290 SetIdentifier: "set-new", 291 Labels: map[string]string{ 292 endpoint.OwnerLabelKey: "test-owner", 293 endpoint.ResourceLabelKey: "ingress/default/new-ingress", 294 }, 295 }, 296 }, 297 }, 298 { 299 name: "create more entries than DynamoDB batch size limit", 300 maxBatchSize: 2, 301 changes: plan.Changes{ 302 Create: []*endpoint.Endpoint{ 303 { 304 DNSName: "new1.test-zone.example.org", 305 Targets: endpoint.Targets{"new1.loadbalancer.com"}, 306 RecordType: endpoint.RecordTypeCNAME, 307 SetIdentifier: "set-new", 308 Labels: map[string]string{ 309 endpoint.ResourceLabelKey: "ingress/default/new1-ingress", 310 }, 311 }, 312 { 313 DNSName: "new2.test-zone.example.org", 314 Targets: endpoint.Targets{"new2.loadbalancer.com"}, 315 RecordType: endpoint.RecordTypeCNAME, 316 SetIdentifier: "set-new", 317 Labels: map[string]string{ 318 endpoint.ResourceLabelKey: "ingress/default/new2-ingress", 319 }, 320 }, 321 { 322 DNSName: "new3.test-zone.example.org", 323 Targets: endpoint.Targets{"new3.loadbalancer.com"}, 324 RecordType: endpoint.RecordTypeCNAME, 325 SetIdentifier: "set-new", 326 Labels: map[string]string{ 327 endpoint.ResourceLabelKey: "ingress/default/new3-ingress", 328 }, 329 }, 330 }, 331 }, 332 stubConfig: DynamoDBStubConfig{ 333 ExpectInsert: map[string]map[string]string{ 334 "new1.test-zone.example.org#CNAME#set-new": {endpoint.ResourceLabelKey: "ingress/default/new1-ingress"}, 335 "new2.test-zone.example.org#CNAME#set-new": {endpoint.ResourceLabelKey: "ingress/default/new2-ingress"}, 336 "new3.test-zone.example.org#CNAME#set-new": {endpoint.ResourceLabelKey: "ingress/default/new3-ingress"}, 337 }, 338 ExpectDelete: sets.New("quux.test-zone.example.org#A#set-2"), 339 }, 340 expectedRecords: []*endpoint.Endpoint{ 341 { 342 DNSName: "foo.test-zone.example.org", 343 Targets: endpoint.Targets{"foo.loadbalancer.com"}, 344 RecordType: endpoint.RecordTypeCNAME, 345 Labels: map[string]string{ 346 endpoint.OwnerLabelKey: "", 347 }, 348 }, 349 { 350 DNSName: "bar.test-zone.example.org", 351 Targets: endpoint.Targets{"my-domain.com"}, 352 RecordType: endpoint.RecordTypeCNAME, 353 Labels: map[string]string{ 354 endpoint.OwnerLabelKey: "test-owner", 355 endpoint.ResourceLabelKey: "ingress/default/my-ingress", 356 }, 357 }, 358 { 359 DNSName: "baz.test-zone.example.org", 360 Targets: endpoint.Targets{"1.1.1.1"}, 361 RecordType: endpoint.RecordTypeA, 362 SetIdentifier: "set-1", 363 Labels: map[string]string{ 364 endpoint.OwnerLabelKey: "test-owner", 365 endpoint.ResourceLabelKey: "ingress/default/my-ingress", 366 }, 367 }, 368 { 369 DNSName: "baz.test-zone.example.org", 370 Targets: endpoint.Targets{"2.2.2.2"}, 371 RecordType: endpoint.RecordTypeA, 372 SetIdentifier: "set-2", 373 Labels: map[string]string{ 374 endpoint.OwnerLabelKey: "test-owner", 375 endpoint.ResourceLabelKey: "ingress/default/other-ingress", 376 }, 377 }, 378 { 379 DNSName: "new1.test-zone.example.org", 380 Targets: endpoint.Targets{"new1.loadbalancer.com"}, 381 RecordType: endpoint.RecordTypeCNAME, 382 SetIdentifier: "set-new", 383 Labels: map[string]string{ 384 endpoint.OwnerLabelKey: "test-owner", 385 endpoint.ResourceLabelKey: "ingress/default/new1-ingress", 386 }, 387 }, 388 { 389 DNSName: "new2.test-zone.example.org", 390 Targets: endpoint.Targets{"new2.loadbalancer.com"}, 391 RecordType: endpoint.RecordTypeCNAME, 392 SetIdentifier: "set-new", 393 Labels: map[string]string{ 394 endpoint.OwnerLabelKey: "test-owner", 395 endpoint.ResourceLabelKey: "ingress/default/new2-ingress", 396 }, 397 }, 398 { 399 DNSName: "new3.test-zone.example.org", 400 Targets: endpoint.Targets{"new3.loadbalancer.com"}, 401 RecordType: endpoint.RecordTypeCNAME, 402 SetIdentifier: "set-new", 403 Labels: map[string]string{ 404 endpoint.OwnerLabelKey: "test-owner", 405 endpoint.ResourceLabelKey: "ingress/default/new3-ingress", 406 }, 407 }, 408 }, 409 }, 410 { 411 name: "create orphaned", 412 changes: plan.Changes{ 413 Create: []*endpoint.Endpoint{ 414 { 415 DNSName: "quux.test-zone.example.org", 416 Targets: endpoint.Targets{"5.5.5.5"}, 417 RecordType: endpoint.RecordTypeA, 418 SetIdentifier: "set-2", 419 Labels: map[string]string{ 420 endpoint.ResourceLabelKey: "ingress/default/quux-ingress", 421 }, 422 }, 423 }, 424 }, 425 stubConfig: DynamoDBStubConfig{}, 426 expectedRecords: []*endpoint.Endpoint{ 427 { 428 DNSName: "foo.test-zone.example.org", 429 Targets: endpoint.Targets{"foo.loadbalancer.com"}, 430 RecordType: endpoint.RecordTypeCNAME, 431 Labels: map[string]string{ 432 endpoint.OwnerLabelKey: "", 433 }, 434 }, 435 { 436 DNSName: "bar.test-zone.example.org", 437 Targets: endpoint.Targets{"my-domain.com"}, 438 RecordType: endpoint.RecordTypeCNAME, 439 Labels: map[string]string{ 440 endpoint.OwnerLabelKey: "test-owner", 441 endpoint.ResourceLabelKey: "ingress/default/my-ingress", 442 }, 443 }, 444 { 445 DNSName: "baz.test-zone.example.org", 446 Targets: endpoint.Targets{"1.1.1.1"}, 447 RecordType: endpoint.RecordTypeA, 448 SetIdentifier: "set-1", 449 Labels: map[string]string{ 450 endpoint.OwnerLabelKey: "test-owner", 451 endpoint.ResourceLabelKey: "ingress/default/my-ingress", 452 }, 453 }, 454 { 455 DNSName: "baz.test-zone.example.org", 456 Targets: endpoint.Targets{"2.2.2.2"}, 457 RecordType: endpoint.RecordTypeA, 458 SetIdentifier: "set-2", 459 Labels: map[string]string{ 460 endpoint.OwnerLabelKey: "test-owner", 461 endpoint.ResourceLabelKey: "ingress/default/other-ingress", 462 }, 463 }, 464 { 465 DNSName: "quux.test-zone.example.org", 466 Targets: endpoint.Targets{"5.5.5.5"}, 467 RecordType: endpoint.RecordTypeA, 468 SetIdentifier: "set-2", 469 Labels: map[string]string{ 470 endpoint.OwnerLabelKey: "test-owner", 471 endpoint.ResourceLabelKey: "ingress/default/quux-ingress", 472 }, 473 }, 474 }, 475 }, 476 { 477 name: "create orphaned change", 478 changes: plan.Changes{ 479 Create: []*endpoint.Endpoint{ 480 { 481 DNSName: "quux.test-zone.example.org", 482 Targets: endpoint.Targets{"5.5.5.5"}, 483 RecordType: endpoint.RecordTypeA, 484 SetIdentifier: "set-2", 485 Labels: map[string]string{ 486 endpoint.ResourceLabelKey: "ingress/default/new-ingress", 487 }, 488 }, 489 }, 490 }, 491 stubConfig: DynamoDBStubConfig{ 492 ExpectUpdate: map[string]map[string]string{ 493 "quux.test-zone.example.org#A#set-2": {endpoint.ResourceLabelKey: "ingress/default/new-ingress"}, 494 }, 495 }, 496 expectedRecords: []*endpoint.Endpoint{ 497 { 498 DNSName: "foo.test-zone.example.org", 499 Targets: endpoint.Targets{"foo.loadbalancer.com"}, 500 RecordType: endpoint.RecordTypeCNAME, 501 Labels: map[string]string{ 502 endpoint.OwnerLabelKey: "", 503 }, 504 }, 505 { 506 DNSName: "bar.test-zone.example.org", 507 Targets: endpoint.Targets{"my-domain.com"}, 508 RecordType: endpoint.RecordTypeCNAME, 509 Labels: map[string]string{ 510 endpoint.OwnerLabelKey: "test-owner", 511 endpoint.ResourceLabelKey: "ingress/default/my-ingress", 512 }, 513 }, 514 { 515 DNSName: "baz.test-zone.example.org", 516 Targets: endpoint.Targets{"1.1.1.1"}, 517 RecordType: endpoint.RecordTypeA, 518 SetIdentifier: "set-1", 519 Labels: map[string]string{ 520 endpoint.OwnerLabelKey: "test-owner", 521 endpoint.ResourceLabelKey: "ingress/default/my-ingress", 522 }, 523 }, 524 { 525 DNSName: "baz.test-zone.example.org", 526 Targets: endpoint.Targets{"2.2.2.2"}, 527 RecordType: endpoint.RecordTypeA, 528 SetIdentifier: "set-2", 529 Labels: map[string]string{ 530 endpoint.OwnerLabelKey: "test-owner", 531 endpoint.ResourceLabelKey: "ingress/default/other-ingress", 532 }, 533 }, 534 { 535 DNSName: "quux.test-zone.example.org", 536 Targets: endpoint.Targets{"5.5.5.5"}, 537 RecordType: endpoint.RecordTypeA, 538 SetIdentifier: "set-2", 539 Labels: map[string]string{ 540 endpoint.OwnerLabelKey: "test-owner", 541 endpoint.ResourceLabelKey: "ingress/default/new-ingress", 542 }, 543 }, 544 }, 545 }, 546 { 547 name: "create duplicate", 548 changes: plan.Changes{ 549 Create: []*endpoint.Endpoint{ 550 { 551 DNSName: "new.test-zone.example.org", 552 Targets: endpoint.Targets{"new.loadbalancer.com"}, 553 RecordType: endpoint.RecordTypeCNAME, 554 SetIdentifier: "set-new", 555 Labels: map[string]string{ 556 endpoint.ResourceLabelKey: "ingress/default/new-ingress", 557 }, 558 }, 559 }, 560 }, 561 stubConfig: DynamoDBStubConfig{ 562 ExpectInsertError: map[string]string{ 563 "new.test-zone.example.org#CNAME#set-new": "DuplicateItem", 564 }, 565 ExpectDelete: sets.New("quux.test-zone.example.org#A#set-2"), 566 }, 567 expectedRecords: []*endpoint.Endpoint{ 568 { 569 DNSName: "foo.test-zone.example.org", 570 Targets: endpoint.Targets{"foo.loadbalancer.com"}, 571 RecordType: endpoint.RecordTypeCNAME, 572 Labels: map[string]string{ 573 endpoint.OwnerLabelKey: "", 574 }, 575 }, 576 { 577 DNSName: "bar.test-zone.example.org", 578 Targets: endpoint.Targets{"my-domain.com"}, 579 RecordType: endpoint.RecordTypeCNAME, 580 Labels: map[string]string{ 581 endpoint.OwnerLabelKey: "test-owner", 582 endpoint.ResourceLabelKey: "ingress/default/my-ingress", 583 }, 584 }, 585 { 586 DNSName: "baz.test-zone.example.org", 587 Targets: endpoint.Targets{"1.1.1.1"}, 588 RecordType: endpoint.RecordTypeA, 589 SetIdentifier: "set-1", 590 Labels: map[string]string{ 591 endpoint.OwnerLabelKey: "test-owner", 592 endpoint.ResourceLabelKey: "ingress/default/my-ingress", 593 }, 594 }, 595 { 596 DNSName: "baz.test-zone.example.org", 597 Targets: endpoint.Targets{"2.2.2.2"}, 598 RecordType: endpoint.RecordTypeA, 599 SetIdentifier: "set-2", 600 Labels: map[string]string{ 601 endpoint.OwnerLabelKey: "test-owner", 602 endpoint.ResourceLabelKey: "ingress/default/other-ingress", 603 }, 604 }, 605 }, 606 }, 607 { 608 name: "create error", 609 changes: plan.Changes{ 610 Create: []*endpoint.Endpoint{ 611 { 612 DNSName: "new.test-zone.example.org", 613 Targets: endpoint.Targets{"new.loadbalancer.com"}, 614 RecordType: endpoint.RecordTypeCNAME, 615 SetIdentifier: "set-new", 616 Labels: map[string]string{ 617 endpoint.ResourceLabelKey: "ingress/default/new-ingress", 618 }, 619 }, 620 }, 621 }, 622 stubConfig: DynamoDBStubConfig{ 623 ExpectInsertError: map[string]string{ 624 "new.test-zone.example.org#CNAME#set-new": "TestingError", 625 }, 626 }, 627 expectedError: "inserting dynamodb record \"new.test-zone.example.org#CNAME#set-new\": TestingError: testing error", 628 expectedRecords: []*endpoint.Endpoint{ 629 { 630 DNSName: "foo.test-zone.example.org", 631 Targets: endpoint.Targets{"foo.loadbalancer.com"}, 632 RecordType: endpoint.RecordTypeCNAME, 633 Labels: map[string]string{ 634 endpoint.OwnerLabelKey: "", 635 }, 636 }, 637 { 638 DNSName: "bar.test-zone.example.org", 639 Targets: endpoint.Targets{"my-domain.com"}, 640 RecordType: endpoint.RecordTypeCNAME, 641 Labels: map[string]string{ 642 endpoint.OwnerLabelKey: "test-owner", 643 endpoint.ResourceLabelKey: "ingress/default/my-ingress", 644 }, 645 }, 646 { 647 DNSName: "baz.test-zone.example.org", 648 Targets: endpoint.Targets{"1.1.1.1"}, 649 RecordType: endpoint.RecordTypeA, 650 SetIdentifier: "set-1", 651 Labels: map[string]string{ 652 endpoint.OwnerLabelKey: "test-owner", 653 endpoint.ResourceLabelKey: "ingress/default/my-ingress", 654 }, 655 }, 656 { 657 DNSName: "baz.test-zone.example.org", 658 Targets: endpoint.Targets{"2.2.2.2"}, 659 RecordType: endpoint.RecordTypeA, 660 SetIdentifier: "set-2", 661 Labels: map[string]string{ 662 endpoint.OwnerLabelKey: "test-owner", 663 endpoint.ResourceLabelKey: "ingress/default/other-ingress", 664 }, 665 }, 666 }, 667 }, 668 { 669 name: "update", 670 changes: plan.Changes{ 671 UpdateOld: []*endpoint.Endpoint{ 672 { 673 DNSName: "bar.test-zone.example.org", 674 Targets: endpoint.Targets{"my-domain.com"}, 675 RecordType: endpoint.RecordTypeCNAME, 676 Labels: map[string]string{ 677 endpoint.OwnerLabelKey: "test-owner", 678 endpoint.ResourceLabelKey: "ingress/default/my-ingress", 679 }, 680 }, 681 }, 682 UpdateNew: []*endpoint.Endpoint{ 683 { 684 DNSName: "bar.test-zone.example.org", 685 Targets: endpoint.Targets{"new-domain.com"}, 686 RecordType: endpoint.RecordTypeCNAME, 687 Labels: map[string]string{ 688 endpoint.OwnerLabelKey: "test-owner", 689 endpoint.ResourceLabelKey: "ingress/default/my-ingress", 690 }, 691 }, 692 }, 693 }, 694 stubConfig: DynamoDBStubConfig{ 695 ExpectDelete: sets.New("quux.test-zone.example.org#A#set-2"), 696 }, 697 expectedRecords: []*endpoint.Endpoint{ 698 { 699 DNSName: "foo.test-zone.example.org", 700 Targets: endpoint.Targets{"foo.loadbalancer.com"}, 701 RecordType: endpoint.RecordTypeCNAME, 702 Labels: map[string]string{ 703 endpoint.OwnerLabelKey: "", 704 }, 705 }, 706 { 707 DNSName: "bar.test-zone.example.org", 708 Targets: endpoint.Targets{"new-domain.com"}, 709 RecordType: endpoint.RecordTypeCNAME, 710 Labels: map[string]string{ 711 endpoint.OwnerLabelKey: "test-owner", 712 endpoint.ResourceLabelKey: "ingress/default/my-ingress", 713 }, 714 }, 715 { 716 DNSName: "baz.test-zone.example.org", 717 Targets: endpoint.Targets{"1.1.1.1"}, 718 RecordType: endpoint.RecordTypeA, 719 SetIdentifier: "set-1", 720 Labels: map[string]string{ 721 endpoint.OwnerLabelKey: "test-owner", 722 endpoint.ResourceLabelKey: "ingress/default/my-ingress", 723 }, 724 }, 725 { 726 DNSName: "baz.test-zone.example.org", 727 Targets: endpoint.Targets{"2.2.2.2"}, 728 RecordType: endpoint.RecordTypeA, 729 SetIdentifier: "set-2", 730 Labels: map[string]string{ 731 endpoint.OwnerLabelKey: "test-owner", 732 endpoint.ResourceLabelKey: "ingress/default/other-ingress", 733 }, 734 }, 735 }, 736 }, 737 { 738 name: "update change", 739 changes: plan.Changes{ 740 UpdateOld: []*endpoint.Endpoint{ 741 { 742 DNSName: "bar.test-zone.example.org", 743 Targets: endpoint.Targets{"my-domain.com"}, 744 RecordType: endpoint.RecordTypeCNAME, 745 Labels: map[string]string{ 746 endpoint.OwnerLabelKey: "test-owner", 747 endpoint.ResourceLabelKey: "ingress/default/my-ingress", 748 }, 749 }, 750 }, 751 UpdateNew: []*endpoint.Endpoint{ 752 { 753 DNSName: "bar.test-zone.example.org", 754 Targets: endpoint.Targets{"new-domain.com"}, 755 RecordType: endpoint.RecordTypeCNAME, 756 Labels: map[string]string{ 757 endpoint.OwnerLabelKey: "test-owner", 758 endpoint.ResourceLabelKey: "ingress/default/new-ingress", 759 }, 760 }, 761 }, 762 }, 763 stubConfig: DynamoDBStubConfig{ 764 ExpectDelete: sets.New("quux.test-zone.example.org#A#set-2"), 765 ExpectUpdate: map[string]map[string]string{ 766 "bar.test-zone.example.org#CNAME#": {endpoint.ResourceLabelKey: "ingress/default/new-ingress"}, 767 }, 768 }, 769 expectedRecords: []*endpoint.Endpoint{ 770 { 771 DNSName: "foo.test-zone.example.org", 772 Targets: endpoint.Targets{"foo.loadbalancer.com"}, 773 RecordType: endpoint.RecordTypeCNAME, 774 Labels: map[string]string{ 775 endpoint.OwnerLabelKey: "", 776 }, 777 }, 778 { 779 DNSName: "bar.test-zone.example.org", 780 Targets: endpoint.Targets{"new-domain.com"}, 781 RecordType: endpoint.RecordTypeCNAME, 782 Labels: map[string]string{ 783 endpoint.OwnerLabelKey: "test-owner", 784 endpoint.ResourceLabelKey: "ingress/default/new-ingress", 785 }, 786 }, 787 { 788 DNSName: "baz.test-zone.example.org", 789 Targets: endpoint.Targets{"1.1.1.1"}, 790 RecordType: endpoint.RecordTypeA, 791 SetIdentifier: "set-1", 792 Labels: map[string]string{ 793 endpoint.OwnerLabelKey: "test-owner", 794 endpoint.ResourceLabelKey: "ingress/default/my-ingress", 795 }, 796 }, 797 { 798 DNSName: "baz.test-zone.example.org", 799 Targets: endpoint.Targets{"2.2.2.2"}, 800 RecordType: endpoint.RecordTypeA, 801 SetIdentifier: "set-2", 802 Labels: map[string]string{ 803 endpoint.OwnerLabelKey: "test-owner", 804 endpoint.ResourceLabelKey: "ingress/default/other-ingress", 805 }, 806 }, 807 }, 808 }, 809 { 810 name: "update migrate", 811 addRecords: []*endpoint.Endpoint{ 812 { 813 DNSName: "txt.bar.test-zone.example.org", 814 Targets: endpoint.Targets{"\"heritage=external-dns,external-dns/owner=test-owner,external-dns/resource=ingress/default/new-ingress\""}, 815 RecordType: endpoint.RecordTypeTXT, 816 SetIdentifier: "set-1", 817 }, 818 }, 819 changes: plan.Changes{ 820 UpdateOld: []*endpoint.Endpoint{ 821 { 822 DNSName: "bar.test-zone.example.org", 823 Targets: endpoint.Targets{"my-domain.com"}, 824 RecordType: endpoint.RecordTypeCNAME, 825 Labels: map[string]string{ 826 endpoint.OwnerLabelKey: "test-owner", 827 endpoint.ResourceLabelKey: "ingress/default/my-ingress", 828 }, 829 ProviderSpecific: endpoint.ProviderSpecific{ 830 { 831 Name: dynamodbAttributeMigrate, 832 Value: "true", 833 }, 834 }, 835 }, 836 }, 837 UpdateNew: []*endpoint.Endpoint{ 838 { 839 DNSName: "bar.test-zone.example.org", 840 Targets: endpoint.Targets{"my-domain.com"}, 841 RecordType: endpoint.RecordTypeCNAME, 842 Labels: map[string]string{ 843 endpoint.OwnerLabelKey: "test-owner", 844 endpoint.ResourceLabelKey: "ingress/default/new-ingress", 845 }, 846 }, 847 }, 848 }, 849 stubConfig: DynamoDBStubConfig{ 850 ExpectDelete: sets.New("quux.test-zone.example.org#A#set-2"), 851 ExpectInsert: map[string]map[string]string{ 852 "bar.test-zone.example.org#CNAME#": {endpoint.ResourceLabelKey: "ingress/default/new-ingress"}, 853 }, 854 }, 855 expectedRecords: []*endpoint.Endpoint{ 856 { 857 DNSName: "foo.test-zone.example.org", 858 Targets: endpoint.Targets{"foo.loadbalancer.com"}, 859 RecordType: endpoint.RecordTypeCNAME, 860 Labels: map[string]string{ 861 endpoint.OwnerLabelKey: "", 862 }, 863 }, 864 { 865 DNSName: "bar.test-zone.example.org", 866 Targets: endpoint.Targets{"my-domain.com"}, 867 RecordType: endpoint.RecordTypeCNAME, 868 Labels: map[string]string{ 869 endpoint.OwnerLabelKey: "test-owner", 870 endpoint.ResourceLabelKey: "ingress/default/new-ingress", 871 }, 872 }, 873 { 874 DNSName: "baz.test-zone.example.org", 875 Targets: endpoint.Targets{"1.1.1.1"}, 876 RecordType: endpoint.RecordTypeA, 877 SetIdentifier: "set-1", 878 Labels: map[string]string{ 879 endpoint.OwnerLabelKey: "test-owner", 880 endpoint.ResourceLabelKey: "ingress/default/my-ingress", 881 }, 882 }, 883 { 884 DNSName: "baz.test-zone.example.org", 885 Targets: endpoint.Targets{"2.2.2.2"}, 886 RecordType: endpoint.RecordTypeA, 887 SetIdentifier: "set-2", 888 Labels: map[string]string{ 889 endpoint.OwnerLabelKey: "test-owner", 890 endpoint.ResourceLabelKey: "ingress/default/other-ingress", 891 }, 892 }, 893 { 894 DNSName: "txt.bar.test-zone.example.org", 895 Targets: endpoint.Targets{"\"heritage=external-dns,external-dns/owner=test-owner,external-dns/resource=ingress/default/new-ingress\""}, 896 RecordType: endpoint.RecordTypeTXT, 897 SetIdentifier: "set-1", 898 Labels: map[string]string{ 899 endpoint.OwnerLabelKey: "test-owner", 900 }, 901 }, 902 }, 903 }, 904 { 905 name: "update error", 906 changes: plan.Changes{ 907 UpdateOld: []*endpoint.Endpoint{ 908 { 909 DNSName: "bar.test-zone.example.org", 910 Targets: endpoint.Targets{"my-domain.com"}, 911 RecordType: endpoint.RecordTypeCNAME, 912 Labels: map[string]string{ 913 endpoint.OwnerLabelKey: "test-owner", 914 endpoint.ResourceLabelKey: "ingress/default/my-ingress", 915 }, 916 }, 917 }, 918 UpdateNew: []*endpoint.Endpoint{ 919 { 920 DNSName: "bar.test-zone.example.org", 921 Targets: endpoint.Targets{"new-domain.com"}, 922 RecordType: endpoint.RecordTypeCNAME, 923 Labels: map[string]string{ 924 endpoint.OwnerLabelKey: "test-owner", 925 endpoint.ResourceLabelKey: "ingress/default/new-ingress", 926 }, 927 }, 928 }, 929 }, 930 stubConfig: DynamoDBStubConfig{ 931 ExpectUpdateError: map[string]string{ 932 "bar.test-zone.example.org#CNAME#": "TestingError", 933 }, 934 }, 935 expectedError: "updating dynamodb record \"bar.test-zone.example.org#CNAME#\": TestingError: testing error", 936 expectedRecords: []*endpoint.Endpoint{ 937 { 938 DNSName: "foo.test-zone.example.org", 939 Targets: endpoint.Targets{"foo.loadbalancer.com"}, 940 RecordType: endpoint.RecordTypeCNAME, 941 Labels: map[string]string{ 942 endpoint.OwnerLabelKey: "", 943 }, 944 }, 945 { 946 DNSName: "bar.test-zone.example.org", 947 Targets: endpoint.Targets{"my-domain.com"}, 948 RecordType: endpoint.RecordTypeCNAME, 949 Labels: map[string]string{ 950 endpoint.OwnerLabelKey: "test-owner", 951 endpoint.ResourceLabelKey: "ingress/default/my-ingress", 952 }, 953 }, 954 { 955 DNSName: "baz.test-zone.example.org", 956 Targets: endpoint.Targets{"1.1.1.1"}, 957 RecordType: endpoint.RecordTypeA, 958 SetIdentifier: "set-1", 959 Labels: map[string]string{ 960 endpoint.OwnerLabelKey: "test-owner", 961 endpoint.ResourceLabelKey: "ingress/default/my-ingress", 962 }, 963 }, 964 { 965 DNSName: "baz.test-zone.example.org", 966 Targets: endpoint.Targets{"2.2.2.2"}, 967 RecordType: endpoint.RecordTypeA, 968 SetIdentifier: "set-2", 969 Labels: map[string]string{ 970 endpoint.OwnerLabelKey: "test-owner", 971 endpoint.ResourceLabelKey: "ingress/default/other-ingress", 972 }, 973 }, 974 }, 975 }, 976 { 977 name: "delete", 978 changes: plan.Changes{ 979 Delete: []*endpoint.Endpoint{ 980 { 981 DNSName: "bar.test-zone.example.org", 982 Targets: endpoint.Targets{"my-domain.com"}, 983 RecordType: endpoint.RecordTypeCNAME, 984 Labels: map[string]string{ 985 endpoint.OwnerLabelKey: "test-owner", 986 endpoint.ResourceLabelKey: "ingress/default/my-ingress", 987 }, 988 }, 989 }, 990 }, 991 stubConfig: DynamoDBStubConfig{ 992 ExpectDelete: sets.New("bar.test-zone.example.org#CNAME#", "quux.test-zone.example.org#A#set-2"), 993 }, 994 expectedRecords: []*endpoint.Endpoint{ 995 { 996 DNSName: "foo.test-zone.example.org", 997 Targets: endpoint.Targets{"foo.loadbalancer.com"}, 998 RecordType: endpoint.RecordTypeCNAME, 999 Labels: map[string]string{ 1000 endpoint.OwnerLabelKey: "", 1001 }, 1002 }, 1003 { 1004 DNSName: "baz.test-zone.example.org", 1005 Targets: endpoint.Targets{"1.1.1.1"}, 1006 RecordType: endpoint.RecordTypeA, 1007 SetIdentifier: "set-1", 1008 Labels: map[string]string{ 1009 endpoint.OwnerLabelKey: "test-owner", 1010 endpoint.ResourceLabelKey: "ingress/default/my-ingress", 1011 }, 1012 }, 1013 { 1014 DNSName: "baz.test-zone.example.org", 1015 Targets: endpoint.Targets{"2.2.2.2"}, 1016 RecordType: endpoint.RecordTypeA, 1017 SetIdentifier: "set-2", 1018 Labels: map[string]string{ 1019 endpoint.OwnerLabelKey: "test-owner", 1020 endpoint.ResourceLabelKey: "ingress/default/other-ingress", 1021 }, 1022 }, 1023 }, 1024 }, 1025 } { 1026 t.Run(tc.name, func(t *testing.T) { 1027 originalMaxBatchSize := dynamodbMaxBatchSize 1028 if tc.maxBatchSize > 0 { 1029 dynamodbMaxBatchSize = tc.maxBatchSize 1030 } 1031 1032 api, p := newDynamoDBAPIStub(t, &tc.stubConfig) 1033 if len(tc.addRecords) > 0 { 1034 _ = p.(*wrappedProvider).Provider.ApplyChanges(context.Background(), &plan.Changes{ 1035 Create: tc.addRecords, 1036 }) 1037 } 1038 1039 ctx := context.Background() 1040 1041 r, _ := NewDynamoDBRegistry(p, "test-owner", api, "test-table", "txt.", "", "", []string{}, []string{}, nil, time.Hour) 1042 _, err := r.Records(ctx) 1043 require.Nil(t, err) 1044 1045 err = r.ApplyChanges(ctx, &tc.changes) 1046 if tc.expectedError == "" { 1047 assert.Nil(t, err) 1048 } else { 1049 assert.EqualError(t, err, tc.expectedError) 1050 } 1051 1052 assert.Empty(t, tc.stubConfig.ExpectInsert, "all expected inserts made") 1053 assert.Empty(t, tc.stubConfig.ExpectDelete, "all expected deletions made") 1054 1055 records, err := r.Records(ctx) 1056 require.Nil(t, err) 1057 assert.True(t, testutils.SameEndpoints(records, tc.expectedRecords)) 1058 1059 r.recordsCache = nil 1060 records, err = r.Records(ctx) 1061 require.Nil(t, err) 1062 assert.True(t, testutils.SameEndpoints(records, tc.expectedRecords)) 1063 if tc.expectedError == "" { 1064 assert.Empty(t, r.orphanedLabels) 1065 } 1066 1067 dynamodbMaxBatchSize = originalMaxBatchSize 1068 }) 1069 } 1070 } 1071 1072 // DynamoDBAPIStub is a minimal implementation of DynamoDBAPI, used primarily for unit testing. 1073 type DynamoDBStub struct { 1074 t *testing.T 1075 stubConfig *DynamoDBStubConfig 1076 tableDescription dynamodb.TableDescription 1077 changesApplied bool 1078 } 1079 1080 type DynamoDBStubConfig struct { 1081 ExpectInsert map[string]map[string]string 1082 ExpectInsertError map[string]string 1083 ExpectUpdate map[string]map[string]string 1084 ExpectUpdateError map[string]string 1085 ExpectDelete sets.Set[string] 1086 } 1087 1088 type wrappedProvider struct { 1089 provider.Provider 1090 stub *DynamoDBStub 1091 } 1092 1093 func (w *wrappedProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error { 1094 assert.False(w.stub.t, w.stub.changesApplied, "ApplyChanges already called") 1095 w.stub.changesApplied = true 1096 return w.Provider.ApplyChanges(ctx, changes) 1097 } 1098 1099 func newDynamoDBAPIStub(t *testing.T, stubConfig *DynamoDBStubConfig) (*DynamoDBStub, provider.Provider) { 1100 stub := &DynamoDBStub{ 1101 t: t, 1102 stubConfig: stubConfig, 1103 tableDescription: dynamodb.TableDescription{ 1104 AttributeDefinitions: []*dynamodb.AttributeDefinition{ 1105 { 1106 AttributeName: aws.String("k"), 1107 AttributeType: aws.String("S"), 1108 }, 1109 }, 1110 KeySchema: []*dynamodb.KeySchemaElement{ 1111 { 1112 AttributeName: aws.String("k"), 1113 KeyType: aws.String("HASH"), 1114 }, 1115 }, 1116 }, 1117 } 1118 p := inmemory.NewInMemoryProvider() 1119 _ = p.CreateZone(testZone) 1120 _ = p.ApplyChanges(context.Background(), &plan.Changes{ 1121 Create: []*endpoint.Endpoint{ 1122 endpoint.NewEndpoint("foo.test-zone.example.org", endpoint.RecordTypeCNAME, "foo.loadbalancer.com"), 1123 endpoint.NewEndpoint("bar.test-zone.example.org", endpoint.RecordTypeCNAME, "my-domain.com"), 1124 endpoint.NewEndpoint("baz.test-zone.example.org", endpoint.RecordTypeA, "1.1.1.1").WithSetIdentifier("set-1"), 1125 endpoint.NewEndpoint("baz.test-zone.example.org", endpoint.RecordTypeA, "2.2.2.2").WithSetIdentifier("set-2"), 1126 }, 1127 }) 1128 return stub, &wrappedProvider{ 1129 Provider: p, 1130 stub: stub, 1131 } 1132 } 1133 1134 func (r *DynamoDBStub) DescribeTableWithContext(ctx aws.Context, input *dynamodb.DescribeTableInput, opts ...request.Option) (*dynamodb.DescribeTableOutput, error) { 1135 assert.NotNil(r.t, ctx) 1136 assert.Equal(r.t, "test-table", *input.TableName, "table name") 1137 return &dynamodb.DescribeTableOutput{ 1138 Table: &r.tableDescription, 1139 }, nil 1140 } 1141 1142 func (r *DynamoDBStub) ScanPagesWithContext(ctx aws.Context, input *dynamodb.ScanInput, fn func(*dynamodb.ScanOutput, bool) bool, opts ...request.Option) error { 1143 assert.NotNil(r.t, ctx) 1144 assert.Equal(r.t, "test-table", *input.TableName, "table name") 1145 assert.Equal(r.t, "o = :ownerval", *input.FilterExpression) 1146 assert.Len(r.t, input.ExpressionAttributeValues, 1) 1147 assert.Equal(r.t, "test-owner", *input.ExpressionAttributeValues[":ownerval"].S) 1148 assert.Equal(r.t, "k,l", *input.ProjectionExpression) 1149 assert.True(r.t, *input.ConsistentRead) 1150 fn(&dynamodb.ScanOutput{ 1151 Items: []map[string]*dynamodb.AttributeValue{ 1152 { 1153 "k": &dynamodb.AttributeValue{S: aws.String("bar.test-zone.example.org#CNAME#")}, 1154 "l": &dynamodb.AttributeValue{M: map[string]*dynamodb.AttributeValue{ 1155 endpoint.ResourceLabelKey: {S: aws.String("ingress/default/my-ingress")}, 1156 }}, 1157 }, 1158 { 1159 "k": &dynamodb.AttributeValue{S: aws.String("baz.test-zone.example.org#A#set-1")}, 1160 "l": &dynamodb.AttributeValue{M: map[string]*dynamodb.AttributeValue{ 1161 endpoint.ResourceLabelKey: {S: aws.String("ingress/default/my-ingress")}, 1162 }}, 1163 }, 1164 { 1165 "k": &dynamodb.AttributeValue{S: aws.String("baz.test-zone.example.org#A#set-2")}, 1166 "l": &dynamodb.AttributeValue{M: map[string]*dynamodb.AttributeValue{ 1167 endpoint.ResourceLabelKey: {S: aws.String("ingress/default/other-ingress")}, 1168 }}, 1169 }, 1170 { 1171 "k": &dynamodb.AttributeValue{S: aws.String("quux.test-zone.example.org#A#set-2")}, 1172 "l": &dynamodb.AttributeValue{M: map[string]*dynamodb.AttributeValue{ 1173 endpoint.ResourceLabelKey: {S: aws.String("ingress/default/quux-ingress")}, 1174 }}, 1175 }, 1176 }, 1177 }, true) 1178 return nil 1179 } 1180 1181 func (r *DynamoDBStub) BatchExecuteStatementWithContext(context aws.Context, input *dynamodb.BatchExecuteStatementInput, option ...request.Option) (*dynamodb.BatchExecuteStatementOutput, error) { 1182 assert.NotNil(r.t, context) 1183 hasDelete := strings.HasPrefix(strings.ToLower(aws.StringValue(input.Statements[0].Statement)), "delete") 1184 assert.Equal(r.t, hasDelete, r.changesApplied, "delete after provider changes, everything else before") 1185 assert.LessOrEqual(r.t, len(input.Statements), 25) 1186 responses := make([]*dynamodb.BatchStatementResponse, 0, len(input.Statements)) 1187 1188 for _, statement := range input.Statements { 1189 assert.Equal(r.t, hasDelete, strings.HasPrefix(strings.ToLower(aws.StringValue(statement.Statement)), "delete")) 1190 switch aws.StringValue(statement.Statement) { 1191 case "DELETE FROM \"test-table\" WHERE \"k\"=? AND \"o\"=?": 1192 assert.True(r.t, r.changesApplied, "unexpected delete before provider changes") 1193 1194 key := aws.StringValue(statement.Parameters[0].S) 1195 assert.True(r.t, r.stubConfig.ExpectDelete.Has(key), "unexpected delete for key %q", key) 1196 r.stubConfig.ExpectDelete.Delete(key) 1197 1198 assert.Equal(r.t, "test-owner", aws.StringValue(statement.Parameters[1].S)) 1199 1200 responses = append(responses, &dynamodb.BatchStatementResponse{}) 1201 1202 case "INSERT INTO \"test-table\" VALUE {'k':?, 'o':?, 'l':?}": 1203 assert.False(r.t, r.changesApplied, "unexpected insert after provider changes") 1204 1205 key := aws.StringValue(statement.Parameters[0].S) 1206 if code, exists := r.stubConfig.ExpectInsertError[key]; exists { 1207 delete(r.stubConfig.ExpectInsertError, key) 1208 responses = append(responses, &dynamodb.BatchStatementResponse{ 1209 Error: &dynamodb.BatchStatementError{ 1210 Code: aws.String(code), 1211 Message: aws.String("testing error"), 1212 }, 1213 }) 1214 break 1215 } 1216 1217 expectedLabels, found := r.stubConfig.ExpectInsert[key] 1218 assert.True(r.t, found, "unexpected insert for key %q", key) 1219 delete(r.stubConfig.ExpectInsert, key) 1220 1221 assert.Equal(r.t, "test-owner", aws.StringValue(statement.Parameters[1].S)) 1222 1223 for label, attribute := range statement.Parameters[2].M { 1224 value := aws.StringValue(attribute.S) 1225 expectedValue, found := expectedLabels[label] 1226 assert.True(r.t, found, "insert for key %q has unexpected label %q", key, label) 1227 delete(expectedLabels, label) 1228 assert.Equal(r.t, expectedValue, value, "insert for key %q label %q value", key, label) 1229 } 1230 1231 for label := range expectedLabels { 1232 r.t.Errorf("insert for key %q did not get expected label %q", key, label) 1233 } 1234 1235 responses = append(responses, &dynamodb.BatchStatementResponse{}) 1236 1237 case "UPDATE \"test-table\" SET \"l\"=? WHERE \"k\"=?": 1238 assert.False(r.t, r.changesApplied, "unexpected update after provider changes") 1239 1240 key := aws.StringValue(statement.Parameters[1].S) 1241 if code, exists := r.stubConfig.ExpectUpdateError[key]; exists { 1242 delete(r.stubConfig.ExpectInsertError, key) 1243 responses = append(responses, &dynamodb.BatchStatementResponse{ 1244 Error: &dynamodb.BatchStatementError{ 1245 Code: aws.String(code), 1246 Message: aws.String("testing error"), 1247 }, 1248 }) 1249 break 1250 } 1251 1252 expectedLabels, found := r.stubConfig.ExpectUpdate[key] 1253 assert.True(r.t, found, "unexpected update for key %q", key) 1254 delete(r.stubConfig.ExpectUpdate, key) 1255 1256 for label, attribute := range statement.Parameters[0].M { 1257 value := aws.StringValue(attribute.S) 1258 expectedValue, found := expectedLabels[label] 1259 assert.True(r.t, found, "update for key %q has unexpected label %q", key, label) 1260 delete(expectedLabels, label) 1261 assert.Equal(r.t, expectedValue, value, "update for key %q label %q value", key, label) 1262 } 1263 1264 for label := range expectedLabels { 1265 r.t.Errorf("update for key %q did not get expected label %q", key, label) 1266 } 1267 1268 responses = append(responses, &dynamodb.BatchStatementResponse{}) 1269 1270 default: 1271 r.t.Errorf("unexpected statement: %s", aws.StringValue(statement.Statement)) 1272 } 1273 } 1274 1275 return &dynamodb.BatchExecuteStatementOutput{ 1276 Responses: responses, 1277 }, nil 1278 }