fake_secret_api_test.go 9.3 KB

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