fake_secret_api_test.go 9.7 KB

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