statuserr.go 2.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  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 ctrlutil
  14. import "errors"
  15. // MaxConditionMessageLength caps how much of a safe error reaches a status
  16. // condition. The Message fields carry no maxLength in the CRDs, so a wrapped
  17. // chain would otherwise grow unbounded in etcd.
  18. const MaxConditionMessageLength = 256
  19. // safeError marks an error whose text may be copied into a status condition.
  20. type safeError struct {
  21. err error
  22. }
  23. func (e *safeError) Error() string { return e.err.Error() }
  24. func (e *safeError) Unwrap() error { return e.err }
  25. // Safe marks err as publishable in a status condition. Wrap only errors ESO
  26. // builds itself or receives from the Kubernetes API, and never compose one from
  27. // text you have not vetted: marking vouches for the whole composed message.
  28. // Provider errors can carry secret payloads, see external-secrets#5884.
  29. func Safe(err error) error {
  30. if err == nil {
  31. return nil
  32. }
  33. return &safeError{err: err}
  34. }
  35. // SafeMessage returns the innermost marked error's text, truncated, or "" when
  36. // err was never marked with Safe. Innermost wins so that text composed around a
  37. // marked error later, by a provider or by errors.Join, is never published.
  38. func SafeMessage(err error) string {
  39. var innermost *safeError
  40. for {
  41. var safe *safeError
  42. if !errors.As(err, &safe) {
  43. break
  44. }
  45. innermost = safe
  46. err = safe.Unwrap()
  47. }
  48. if innermost == nil {
  49. return ""
  50. }
  51. return truncate(innermost.Error(), MaxConditionMessageLength)
  52. }
  53. // truncate shortens msg to at most limit runes in total, counting the marker
  54. // that says the text was cut.
  55. func truncate(msg string, limit int) string {
  56. const marker = "..." // ASCII, so len is both its byte and its rune count
  57. runes := []rune(msg)
  58. if len(runes) <= limit {
  59. return msg
  60. }
  61. if limit < len(marker) {
  62. return string(runes[:limit])
  63. }
  64. return string(runes[:limit-len(marker)]) + marker
  65. }