fake_secret_api_test.go 10 KB

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