retry_client.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. /*
  2. Copyright © The ESO Authors
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. https://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package onepassword
  14. import (
  15. "strings"
  16. "time"
  17. "github.com/1Password/connect-sdk-go/connect"
  18. "github.com/1Password/connect-sdk-go/onepassword"
  19. "k8s.io/client-go/util/retry"
  20. )
  21. func is403AuthError(err error) bool {
  22. if err == nil {
  23. return false
  24. }
  25. // sadly, we don't have an error's body from the onepassword http call to match on, so all we do is string match.
  26. return strings.Contains(err.Error(), "status 403: Authorization")
  27. }
  28. // retryClient wraps a connect.Client with retry logic for 403 authorization errors.
  29. type retryClient struct {
  30. client connect.Client
  31. }
  32. // newRetryClient creates a new retryClient that wraps the given connect.Client.
  33. func newRetryClient(client connect.Client) connect.Client {
  34. return &retryClient{client: client}
  35. }
  36. // retryOn403 will retry the operation if it returns a 403 error.
  37. // For the reason, see: https://github.com/external-secrets/external-secrets/issues/4205
  38. func retryOn403(operation func() error) error {
  39. backoff := retry.DefaultBackoff
  40. backoff.Duration = 100 * time.Millisecond
  41. backoff.Steps = 3
  42. return retry.OnError(backoff, is403AuthError, operation)
  43. }
  44. // retryWithResult is a helper to wrap function calls that return a result and error.
  45. func retryWithResult[T any](fn func() (T, error)) (T, error) {
  46. var result T
  47. err := retryOn403(func() error {
  48. var retryErr error
  49. result, retryErr = fn()
  50. return retryErr
  51. })
  52. return result, err
  53. }
  54. func (r *retryClient) GetVaults() ([]onepassword.Vault, error) {
  55. return retryWithResult(r.client.GetVaults)
  56. }
  57. func (r *retryClient) GetVault(uuid string) (*onepassword.Vault, error) {
  58. return retryWithResult(func() (*onepassword.Vault, error) {
  59. return r.client.GetVault(uuid)
  60. })
  61. }
  62. func (r *retryClient) GetVaultByUUID(uuid string) (*onepassword.Vault, error) {
  63. return retryWithResult(func() (*onepassword.Vault, error) {
  64. return r.client.GetVaultByUUID(uuid)
  65. })
  66. }
  67. func (r *retryClient) GetVaultByTitle(title string) (*onepassword.Vault, error) {
  68. return retryWithResult(func() (*onepassword.Vault, error) {
  69. return r.client.GetVaultByTitle(title)
  70. })
  71. }
  72. func (r *retryClient) GetVaultsByTitle(uuid string) ([]onepassword.Vault, error) {
  73. return retryWithResult(func() ([]onepassword.Vault, error) {
  74. return r.client.GetVaultsByTitle(uuid)
  75. })
  76. }
  77. func (r *retryClient) GetItems(vaultQuery string) ([]onepassword.Item, error) {
  78. return retryWithResult(func() ([]onepassword.Item, error) {
  79. return r.client.GetItems(vaultQuery)
  80. })
  81. }
  82. func (r *retryClient) GetItem(itemQuery, vaultQuery string) (*onepassword.Item, error) {
  83. return retryWithResult(func() (*onepassword.Item, error) {
  84. return r.client.GetItem(itemQuery, vaultQuery)
  85. })
  86. }
  87. func (r *retryClient) GetItemByUUID(uuid, vaultQuery string) (*onepassword.Item, error) {
  88. return retryWithResult(func() (*onepassword.Item, error) {
  89. return r.client.GetItemByUUID(uuid, vaultQuery)
  90. })
  91. }
  92. func (r *retryClient) GetItemByTitle(title, vaultQuery string) (*onepassword.Item, error) {
  93. return retryWithResult(func() (*onepassword.Item, error) {
  94. return r.client.GetItemByTitle(title, vaultQuery)
  95. })
  96. }
  97. func (r *retryClient) GetItemsByTitle(title, vaultQuery string) ([]onepassword.Item, error) {
  98. return retryWithResult(func() ([]onepassword.Item, error) {
  99. return r.client.GetItemsByTitle(title, vaultQuery)
  100. })
  101. }
  102. func (r *retryClient) CreateItem(item *onepassword.Item, vaultQuery string) (*onepassword.Item, error) {
  103. return retryWithResult(func() (*onepassword.Item, error) {
  104. return r.client.CreateItem(item, vaultQuery)
  105. })
  106. }
  107. func (r *retryClient) UpdateItem(item *onepassword.Item, vaultQuery string) (*onepassword.Item, error) {
  108. return retryWithResult(func() (*onepassword.Item, error) {
  109. return r.client.UpdateItem(item, vaultQuery)
  110. })
  111. }
  112. func (r *retryClient) DeleteItem(item *onepassword.Item, vaultQuery string) error {
  113. return retryOn403(func() error {
  114. return r.client.DeleteItem(item, vaultQuery)
  115. })
  116. }
  117. func (r *retryClient) DeleteItemByID(itemUUID, vaultQuery string) error {
  118. return retryOn403(func() error {
  119. return r.client.DeleteItemByID(itemUUID, vaultQuery)
  120. })
  121. }
  122. func (r *retryClient) DeleteItemByTitle(title, vaultQuery string) error {
  123. return retryOn403(func() error {
  124. return r.client.DeleteItemByTitle(title, vaultQuery)
  125. })
  126. }
  127. func (r *retryClient) GetFiles(itemQuery, vaultQuery string) ([]onepassword.File, error) {
  128. return retryWithResult(func() ([]onepassword.File, error) {
  129. return r.client.GetFiles(itemQuery, vaultQuery)
  130. })
  131. }
  132. func (r *retryClient) GetFile(uuid, itemQuery, vaultQuery string) (*onepassword.File, error) {
  133. return retryWithResult(func() (*onepassword.File, error) {
  134. return r.client.GetFile(uuid, itemQuery, vaultQuery)
  135. })
  136. }
  137. func (r *retryClient) GetFileContent(file *onepassword.File) ([]byte, error) {
  138. return retryWithResult(func() ([]byte, error) {
  139. return r.client.GetFileContent(file)
  140. })
  141. }
  142. func (r *retryClient) DownloadFile(file *onepassword.File, targetDirectory string, overwrite bool) (string, error) {
  143. return retryWithResult(func() (string, error) {
  144. return r.client.DownloadFile(file, targetDirectory, overwrite)
  145. })
  146. }
  147. func (r *retryClient) LoadStructFromItemByUUID(config any, itemUUID, vaultQuery string) error {
  148. return retryOn403(func() error {
  149. return r.client.LoadStructFromItemByUUID(config, itemUUID, vaultQuery)
  150. })
  151. }
  152. func (r *retryClient) LoadStructFromItemByTitle(config any, itemTitle, vaultQuery string) error {
  153. return retryOn403(func() error {
  154. return r.client.LoadStructFromItemByTitle(config, itemTitle, vaultQuery)
  155. })
  156. }
  157. func (r *retryClient) LoadStructFromItem(config any, itemQuery, vaultQuery string) error {
  158. return retryOn403(func() error {
  159. return r.client.LoadStructFromItem(config, itemQuery, vaultQuery)
  160. })
  161. }
  162. func (r *retryClient) LoadStruct(config any) error {
  163. return retryOn403(func() error {
  164. return r.client.LoadStruct(config)
  165. })
  166. }