github.com/vnpaycloud-console/gophercloud/v2@v2.0.5/internal/acceptance/openstack/keymanager/v1/keymanager.go (about) 1 package v1 2 3 import ( 4 "context" 5 "crypto/rand" 6 "crypto/rsa" 7 "crypto/x509" 8 "crypto/x509/pkix" 9 "encoding/asn1" 10 "encoding/base64" 11 "encoding/pem" 12 "fmt" 13 "math/big" 14 "strings" 15 "testing" 16 "time" 17 18 "github.com/vnpaycloud-console/gophercloud/v2" 19 "github.com/vnpaycloud-console/gophercloud/v2/internal/acceptance/tools" 20 "github.com/vnpaycloud-console/gophercloud/v2/openstack/keymanager/v1/containers" 21 "github.com/vnpaycloud-console/gophercloud/v2/openstack/keymanager/v1/orders" 22 "github.com/vnpaycloud-console/gophercloud/v2/openstack/keymanager/v1/secrets" 23 th "github.com/vnpaycloud-console/gophercloud/v2/testhelper" 24 ) 25 26 // CreateAsymmetric Order will create a random asymmetric order. 27 // An error will be returned if the order could not be created. 28 func CreateAsymmetricOrder(t *testing.T, client *gophercloud.ServiceClient) (*orders.Order, error) { 29 name := tools.RandomString("TESTACC-", 8) 30 t.Logf("Attempting to create order %s", name) 31 32 expiration := time.Date(2049, 1, 1, 1, 1, 1, 0, time.UTC) 33 createOpts := orders.CreateOpts{ 34 Type: orders.AsymmetricOrder, 35 Meta: orders.MetaOpts{ 36 Name: name, 37 Algorithm: "rsa", 38 BitLength: 2048, 39 Mode: "cbc", 40 Expiration: &expiration, 41 }, 42 } 43 44 order, err := orders.Create(context.TODO(), client, createOpts).Extract() 45 if err != nil { 46 return nil, err 47 } 48 49 orderID, err := ParseID(order.OrderRef) 50 if err != nil { 51 return nil, err 52 } 53 54 err = WaitForOrder(client, orderID) 55 th.AssertNoErr(t, err) 56 57 order, err = orders.Get(context.TODO(), client, orderID).Extract() 58 if err != nil { 59 return nil, err 60 } 61 62 tools.PrintResource(t, order) 63 tools.PrintResource(t, order.Meta.Expiration) 64 65 th.AssertEquals(t, order.Meta.Name, name) 66 th.AssertEquals(t, order.Type, "asymmetric") 67 68 return order, nil 69 } 70 71 // CreateCertificateContainer will create a random certificate container. 72 // An error will be returned if the container could not be created. 73 func CreateCertificateContainer(t *testing.T, client *gophercloud.ServiceClient, passphrase, private, certificate *secrets.Secret) (*containers.Container, error) { 74 containerName := tools.RandomString("TESTACC-", 8) 75 76 t.Logf("Attempting to create container %s", containerName) 77 78 createOpts := containers.CreateOpts{ 79 Type: containers.CertificateContainer, 80 Name: containerName, 81 SecretRefs: []containers.SecretRef{ 82 { 83 Name: "certificate", 84 SecretRef: certificate.SecretRef, 85 }, 86 { 87 Name: "private_key", 88 SecretRef: private.SecretRef, 89 }, 90 { 91 Name: "private_key_passphrase", 92 SecretRef: passphrase.SecretRef, 93 }, 94 }, 95 } 96 97 container, err := containers.Create(context.TODO(), client, createOpts).Extract() 98 if err != nil { 99 return nil, err 100 } 101 102 t.Logf("Successfully created container: %s", container.ContainerRef) 103 104 containerID, err := ParseID(container.ContainerRef) 105 if err != nil { 106 return nil, err 107 } 108 109 container, err = containers.Get(context.TODO(), client, containerID).Extract() 110 if err != nil { 111 return nil, err 112 } 113 114 tools.PrintResource(t, container) 115 116 th.AssertEquals(t, container.Name, containerName) 117 th.AssertEquals(t, container.Type, "certificate") 118 119 return container, nil 120 } 121 122 // CreateKeyOrder will create a random key order. 123 // An error will be returned if the order could not be created. 124 func CreateKeyOrder(t *testing.T, client *gophercloud.ServiceClient) (*orders.Order, error) { 125 name := tools.RandomString("TESTACC-", 8) 126 t.Logf("Attempting to create order %s", name) 127 128 expiration := time.Date(2049, 1, 1, 1, 1, 1, 0, time.UTC) 129 createOpts := orders.CreateOpts{ 130 Type: orders.KeyOrder, 131 Meta: orders.MetaOpts{ 132 Name: name, 133 Algorithm: "aes", 134 BitLength: 256, 135 Mode: "cbc", 136 Expiration: &expiration, 137 }, 138 } 139 140 order, err := orders.Create(context.TODO(), client, createOpts).Extract() 141 if err != nil { 142 return nil, err 143 } 144 145 orderID, err := ParseID(order.OrderRef) 146 if err != nil { 147 return nil, err 148 } 149 150 order, err = orders.Get(context.TODO(), client, orderID).Extract() 151 if err != nil { 152 return nil, err 153 } 154 155 tools.PrintResource(t, order) 156 tools.PrintResource(t, order.Meta.Expiration) 157 158 th.AssertEquals(t, order.Meta.Name, name) 159 th.AssertEquals(t, order.Type, "key") 160 161 return order, nil 162 } 163 164 // CreateRSAContainer will create a random RSA container. 165 // An error will be returned if the container could not be created. 166 func CreateRSAContainer(t *testing.T, client *gophercloud.ServiceClient, passphrase, private, public *secrets.Secret) (*containers.Container, error) { 167 containerName := tools.RandomString("TESTACC-", 8) 168 169 t.Logf("Attempting to create container %s", containerName) 170 171 createOpts := containers.CreateOpts{ 172 Type: containers.RSAContainer, 173 Name: containerName, 174 SecretRefs: []containers.SecretRef{ 175 { 176 Name: "public_key", 177 SecretRef: public.SecretRef, 178 }, 179 { 180 Name: "private_key", 181 SecretRef: private.SecretRef, 182 }, 183 { 184 Name: "private_key_passphrase", 185 SecretRef: passphrase.SecretRef, 186 }, 187 }, 188 } 189 190 container, err := containers.Create(context.TODO(), client, createOpts).Extract() 191 if err != nil { 192 return nil, err 193 } 194 195 t.Logf("Successfully created container: %s", container.ContainerRef) 196 197 containerID, err := ParseID(container.ContainerRef) 198 if err != nil { 199 return nil, err 200 } 201 202 container, err = containers.Get(context.TODO(), client, containerID).Extract() 203 if err != nil { 204 return nil, err 205 } 206 207 tools.PrintResource(t, container) 208 209 th.AssertEquals(t, container.Name, containerName) 210 th.AssertEquals(t, container.Type, "rsa") 211 212 return container, nil 213 } 214 215 // CreateCertificateSecret will create a random certificate secret. An error 216 // will be returned if the secret could not be created. 217 func CreateCertificateSecret(t *testing.T, client *gophercloud.ServiceClient, cert []byte) (*secrets.Secret, error) { 218 b64Cert := base64.StdEncoding.EncodeToString(cert) 219 220 name := tools.RandomString("TESTACC-", 8) 221 t.Logf("Attempting to create public key %s", name) 222 223 createOpts := secrets.CreateOpts{ 224 Name: name, 225 SecretType: secrets.CertificateSecret, 226 Payload: b64Cert, 227 PayloadContentType: "application/octet-stream", 228 PayloadContentEncoding: "base64", 229 Algorithm: "rsa", 230 } 231 232 secret, err := secrets.Create(context.TODO(), client, createOpts).Extract() 233 if err != nil { 234 return nil, err 235 } 236 237 t.Logf("Successfully created secret: %s", secret.SecretRef) 238 239 secretID, err := ParseID(secret.SecretRef) 240 if err != nil { 241 return nil, err 242 } 243 244 secret, err = secrets.Get(context.TODO(), client, secretID).Extract() 245 if err != nil { 246 return nil, err 247 } 248 249 tools.PrintResource(t, secret) 250 251 th.AssertEquals(t, secret.Name, name) 252 th.AssertEquals(t, secret.Algorithm, "rsa") 253 254 return secret, nil 255 } 256 257 // CreateEmptySecret will create a random secret with no payload. An error will 258 // be returned if the secret could not be created. 259 func CreateEmptySecret(t *testing.T, client *gophercloud.ServiceClient) (*secrets.Secret, error) { 260 secretName := tools.RandomString("TESTACC-", 8) 261 262 t.Logf("Attempting to create secret %s", secretName) 263 264 createOpts := secrets.CreateOpts{ 265 Algorithm: "aes", 266 BitLength: 256, 267 Mode: "cbc", 268 Name: secretName, 269 SecretType: secrets.OpaqueSecret, 270 } 271 272 secret, err := secrets.Create(context.TODO(), client, createOpts).Extract() 273 if err != nil { 274 return nil, err 275 } 276 277 t.Logf("Successfully created secret: %s", secret.SecretRef) 278 279 secretID, err := ParseID(secret.SecretRef) 280 if err != nil { 281 return nil, err 282 } 283 284 secret, err = secrets.Get(context.TODO(), client, secretID).Extract() 285 if err != nil { 286 return nil, err 287 } 288 289 tools.PrintResource(t, secret) 290 291 th.AssertEquals(t, secret.Name, secretName) 292 th.AssertEquals(t, secret.Algorithm, "aes") 293 294 return secret, nil 295 } 296 297 // CreateGenericContainer will create a random generic container with a 298 // specified secret. An error will be returned if the container could not 299 // be created. 300 func CreateGenericContainer(t *testing.T, client *gophercloud.ServiceClient, secret *secrets.Secret) (*containers.Container, error) { 301 containerName := tools.RandomString("TESTACC-", 8) 302 303 t.Logf("Attempting to create container %s", containerName) 304 305 createOpts := containers.CreateOpts{ 306 Type: containers.GenericContainer, 307 Name: containerName, 308 SecretRefs: []containers.SecretRef{ 309 { 310 Name: secret.Name, 311 SecretRef: secret.SecretRef, 312 }, 313 }, 314 } 315 316 container, err := containers.Create(context.TODO(), client, createOpts).Extract() 317 if err != nil { 318 return nil, err 319 } 320 321 t.Logf("Successfully created container: %s", container.ContainerRef) 322 323 containerID, err := ParseID(container.ContainerRef) 324 if err != nil { 325 return nil, err 326 } 327 328 container, err = containers.Get(context.TODO(), client, containerID).Extract() 329 if err != nil { 330 return nil, err 331 } 332 333 tools.PrintResource(t, container) 334 335 th.AssertEquals(t, container.Name, containerName) 336 th.AssertEquals(t, container.Type, "generic") 337 338 return container, nil 339 } 340 341 // ReplaceGenericContainerSecretRef will replace the container old secret 342 // reference with a new one. An error will be returned if the reference could 343 // not be replaced. 344 func ReplaceGenericContainerSecretRef(t *testing.T, client *gophercloud.ServiceClient, container *containers.Container, secretOld *secrets.Secret, secretNew *secrets.Secret) error { 345 containerID, err := ParseID(container.ContainerRef) 346 if err != nil { 347 return err 348 } 349 350 t.Logf("Attempting to remove an old secret reference %s", secretOld.SecretRef) 351 352 res1 := containers.DeleteSecretRef(context.TODO(), client, containerID, containers.SecretRef{Name: secretOld.Name, SecretRef: secretOld.SecretRef}) 353 if res1.Err != nil { 354 return res1.Err 355 } 356 357 t.Logf("Successfully removed old secret reference: %s", secretOld.SecretRef) 358 359 t.Logf("Attempting to remove a new secret reference %s", secretNew.SecretRef) 360 361 newRef := containers.SecretRef{Name: secretNew.Name, SecretRef: secretNew.SecretRef} 362 res2 := containers.CreateSecretRef(context.TODO(), client, containerID, newRef) 363 if res2.Err != nil { 364 return res2.Err 365 } 366 367 c, err := res2.Extract() 368 if err != nil { 369 return err 370 } 371 tools.PrintResource(t, c) 372 373 t.Logf("Successfully created new secret reference: %s", secretNew.SecretRef) 374 375 updatedContainer, err := containers.Get(context.TODO(), client, containerID).Extract() 376 if err != nil { 377 return err 378 } 379 380 tools.PrintResource(t, container) 381 382 th.AssertEquals(t, updatedContainer.Name, container.Name) 383 th.AssertEquals(t, updatedContainer.Type, container.Type) 384 th.AssertEquals(t, updatedContainer.SecretRefs[0], newRef) 385 386 return nil 387 } 388 389 // CreatePassphraseSecret will create a random passphrase secret. 390 // An error will be returned if the secret could not be created. 391 func CreatePassphraseSecret(t *testing.T, client *gophercloud.ServiceClient, passphrase string) (*secrets.Secret, error) { 392 secretName := tools.RandomString("TESTACC-", 8) 393 394 t.Logf("Attempting to create secret %s", secretName) 395 396 createOpts := secrets.CreateOpts{ 397 Algorithm: "aes", 398 BitLength: 256, 399 Mode: "cbc", 400 Name: secretName, 401 Payload: passphrase, 402 PayloadContentType: "text/plain", 403 SecretType: secrets.PassphraseSecret, 404 } 405 406 secret, err := secrets.Create(context.TODO(), client, createOpts).Extract() 407 if err != nil { 408 return nil, err 409 } 410 411 t.Logf("Successfully created secret: %s", secret.SecretRef) 412 413 secretID, err := ParseID(secret.SecretRef) 414 if err != nil { 415 return nil, err 416 } 417 418 secret, err = secrets.Get(context.TODO(), client, secretID).Extract() 419 if err != nil { 420 return nil, err 421 } 422 423 tools.PrintResource(t, secret) 424 425 th.AssertEquals(t, secret.Name, secretName) 426 th.AssertEquals(t, secret.Algorithm, "aes") 427 428 return secret, nil 429 } 430 431 // CreatePublicSecret will create a random public secret. An error 432 // will be returned if the secret could not be created. 433 func CreatePublicSecret(t *testing.T, client *gophercloud.ServiceClient, pub []byte) (*secrets.Secret, error) { 434 b64Cert := base64.StdEncoding.EncodeToString(pub) 435 436 name := tools.RandomString("TESTACC-", 8) 437 t.Logf("Attempting to create public key %s", name) 438 439 createOpts := secrets.CreateOpts{ 440 Name: name, 441 SecretType: secrets.PublicSecret, 442 Payload: b64Cert, 443 PayloadContentType: "application/octet-stream", 444 PayloadContentEncoding: "base64", 445 Algorithm: "rsa", 446 } 447 448 secret, err := secrets.Create(context.TODO(), client, createOpts).Extract() 449 if err != nil { 450 return nil, err 451 } 452 453 t.Logf("Successfully created secret: %s", secret.SecretRef) 454 455 secretID, err := ParseID(secret.SecretRef) 456 if err != nil { 457 return nil, err 458 } 459 460 secret, err = secrets.Get(context.TODO(), client, secretID).Extract() 461 if err != nil { 462 return nil, err 463 } 464 465 tools.PrintResource(t, secret) 466 467 th.AssertEquals(t, secret.Name, name) 468 th.AssertEquals(t, secret.Algorithm, "rsa") 469 470 return secret, nil 471 } 472 473 // CreatePrivateSecret will create a random private secret. An error 474 // will be returned if the secret could not be created. 475 func CreatePrivateSecret(t *testing.T, client *gophercloud.ServiceClient, priv []byte) (*secrets.Secret, error) { 476 b64Cert := base64.StdEncoding.EncodeToString(priv) 477 478 name := tools.RandomString("TESTACC-", 8) 479 t.Logf("Attempting to create public key %s", name) 480 481 createOpts := secrets.CreateOpts{ 482 Name: name, 483 SecretType: secrets.PrivateSecret, 484 Payload: b64Cert, 485 PayloadContentType: "application/octet-stream", 486 PayloadContentEncoding: "base64", 487 Algorithm: "rsa", 488 } 489 490 secret, err := secrets.Create(context.TODO(), client, createOpts).Extract() 491 if err != nil { 492 return nil, err 493 } 494 495 t.Logf("Successfully created secret: %s", secret.SecretRef) 496 497 secretID, err := ParseID(secret.SecretRef) 498 if err != nil { 499 return nil, err 500 } 501 502 secret, err = secrets.Get(context.TODO(), client, secretID).Extract() 503 if err != nil { 504 return nil, err 505 } 506 507 tools.PrintResource(t, secret) 508 509 th.AssertEquals(t, secret.Name, name) 510 th.AssertEquals(t, secret.Algorithm, "rsa") 511 512 return secret, nil 513 } 514 515 // CreateSecretWithPayload will create a random secret with a given payload. 516 // An error will be returned if the secret could not be created. 517 func CreateSecretWithPayload(t *testing.T, client *gophercloud.ServiceClient, payload string) (*secrets.Secret, error) { 518 secretName := tools.RandomString("TESTACC-", 8) 519 520 t.Logf("Attempting to create secret %s", secretName) 521 522 expiration := time.Date(2049, 1, 1, 1, 1, 1, 0, time.UTC) 523 createOpts := secrets.CreateOpts{ 524 Algorithm: "aes", 525 BitLength: 256, 526 Mode: "cbc", 527 Name: secretName, 528 Payload: payload, 529 PayloadContentType: "text/plain", 530 SecretType: secrets.OpaqueSecret, 531 Expiration: &expiration, 532 } 533 534 secret, err := secrets.Create(context.TODO(), client, createOpts).Extract() 535 if err != nil { 536 return nil, err 537 } 538 539 t.Logf("Successfully created secret: %s", secret.SecretRef) 540 541 secretID, err := ParseID(secret.SecretRef) 542 if err != nil { 543 return nil, err 544 } 545 546 secret, err = secrets.Get(context.TODO(), client, secretID).Extract() 547 if err != nil { 548 return nil, err 549 } 550 551 tools.PrintResource(t, secret) 552 553 th.AssertEquals(t, secret.Name, secretName) 554 th.AssertEquals(t, secret.Algorithm, "aes") 555 th.AssertEquals(t, secret.Expiration, expiration) 556 557 return secret, nil 558 } 559 560 // CreateSymmetricSecret will create a random symmetric secret. An error 561 // will be returned if the secret could not be created. 562 func CreateSymmetricSecret(t *testing.T, client *gophercloud.ServiceClient) (*secrets.Secret, error) { 563 name := tools.RandomString("TESTACC-", 8) 564 key := tools.RandomString("", 256) 565 b64Key := base64.StdEncoding.EncodeToString([]byte(key)) 566 567 t.Logf("Attempting to create symmetric key %s", name) 568 569 createOpts := secrets.CreateOpts{ 570 Name: name, 571 SecretType: secrets.SymmetricSecret, 572 Payload: b64Key, 573 PayloadContentType: "application/octet-stream", 574 PayloadContentEncoding: "base64", 575 Algorithm: "aes", 576 BitLength: 256, 577 Mode: "cbc", 578 } 579 580 secret, err := secrets.Create(context.TODO(), client, createOpts).Extract() 581 if err != nil { 582 return nil, err 583 } 584 585 t.Logf("Successfully created secret: %s", secret.SecretRef) 586 587 secretID, err := ParseID(secret.SecretRef) 588 if err != nil { 589 return nil, err 590 } 591 592 secret, err = secrets.Get(context.TODO(), client, secretID).Extract() 593 if err != nil { 594 return nil, err 595 } 596 597 tools.PrintResource(t, secret) 598 599 th.AssertEquals(t, secret.Name, name) 600 th.AssertEquals(t, secret.Algorithm, "aes") 601 602 return secret, nil 603 } 604 605 // DeleteContainer will delete a container. A fatal error will occur if the 606 // container could not be deleted. This works best when used as a deferred 607 // function. 608 func DeleteContainer(t *testing.T, client *gophercloud.ServiceClient, id string) { 609 t.Logf("Attempting to delete container %s", id) 610 611 err := containers.Delete(context.TODO(), client, id).ExtractErr() 612 if err != nil { 613 t.Fatalf("Could not delete container: %s", err) 614 } 615 616 t.Logf("Successfully deleted container %s", id) 617 } 618 619 // DeleteOrder will delete an order. A fatal error will occur if the 620 // order could not be deleted. This works best when used as a deferred 621 // function. 622 func DeleteOrder(t *testing.T, client *gophercloud.ServiceClient, id string) { 623 t.Logf("Attempting to delete order %s", id) 624 625 err := orders.Delete(context.TODO(), client, id).ExtractErr() 626 if err != nil { 627 t.Fatalf("Could not delete order: %s", err) 628 } 629 630 t.Logf("Successfully deleted order %s", id) 631 } 632 633 // DeleteSecret will delete a secret. A fatal error will occur if the secret 634 // could not be deleted. This works best when used as a deferred function. 635 func DeleteSecret(t *testing.T, client *gophercloud.ServiceClient, id string) { 636 t.Logf("Attempting to delete secret %s", id) 637 638 err := secrets.Delete(context.TODO(), client, id).ExtractErr() 639 if err != nil { 640 t.Fatalf("Could not delete secret: %s", err) 641 } 642 643 t.Logf("Successfully deleted secret %s", id) 644 } 645 646 func ParseID(ref string) (string, error) { 647 parts := strings.Split(ref, "/") 648 if len(parts) < 2 { 649 return "", fmt.Errorf("Could not parse %s", ref) 650 } 651 652 return parts[len(parts)-1], nil 653 } 654 655 // CreateCertificate will create a random certificate. A fatal error will 656 // be returned if creation failed. 657 // https://golang.org/src/crypto/tls/generate_cert.go 658 func CreateCertificate(t *testing.T, passphrase string) ([]byte, []byte, error) { 659 key, err := rsa.GenerateKey(rand.Reader, 2048) 660 if err != nil { 661 return nil, nil, err 662 } 663 664 block := &pem.Block{ 665 Type: "RSA PRIVATE KEY", 666 Bytes: x509.MarshalPKCS1PrivateKey(key), 667 } 668 669 if passphrase != "" { 670 block, err = x509.EncryptPEMBlock(rand.Reader, block.Type, block.Bytes, []byte(passphrase), x509.PEMCipherAES256) 671 if err != nil { 672 return nil, nil, err 673 } 674 } 675 676 keyPem := pem.EncodeToMemory(block) 677 678 serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) 679 serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) 680 if err != nil { 681 return nil, nil, err 682 } 683 684 tpl := x509.Certificate{ 685 SerialNumber: serialNumber, 686 Subject: pkix.Name{ 687 Organization: []string{"Some Org"}, 688 }, 689 NotBefore: time.Now(), 690 NotAfter: time.Now().AddDate(5, 0, 0), 691 BasicConstraintsValid: true, 692 } 693 694 cert, err := x509.CreateCertificate(rand.Reader, &tpl, &tpl, &key.PublicKey, key) 695 if err != nil { 696 return nil, nil, err 697 } 698 699 certPem := pem.EncodeToMemory(&pem.Block{ 700 Type: "CERTIFICATE", 701 Bytes: cert, 702 }) 703 704 return keyPem, certPem, nil 705 } 706 707 // CreateRSAKeyPair will create a random RSA key pair. An error will be 708 // returned if the pair could not be created. 709 func CreateRSAKeyPair(t *testing.T, passphrase string) ([]byte, []byte, error) { 710 key, err := rsa.GenerateKey(rand.Reader, 2048) 711 if err != nil { 712 return nil, nil, err 713 } 714 715 block := &pem.Block{ 716 Type: "RSA PRIVATE KEY", 717 Bytes: x509.MarshalPKCS1PrivateKey(key), 718 } 719 720 if passphrase != "" { 721 block, err = x509.EncryptPEMBlock(rand.Reader, block.Type, block.Bytes, []byte(passphrase), x509.PEMCipherAES256) 722 if err != nil { 723 return nil, nil, err 724 } 725 } 726 727 keyPem := pem.EncodeToMemory(block) 728 729 asn1Bytes, err := asn1.Marshal(key.PublicKey) 730 if err != nil { 731 return nil, nil, err 732 } 733 734 block = &pem.Block{ 735 Type: "RSA PUBLIC KEY", 736 Bytes: asn1Bytes, 737 } 738 739 pubPem := pem.EncodeToMemory(block) 740 741 return keyPem, pubPem, nil 742 } 743 744 func WaitForOrder(client *gophercloud.ServiceClient, orderID string) error { 745 return tools.WaitFor(func(ctx context.Context) (bool, error) { 746 order, err := orders.Get(ctx, client, orderID).Extract() 747 if err != nil { 748 return false, err 749 } 750 751 if order.SecretRef != "" { 752 return true, nil 753 } 754 755 if order.ContainerRef != "" { 756 return true, nil 757 } 758 759 if order.Status == "ERROR" { 760 return false, fmt.Errorf("Order %s in ERROR state", orderID) 761 } 762 763 return false, nil 764 }) 765 }