version.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. /*
  2. Copyright © The CDI 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 cdi
  14. import (
  15. "strings"
  16. "golang.org/x/mod/semver"
  17. "github.com/container-orchestrated-devices/container-device-interface/pkg/parser"
  18. cdi "github.com/container-orchestrated-devices/container-device-interface/specs-go"
  19. )
  20. const (
  21. // CurrentVersion is the current version of the CDI Spec.
  22. CurrentVersion = cdi.CurrentVersion
  23. // vCurrent is the current version as a semver-comparable type
  24. vCurrent version = "v" + CurrentVersion
  25. // These represent the released versions of the CDI specification
  26. v010 version = "v0.1.0"
  27. v020 version = "v0.2.0"
  28. v030 version = "v0.3.0"
  29. v040 version = "v0.4.0"
  30. v050 version = "v0.5.0"
  31. v060 version = "v0.6.0"
  32. // vEarliest is the earliest supported version of the CDI specification
  33. vEarliest version = v030
  34. )
  35. // validSpecVersions stores a map of spec versions to functions to check the required versions.
  36. // Adding new fields / spec versions requires that a `requiredFunc` be implemented and
  37. // this map be updated.
  38. var validSpecVersions = requiredVersionMap{
  39. v010: nil,
  40. v020: nil,
  41. v030: nil,
  42. v040: requiresV040,
  43. v050: requiresV050,
  44. v060: requiresV060,
  45. }
  46. // MinimumRequiredVersion determines the minimum spec version for the input spec.
  47. func MinimumRequiredVersion(spec *cdi.Spec) (string, error) {
  48. minVersion := validSpecVersions.requiredVersion(spec)
  49. return minVersion.String(), nil
  50. }
  51. // version represents a semantic version string
  52. type version string
  53. // newVersion creates a version that can be used for semantic version comparisons.
  54. func newVersion(v string) version {
  55. return version("v" + strings.TrimPrefix(v, "v"))
  56. }
  57. // String returns the string representation of the version.
  58. // This trims a leading v if present.
  59. func (v version) String() string {
  60. return strings.TrimPrefix(string(v), "v")
  61. }
  62. // IsGreaterThan checks with a version is greater than the specified version.
  63. func (v version) IsGreaterThan(o version) bool {
  64. return semver.Compare(string(v), string(o)) > 0
  65. }
  66. // IsLatest checks whether the version is the latest supported version
  67. func (v version) IsLatest() bool {
  68. return v == vCurrent
  69. }
  70. type requiredFunc func(*cdi.Spec) bool
  71. type requiredVersionMap map[version]requiredFunc
  72. // isValidVersion checks whether the specified version is valid.
  73. // A version is valid if it is contained in the required version map.
  74. func (r requiredVersionMap) isValidVersion(specVersion string) bool {
  75. _, ok := validSpecVersions[newVersion(specVersion)]
  76. return ok
  77. }
  78. // requiredVersion returns the minimum version required for the given spec
  79. func (r requiredVersionMap) requiredVersion(spec *cdi.Spec) version {
  80. minVersion := vEarliest
  81. for v, isRequired := range validSpecVersions {
  82. if isRequired == nil {
  83. continue
  84. }
  85. if isRequired(spec) && v.IsGreaterThan(minVersion) {
  86. minVersion = v
  87. }
  88. // If we have already detected the latest version then no later version could be detected
  89. if minVersion.IsLatest() {
  90. break
  91. }
  92. }
  93. return minVersion
  94. }
  95. // requiresV060 returns true if the spec uses v0.6.0 features
  96. func requiresV060(spec *cdi.Spec) bool {
  97. // The v0.6.0 spec allows annotations to be specified at a spec level
  98. for range spec.Annotations {
  99. return true
  100. }
  101. // The v0.6.0 spec allows annotations to be specified at a device level
  102. for _, d := range spec.Devices {
  103. for range d.Annotations {
  104. return true
  105. }
  106. }
  107. // The v0.6.0 spec allows dots "." in Kind name label (class)
  108. vendor, class := parser.ParseQualifier(spec.Kind)
  109. if vendor != "" {
  110. if strings.ContainsRune(class, '.') {
  111. return true
  112. }
  113. }
  114. return false
  115. }
  116. // requiresV050 returns true if the spec uses v0.5.0 features
  117. func requiresV050(spec *cdi.Spec) bool {
  118. var edits []*cdi.ContainerEdits
  119. for _, d := range spec.Devices {
  120. // The v0.5.0 spec allowed device names to start with a digit instead of requiring a letter
  121. if len(d.Name) > 0 && !parser.IsLetter(rune(d.Name[0])) {
  122. return true
  123. }
  124. edits = append(edits, &d.ContainerEdits)
  125. }
  126. edits = append(edits, &spec.ContainerEdits)
  127. for _, e := range edits {
  128. for _, dn := range e.DeviceNodes {
  129. // The HostPath field was added in v0.5.0
  130. if dn.HostPath != "" {
  131. return true
  132. }
  133. }
  134. }
  135. return false
  136. }
  137. // requiresV040 returns true if the spec uses v0.4.0 features
  138. func requiresV040(spec *cdi.Spec) bool {
  139. var edits []*cdi.ContainerEdits
  140. for _, d := range spec.Devices {
  141. edits = append(edits, &d.ContainerEdits)
  142. }
  143. edits = append(edits, &spec.ContainerEdits)
  144. for _, e := range edits {
  145. for _, m := range e.Mounts {
  146. // The Type field was added in v0.4.0
  147. if m.Type != "" {
  148. return true
  149. }
  150. }
  151. }
  152. return false
  153. }