intstr.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. /*
  2. Copyright 2014 The Kubernetes 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. http://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 intstr
  14. import (
  15. "encoding/json"
  16. "errors"
  17. "fmt"
  18. "math"
  19. "runtime/debug"
  20. "strconv"
  21. "strings"
  22. "k8s.io/klog/v2"
  23. )
  24. // IntOrString is a type that can hold an int32 or a string. When used in
  25. // JSON or YAML marshalling and unmarshalling, it produces or consumes the
  26. // inner type. This allows you to have, for example, a JSON field that can
  27. // accept a name or number.
  28. // TODO: Rename to Int32OrString
  29. //
  30. // +protobuf=true
  31. // +protobuf.options.(gogoproto.goproto_stringer)=false
  32. // +k8s:openapi-gen=true
  33. type IntOrString struct {
  34. Type Type `protobuf:"varint,1,opt,name=type,casttype=Type"`
  35. IntVal int32 `protobuf:"varint,2,opt,name=intVal"`
  36. StrVal string `protobuf:"bytes,3,opt,name=strVal"`
  37. }
  38. // Type represents the stored type of IntOrString.
  39. type Type int64
  40. const (
  41. Int Type = iota // The IntOrString holds an int.
  42. String // The IntOrString holds a string.
  43. )
  44. // FromInt creates an IntOrString object with an int32 value. It is
  45. // your responsibility not to call this method with a value greater
  46. // than int32.
  47. // Deprecated: use FromInt32 instead.
  48. func FromInt(val int) IntOrString {
  49. if val > math.MaxInt32 || val < math.MinInt32 {
  50. klog.Errorf("value: %d overflows int32\n%s\n", val, debug.Stack())
  51. }
  52. return IntOrString{Type: Int, IntVal: int32(val)}
  53. }
  54. // FromInt32 creates an IntOrString object with an int32 value.
  55. func FromInt32(val int32) IntOrString {
  56. return IntOrString{Type: Int, IntVal: val}
  57. }
  58. // FromString creates an IntOrString object with a string value.
  59. func FromString(val string) IntOrString {
  60. return IntOrString{Type: String, StrVal: val}
  61. }
  62. // Parse the given string and try to convert it to an integer before
  63. // setting it as a string value.
  64. func Parse(val string) IntOrString {
  65. i, err := strconv.Atoi(val)
  66. if err != nil {
  67. return FromString(val)
  68. }
  69. return FromInt(i)
  70. }
  71. // UnmarshalJSON implements the json.Unmarshaller interface.
  72. func (intstr *IntOrString) UnmarshalJSON(value []byte) error {
  73. if value[0] == '"' {
  74. intstr.Type = String
  75. return json.Unmarshal(value, &intstr.StrVal)
  76. }
  77. intstr.Type = Int
  78. return json.Unmarshal(value, &intstr.IntVal)
  79. }
  80. // String returns the string value, or the Itoa of the int value.
  81. func (intstr *IntOrString) String() string {
  82. if intstr == nil {
  83. return "<nil>"
  84. }
  85. if intstr.Type == String {
  86. return intstr.StrVal
  87. }
  88. return strconv.Itoa(intstr.IntValue())
  89. }
  90. // IntValue returns the IntVal if type Int, or if
  91. // it is a String, will attempt a conversion to int,
  92. // returning 0 if a parsing error occurs.
  93. func (intstr *IntOrString) IntValue() int {
  94. if intstr.Type == String {
  95. i, _ := strconv.Atoi(intstr.StrVal)
  96. return i
  97. }
  98. return int(intstr.IntVal)
  99. }
  100. // MarshalJSON implements the json.Marshaller interface.
  101. func (intstr IntOrString) MarshalJSON() ([]byte, error) {
  102. switch intstr.Type {
  103. case Int:
  104. return json.Marshal(intstr.IntVal)
  105. case String:
  106. return json.Marshal(intstr.StrVal)
  107. default:
  108. return []byte{}, fmt.Errorf("impossible IntOrString.Type")
  109. }
  110. }
  111. // OpenAPISchemaType is used by the kube-openapi generator when constructing
  112. // the OpenAPI spec of this type.
  113. //
  114. // See: https://github.com/kubernetes/kube-openapi/tree/master/pkg/generators
  115. func (IntOrString) OpenAPISchemaType() []string { return []string{"string"} }
  116. // OpenAPISchemaFormat is used by the kube-openapi generator when constructing
  117. // the OpenAPI spec of this type.
  118. func (IntOrString) OpenAPISchemaFormat() string { return "int-or-string" }
  119. // OpenAPIV3OneOfTypes is used by the kube-openapi generator when constructing
  120. // the OpenAPI v3 spec of this type.
  121. func (IntOrString) OpenAPIV3OneOfTypes() []string { return []string{"integer", "string"} }
  122. func ValueOrDefault(intOrPercent *IntOrString, defaultValue IntOrString) *IntOrString {
  123. if intOrPercent == nil {
  124. return &defaultValue
  125. }
  126. return intOrPercent
  127. }
  128. // GetScaledValueFromIntOrPercent is meant to replace GetValueFromIntOrPercent.
  129. // This method returns a scaled value from an IntOrString type. If the IntOrString
  130. // is a percentage string value it's treated as a percentage and scaled appropriately
  131. // in accordance to the total, if it's an int value it's treated as a simple value and
  132. // if it is a string value which is either non-numeric or numeric but lacking a trailing '%' it returns an error.
  133. func GetScaledValueFromIntOrPercent(intOrPercent *IntOrString, total int, roundUp bool) (int, error) {
  134. if intOrPercent == nil {
  135. return 0, errors.New("nil value for IntOrString")
  136. }
  137. value, isPercent, err := getIntOrPercentValueSafely(intOrPercent)
  138. if err != nil {
  139. return 0, fmt.Errorf("invalid value for IntOrString: %v", err)
  140. }
  141. if isPercent {
  142. if roundUp {
  143. value = int(math.Ceil(float64(value) * (float64(total)) / 100))
  144. } else {
  145. value = int(math.Floor(float64(value) * (float64(total)) / 100))
  146. }
  147. }
  148. return value, nil
  149. }
  150. // GetValueFromIntOrPercent was deprecated in favor of
  151. // GetScaledValueFromIntOrPercent. This method was treating all int as a numeric value and all
  152. // strings with or without a percent symbol as a percentage value.
  153. // Deprecated
  154. func GetValueFromIntOrPercent(intOrPercent *IntOrString, total int, roundUp bool) (int, error) {
  155. if intOrPercent == nil {
  156. return 0, errors.New("nil value for IntOrString")
  157. }
  158. value, isPercent, err := getIntOrPercentValue(intOrPercent)
  159. if err != nil {
  160. return 0, fmt.Errorf("invalid value for IntOrString: %v", err)
  161. }
  162. if isPercent {
  163. if roundUp {
  164. value = int(math.Ceil(float64(value) * (float64(total)) / 100))
  165. } else {
  166. value = int(math.Floor(float64(value) * (float64(total)) / 100))
  167. }
  168. }
  169. return value, nil
  170. }
  171. // getIntOrPercentValue is a legacy function and only meant to be called by GetValueFromIntOrPercent
  172. // For a more correct implementation call getIntOrPercentSafely
  173. func getIntOrPercentValue(intOrStr *IntOrString) (int, bool, error) {
  174. switch intOrStr.Type {
  175. case Int:
  176. return intOrStr.IntValue(), false, nil
  177. case String:
  178. s := strings.Replace(intOrStr.StrVal, "%", "", -1)
  179. v, err := strconv.Atoi(s)
  180. if err != nil {
  181. return 0, false, fmt.Errorf("invalid value %q: %v", intOrStr.StrVal, err)
  182. }
  183. return int(v), true, nil
  184. }
  185. return 0, false, fmt.Errorf("invalid type: neither int nor percentage")
  186. }
  187. func getIntOrPercentValueSafely(intOrStr *IntOrString) (int, bool, error) {
  188. switch intOrStr.Type {
  189. case Int:
  190. return intOrStr.IntValue(), false, nil
  191. case String:
  192. isPercent := false
  193. s := intOrStr.StrVal
  194. if strings.HasSuffix(s, "%") {
  195. isPercent = true
  196. s = strings.TrimSuffix(intOrStr.StrVal, "%")
  197. } else {
  198. return 0, false, fmt.Errorf("invalid type: string is not a percentage")
  199. }
  200. v, err := strconv.Atoi(s)
  201. if err != nil {
  202. return 0, false, fmt.Errorf("invalid value %q: %v", intOrStr.StrVal, err)
  203. }
  204. return int(v), isPercent, nil
  205. }
  206. return 0, false, fmt.Errorf("invalid type: neither int nor percentage")
  207. }