/acceptance/openstack/keymanager/v1/keymanager.go

https://github.com/gophercloud/gophercloud · Go · 764 lines · 559 code · 164 blank · 41 comment · 115 complexity · a5f134e067a0c2d999230bbcfd1cd8cc MD5 · raw file

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