retry_client.go 6.2 KB

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