fake_secret_api_test.go 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. /*
  2. Copyright © 2025 ESO Maintainer Team
  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 scaleway
  14. import (
  15. "fmt"
  16. "sort"
  17. "strconv"
  18. "github.com/google/uuid"
  19. smapi "github.com/scaleway/scaleway-sdk-go/api/secret/v1beta1"
  20. "github.com/scaleway/scaleway-sdk-go/scw"
  21. )
  22. type fakeSecretVersion struct {
  23. revision int
  24. data []byte
  25. dontFillData bool
  26. status string
  27. }
  28. type fakeSecret struct {
  29. id string
  30. name string
  31. versions []*fakeSecretVersion
  32. tags []string
  33. status string
  34. path string
  35. }
  36. var _ secretAPI = (*fakeSecretAPI)(nil)
  37. type fakeSecretAPI struct {
  38. secrets []*fakeSecret
  39. _secretsByID map[string]*fakeSecret
  40. _secretsByName map[string]*fakeSecret
  41. }
  42. func buildDB(f *fakeSecretAPI) *fakeSecretAPI {
  43. f._secretsByID = map[string]*fakeSecret{}
  44. f._secretsByName = map[string]*fakeSecret{}
  45. for _, secret := range f.secrets {
  46. if secret.id == "" {
  47. secret.id = uuid.NewString()
  48. }
  49. if secret.path == "" {
  50. secret.path = "/"
  51. }
  52. sort.Slice(secret.versions, func(i, j int) bool {
  53. return secret.versions[i].revision < secret.versions[j].revision
  54. })
  55. for index, version := range secret.versions {
  56. if version.revision != index+1 {
  57. panic("bad revision number in fixtures")
  58. }
  59. if version.status == "" {
  60. version.status = "enabled"
  61. }
  62. }
  63. for _, version := range secret.versions {
  64. if len(version.data) == 0 && !version.dontFillData {
  65. version.data = []byte(fmt.Sprintf("some data for secret %s version %d: %s", secret.id, version.revision, uuid.NewString()))
  66. }
  67. }
  68. if secret.status == "" {
  69. secret.status = "ready"
  70. }
  71. f._secretsByID[secret.id] = secret
  72. f._secretsByName[secret.name] = secret
  73. }
  74. return f
  75. }
  76. func (s *fakeSecret) getVersion(revision string) (*fakeSecretVersion, bool) {
  77. if len(s.versions) == 0 {
  78. return nil, false
  79. }
  80. if revision == "latest" {
  81. return s.versions[len(s.versions)-1], true
  82. }
  83. if revision == "latest_enabled" {
  84. for i := len(s.versions) - 1; i >= 0; i-- {
  85. if s.versions[i].status == "enabled" {
  86. return s.versions[i], true
  87. }
  88. }
  89. return nil, false
  90. }
  91. revisionNumber, err := strconv.Atoi(revision)
  92. if err != nil {
  93. return nil, false
  94. }
  95. i, found := sort.Find(len(s.versions), func(i int) int {
  96. if revisionNumber < s.versions[i].revision {
  97. return -1
  98. } else if revisionNumber > s.versions[i].revision {
  99. return 1
  100. } else {
  101. return 0
  102. }
  103. })
  104. if found {
  105. return s.versions[i], true
  106. }
  107. return nil, false
  108. }
  109. func (s *fakeSecret) mustGetVersion(revision string) *fakeSecretVersion {
  110. version, ok := s.getVersion(revision)
  111. if !ok {
  112. panic("no such version")
  113. }
  114. return version
  115. }
  116. func (f *fakeSecretAPI) secret(name string) *fakeSecret {
  117. return f._secretsByName[name]
  118. }
  119. func (f *fakeSecretAPI) getSecretByID(secretID string) (*fakeSecret, error) {
  120. secret, foundSecret := f._secretsByID[secretID]
  121. if !foundSecret {
  122. return nil, &scw.ResourceNotFoundError{
  123. Resource: "secret",
  124. ResourceID: secretID,
  125. }
  126. }
  127. return secret, nil
  128. }
  129. func (f *fakeSecretAPI) GetSecret(request *smapi.GetSecretRequest, _ ...scw.RequestOption) (*smapi.Secret, error) {
  130. if request.Region != "" {
  131. panic("explicit region in request is not supported")
  132. }
  133. secret, err := f.getSecretByID(request.SecretID)
  134. if err != nil {
  135. return nil, err
  136. }
  137. return &smapi.Secret{
  138. ID: secret.id,
  139. Name: secret.name,
  140. Status: smapi.SecretStatus(secret.status),
  141. Tags: secret.tags,
  142. VersionCount: uint32(len(secret.versions)),
  143. Path: secret.path,
  144. }, nil
  145. }
  146. func (f *fakeSecretAPI) GetSecretVersion(request *smapi.GetSecretVersionRequest, _ ...scw.RequestOption) (*smapi.SecretVersion, error) {
  147. if request.Region != "" {
  148. panic("explicit region in request is not supported")
  149. }
  150. secret, err := f.getSecretByID(request.SecretID)
  151. if err != nil {
  152. return nil, err
  153. }
  154. version, ok := secret.getVersion(request.Revision)
  155. if !ok {
  156. return nil, &scw.ResourceNotFoundError{
  157. Resource: "secret_version",
  158. ResourceID: request.Revision,
  159. }
  160. }
  161. return &smapi.SecretVersion{
  162. SecretID: secret.id,
  163. Revision: uint32(version.revision),
  164. Status: smapi.SecretVersionStatus(secret.status),
  165. }, nil
  166. }
  167. func (f *fakeSecretAPI) AccessSecretVersion(request *smapi.AccessSecretVersionRequest, _ ...scw.RequestOption) (*smapi.AccessSecretVersionResponse, error) {
  168. if request.Region != "" {
  169. panic("explicit region in request is not supported")
  170. }
  171. secret, err := f.getSecretByID(request.SecretID)
  172. if err != nil {
  173. return nil, err
  174. }
  175. version, ok := secret.getVersion(request.Revision)
  176. if !ok {
  177. return nil, &scw.ResourceNotFoundError{
  178. Resource: "secret_version",
  179. ResourceID: request.Revision,
  180. }
  181. }
  182. return &smapi.AccessSecretVersionResponse{
  183. SecretID: secret.id,
  184. Revision: uint32(version.revision),
  185. Data: version.data,
  186. }, nil
  187. }
  188. func (f *fakeSecretAPI) DisableSecretVersion(request *smapi.DisableSecretVersionRequest, _ ...scw.RequestOption) (*smapi.SecretVersion, error) {
  189. if request.Region != "" {
  190. panic("explicit region in request is not supported")
  191. }
  192. secret, err := f.getSecretByID(request.SecretID)
  193. if err != nil {
  194. return nil, err
  195. }
  196. version, ok := secret.getVersion(request.Revision)
  197. if !ok {
  198. return nil, &scw.ResourceNotFoundError{
  199. Resource: "secret_version",
  200. ResourceID: request.Revision,
  201. }
  202. }
  203. version.status = "disabled"
  204. return &smapi.SecretVersion{
  205. SecretID: secret.id,
  206. Revision: uint32(version.revision),
  207. Status: smapi.SecretVersionStatus(version.status),
  208. }, nil
  209. }
  210. type secretFilter func(*fakeSecret) bool
  211. func matchListSecretFilter(secret *fakeSecret, filter *smapi.ListSecretsRequest) bool {
  212. filters := make([]secretFilter, 0)
  213. if filter.Tags != nil {
  214. filters = append(filters, func(fs *fakeSecret) bool {
  215. for _, requiredTag := range filter.Tags {
  216. for _, secretTag := range fs.tags {
  217. if requiredTag == secretTag {
  218. return true
  219. }
  220. }
  221. }
  222. return false
  223. })
  224. }
  225. if filter.Name != nil {
  226. filters = append(filters, func(fs *fakeSecret) bool {
  227. return *filter.Name == fs.name
  228. })
  229. }
  230. if filter.Path != nil {
  231. filters = append(filters, func(fs *fakeSecret) bool {
  232. return *filter.Path == fs.path
  233. })
  234. }
  235. match := true
  236. for _, filterFn := range filters {
  237. match = match && filterFn(secret)
  238. }
  239. return match
  240. }
  241. func (f *fakeSecretAPI) ListSecrets(request *smapi.ListSecretsRequest, _ ...scw.RequestOption) (*smapi.ListSecretsResponse, error) {
  242. var matches []*fakeSecret
  243. // filtering
  244. for _, secret := range f.secrets {
  245. if matchListSecretFilter(secret, request) {
  246. matches = append(matches, secret)
  247. }
  248. }
  249. // ordering
  250. if request.OrderBy != "" {
  251. panic("explicit order by is not implemented")
  252. }
  253. sort.Slice(matches, func(i, j int) bool {
  254. return matches[i].id >= matches[j].id
  255. })
  256. // pagination
  257. response := smapi.ListSecretsResponse{
  258. TotalCount: uint64(len(matches)),
  259. }
  260. if request.Page == nil || request.PageSize == nil {
  261. panic("list secrets without explicit pagination not implemented")
  262. }
  263. page := int(*request.Page)
  264. pageSize := int(*request.PageSize)
  265. startOffset := (page - 1) * pageSize
  266. if startOffset > len(matches) {
  267. return nil, fmt.Errorf("invalid page offset (page = %d, page size = %d, total = %d)", page, pageSize, len(matches))
  268. }
  269. endOffset := page * pageSize
  270. if endOffset > len(matches) {
  271. endOffset = len(matches)
  272. }
  273. for _, secret := range matches[startOffset:endOffset] {
  274. response.Secrets = append(response.Secrets, &smapi.Secret{
  275. ID: secret.id,
  276. Name: secret.name,
  277. Status: smapi.SecretStatus(secret.status),
  278. Tags: secret.tags,
  279. VersionCount: uint32(len(secret.versions)),
  280. Path: secret.path,
  281. })
  282. }
  283. return &response, nil
  284. }
  285. func (f *fakeSecretAPI) CreateSecret(request *smapi.CreateSecretRequest, _ ...scw.RequestOption) (*smapi.Secret, error) {
  286. if request.Region != "" {
  287. panic("explicit region in request is not supported")
  288. }
  289. path := "/"
  290. if request.Path != nil {
  291. path = *request.Path
  292. }
  293. secret := &fakeSecret{
  294. id: uuid.NewString(),
  295. name: request.Name,
  296. status: "ready",
  297. path: path,
  298. }
  299. f.secrets = append(f.secrets, secret)
  300. f._secretsByID[secret.id] = secret
  301. f._secretsByName[secret.name] = secret
  302. return &smapi.Secret{
  303. ID: secret.id,
  304. ProjectID: request.ProjectID,
  305. Name: secret.name,
  306. Status: smapi.SecretStatus(secret.status),
  307. VersionCount: 0,
  308. Path: secret.path,
  309. }, nil
  310. }
  311. func (f *fakeSecretAPI) CreateSecretVersion(request *smapi.CreateSecretVersionRequest, _ ...scw.RequestOption) (*smapi.SecretVersion, error) {
  312. if request.Region != "" {
  313. panic("explicit region in request is not supported")
  314. }
  315. secret, ok := f._secretsByID[request.SecretID]
  316. if !ok {
  317. return nil, &scw.ResourceNotFoundError{
  318. Resource: "secret",
  319. ResourceID: request.SecretID,
  320. }
  321. }
  322. newVersion := &fakeSecretVersion{
  323. revision: len(secret.versions) + 1,
  324. data: request.Data,
  325. }
  326. secret.versions = append(secret.versions, newVersion)
  327. return &smapi.SecretVersion{
  328. SecretID: request.SecretID,
  329. Revision: uint32(newVersion.revision),
  330. Status: smapi.SecretVersionStatus(newVersion.status),
  331. }, nil
  332. }
  333. func (f *fakeSecretAPI) DeleteSecret(request *smapi.DeleteSecretRequest, _ ...scw.RequestOption) error {
  334. secret, ok := f._secretsByID[request.SecretID]
  335. if !ok {
  336. return &scw.ResourceNotFoundError{
  337. Resource: "secret",
  338. ResourceID: request.SecretID,
  339. }
  340. }
  341. delete(f._secretsByID, secret.id)
  342. return nil
  343. }