cluster_handler_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. package api_test
  2. import (
  3. "encoding/json"
  4. "net/http"
  5. "net/http/httptest"
  6. "strings"
  7. "testing"
  8. "github.com/porter-dev/porter/internal/kubernetes/fixtures"
  9. "github.com/porter-dev/porter/internal/models/integrations"
  10. "gorm.io/gorm"
  11. "github.com/go-test/deep"
  12. "github.com/porter-dev/porter/internal/forms"
  13. "github.com/porter-dev/porter/internal/models"
  14. )
  15. // ------------------------- TEST TYPES AND MAIN LOOP ------------------------- //
  16. type clusterTest struct {
  17. initializers []func(t *tester)
  18. msg string
  19. method string
  20. endpoint string
  21. body string
  22. expStatus int
  23. expBody string
  24. useCookie bool
  25. validators []func(c *clusterTest, tester *tester, t *testing.T)
  26. }
  27. func testClusterRequests(t *testing.T, tests []*clusterTest, canQuery bool) {
  28. for _, c := range tests {
  29. // create a new tester
  30. tester := newTester(canQuery)
  31. // if there's an initializer, call it
  32. for _, init := range c.initializers {
  33. init(tester)
  34. }
  35. req, err := http.NewRequest(
  36. c.method,
  37. c.endpoint,
  38. strings.NewReader(c.body),
  39. )
  40. tester.req = req
  41. if c.useCookie {
  42. req.AddCookie(tester.cookie)
  43. }
  44. if err != nil {
  45. t.Fatal(err)
  46. }
  47. tester.execute()
  48. rr := tester.rr
  49. // first, check that the status matches
  50. if status := rr.Code; status != c.expStatus {
  51. t.Errorf("%s, handler returned wrong status code: got %v want %v",
  52. c.msg, status, c.expStatus)
  53. }
  54. // if there's a validator, call it
  55. for _, validate := range c.validators {
  56. validate(c, tester, t)
  57. }
  58. }
  59. }
  60. // ------------------------- TEST FIXTURES AND FUNCTIONS ------------------------- //
  61. var createClusterTests = []*clusterTest{
  62. &clusterTest{
  63. initializers: []func(t *tester){
  64. initUserDefault,
  65. initProject,
  66. },
  67. msg: "Create cluster",
  68. method: "POST",
  69. endpoint: "/api/projects/1/clusters",
  70. body: `{"name":"cluster-test","server":"https://10.10.10.10:6443","aws_integration_id":1}`,
  71. expStatus: http.StatusCreated,
  72. expBody: `{"id":1,"project_id":1,"name":"cluster-test","server":"https://10.10.10.10:6443","service":"eks"}`,
  73. useCookie: true,
  74. validators: []func(c *clusterTest, tester *tester, t *testing.T){
  75. projectClusterBodyValidator,
  76. },
  77. },
  78. }
  79. func TestHandleCreateCluster(t *testing.T) {
  80. testRegistryRequests(t, createRegistryTests, true)
  81. }
  82. var readProjectClusterTest = []*clusterTest{
  83. &clusterTest{
  84. initializers: []func(t *tester){
  85. initUserDefault,
  86. initProject,
  87. initProjectClusterDefault,
  88. },
  89. msg: "Read project cluster",
  90. method: "GET",
  91. endpoint: "/api/projects/1/clusters/1",
  92. body: ``,
  93. expStatus: http.StatusOK,
  94. expBody: `{"id":1,"project_id":1,"name":"cluster-test","server":"https://10.10.10.10","service":"kube"}`,
  95. useCookie: true,
  96. validators: []func(c *clusterTest, tester *tester, t *testing.T){
  97. projectClusterBodyValidator,
  98. },
  99. },
  100. }
  101. func TestHandleReadProjectCluster(t *testing.T) {
  102. testClusterRequests(t, readProjectClusterTest, true)
  103. }
  104. var listProjectClustersTest = []*clusterTest{
  105. &clusterTest{
  106. initializers: []func(t *tester){
  107. initUserDefault,
  108. initProject,
  109. initProjectClusterDefault,
  110. },
  111. msg: "List project clusters",
  112. method: "GET",
  113. endpoint: "/api/projects/1/clusters",
  114. body: ``,
  115. expStatus: http.StatusOK,
  116. expBody: `[{"id":1,"project_id":1,"name":"cluster-test","server":"https://10.10.10.10","service":"kube"}]`,
  117. useCookie: true,
  118. validators: []func(c *clusterTest, tester *tester, t *testing.T){
  119. projectClustersBodyValidator,
  120. },
  121. },
  122. }
  123. func TestHandleListProjectClusters(t *testing.T) {
  124. testClusterRequests(t, listProjectClustersTest, true)
  125. }
  126. var updateClusterTests = []*clusterTest{
  127. &clusterTest{
  128. initializers: []func(t *tester){
  129. initUserDefault,
  130. initProject,
  131. initProjectClusterDefault,
  132. },
  133. msg: "Update cluster name",
  134. method: "POST",
  135. endpoint: "/api/projects/1/clusters/1",
  136. body: `{"name":"cluster-new-name"}`,
  137. expStatus: http.StatusOK,
  138. expBody: `{"id":1,"project_id":1,"name":"cluster-new-name","server":"https://10.10.10.10","service":"kube"}`,
  139. useCookie: true,
  140. validators: []func(c *clusterTest, tester *tester, t *testing.T){
  141. projectClusterBodyValidator,
  142. },
  143. },
  144. }
  145. func TestHandleUpdateCluster(t *testing.T) {
  146. testClusterRequests(t, updateClusterTests, true)
  147. }
  148. var deleteClusterTests = []*clusterTest{
  149. &clusterTest{
  150. initializers: []func(t *tester){
  151. initUserDefault,
  152. initProject,
  153. initProjectClusterDefault,
  154. },
  155. msg: "Delete cluster",
  156. method: "DELETE",
  157. endpoint: "/api/projects/1/clusters/1",
  158. body: ``,
  159. expStatus: http.StatusOK,
  160. expBody: ``,
  161. useCookie: true,
  162. validators: []func(c *clusterTest, tester *tester, t *testing.T){
  163. func(c *clusterTest, tester *tester, t *testing.T) {
  164. req, err := http.NewRequest(
  165. "GET",
  166. "/api/projects/1/clusters/1",
  167. strings.NewReader(""),
  168. )
  169. req.AddCookie(tester.cookie)
  170. if err != nil {
  171. t.Fatal(err)
  172. }
  173. rr2 := httptest.NewRecorder()
  174. tester.router.ServeHTTP(rr2, req)
  175. if status := rr2.Code; status != 403 {
  176. t.Errorf("DELETE cluster validation, handler returned wrong status code: got %v want %v",
  177. status, 403)
  178. }
  179. },
  180. },
  181. },
  182. }
  183. func TestHandleDeleteCluster(t *testing.T) {
  184. testClusterRequests(t, deleteClusterTests, true)
  185. }
  186. var createProjectClusterCandidatesTests = []*clusterTest{
  187. &clusterTest{
  188. initializers: []func(t *tester){
  189. initUserDefault,
  190. initProject,
  191. },
  192. msg: "Create project cluster candidate w/ no actions -- should create SA by default",
  193. method: "POST",
  194. endpoint: "/api/projects/1/clusters/candidates",
  195. body: `{"kubeconfig":"` + OIDCAuthWithDataForJSON + `"}`,
  196. expStatus: http.StatusCreated,
  197. expBody: `[{"id":1,"resolvers":[],"created_cluster_id":1,"project_id":1,"context_name":"context-test","name":"cluster-test","server":"https://10.10.10.10"}]`,
  198. useCookie: true,
  199. validators: []func(c *clusterTest, tester *tester, t *testing.T){
  200. projectClusterCandidateBodyValidator,
  201. // check that Cluster was created by default
  202. func(c *clusterTest, tester *tester, t *testing.T) {
  203. clusters, err := tester.repo.Cluster.ListClustersByProjectID(1)
  204. if err != nil {
  205. t.Fatalf("%v\n", err)
  206. }
  207. if len(clusters) != 1 {
  208. t.Fatal("Expected cluster to be created by default, but does not exist\n")
  209. }
  210. gotCluster := clusters[0]
  211. gotCluster.Model = gorm.Model{}
  212. expCluster := &models.Cluster{
  213. AuthMechanism: models.OIDC,
  214. ProjectID: 1,
  215. Name: "cluster-test",
  216. Server: "https://10.10.10.10",
  217. OIDCIntegrationID: 1,
  218. TokenCache: integrations.TokenCache{},
  219. CertificateAuthorityData: []byte("-----BEGIN CER"),
  220. }
  221. if diff := deep.Equal(gotCluster, expCluster); diff != nil {
  222. t.Errorf("handler returned wrong body:\n")
  223. t.Error(diff)
  224. }
  225. },
  226. },
  227. },
  228. &clusterTest{
  229. initializers: []func(t *tester){
  230. initUserDefault,
  231. initProject,
  232. },
  233. msg: "Create project SA candidate",
  234. method: "POST",
  235. endpoint: "/api/projects/1/clusters/candidates",
  236. body: `{"kubeconfig":"` + OIDCAuthWithoutDataForJSON + `"}`,
  237. expStatus: http.StatusCreated,
  238. expBody: `[{"id":1,"resolvers":[{"name":"upload-oidc-idp-issuer-ca-data","data":{"filename":"/fake/path/to/ca.pem"},"docs":"https://github.com/porter-dev/porter","resolved":false,"fields":"oidc_idp_issuer_ca_data"}],"created_cluster_id":0,"project_id":1,"context_name":"context-test","name":"cluster-test","server":"https://10.10.10.10"}]`,
  239. useCookie: true,
  240. validators: []func(c *clusterTest, tester *tester, t *testing.T){
  241. projectClusterCandidateBodyValidator,
  242. },
  243. },
  244. }
  245. func TestHandleCreateProjectClusterCandidate(t *testing.T) {
  246. testClusterRequests(t, createProjectClusterCandidatesTests, true)
  247. }
  248. var listProjectClusterCandidatesTests = []*clusterTest{
  249. &clusterTest{
  250. initializers: []func(t *tester){
  251. initUserDefault,
  252. initProject,
  253. initProjectClusterCandidate,
  254. },
  255. msg: "List project cluster candidates",
  256. method: "GET",
  257. endpoint: "/api/projects/1/clusters/candidates",
  258. body: ``,
  259. expStatus: http.StatusOK,
  260. expBody: `[{"id":1,"resolvers":[{"name":"upload-oidc-idp-issuer-ca-data","data":{"filename":"/fake/path/to/ca.pem"},"docs":"https://github.com/porter-dev/porter","resolved":false,"fields":"oidc_idp_issuer_ca_data"}],"created_cluster_id":0,"project_id":1,"context_name":"context-test","name":"cluster-test","server":"https://10.10.10.10"}]`,
  261. useCookie: true,
  262. validators: []func(c *clusterTest, tester *tester, t *testing.T){
  263. projectClusterCandidateBodyValidator,
  264. },
  265. },
  266. }
  267. func TestHandleListProjectClusterCandidates(t *testing.T) {
  268. testClusterRequests(t, listProjectClusterCandidatesTests, true)
  269. }
  270. var resolveProjectClusterCandidatesTests = []*clusterTest{
  271. &clusterTest{
  272. initializers: []func(t *tester){
  273. initUserDefault,
  274. initProject,
  275. initProjectClusterCandidate,
  276. },
  277. msg: "Resolve project cluster candidate",
  278. method: "POST",
  279. endpoint: "/api/projects/1/clusters/candidates/1/resolve",
  280. body: `{"oidc_idp_issuer_ca_data": "LS0tLS1CRUdJTiBDRVJ="}`,
  281. expStatus: http.StatusCreated,
  282. expBody: `{"id":1,"project_id":1,"name":"cluster-test","server":"https://10.10.10.10","service":"kube"}`,
  283. useCookie: true,
  284. validators: []func(c *clusterTest, tester *tester, t *testing.T){
  285. projectClusterBodyValidator,
  286. },
  287. },
  288. }
  289. func TestHandleResolveProjectClusterCandidate(t *testing.T) {
  290. testClusterRequests(t, resolveProjectClusterCandidatesTests, true)
  291. }
  292. // ------------------------- INITIALIZERS AND VALIDATORS ------------------------- //
  293. func initProjectClusterCandidate(tester *tester) {
  294. proj, _ := tester.repo.Project.ReadProject(1)
  295. form := &forms.CreateClusterCandidatesForm{
  296. ProjectID: proj.ID,
  297. Kubeconfig: fixtures.OIDCAuthWithoutData,
  298. }
  299. // convert the form to a ServiceAccountCandidate
  300. ccs, _ := form.ToClusterCandidates(false)
  301. for _, cc := range ccs {
  302. tester.repo.Cluster.CreateClusterCandidate(cc)
  303. }
  304. }
  305. func initProjectClusterDefault(tester *tester) {
  306. proj, _ := tester.repo.Project.ReadProject(1)
  307. form := &forms.CreateClusterCandidatesForm{
  308. ProjectID: proj.ID,
  309. Kubeconfig: fixtures.OIDCAuthWithData,
  310. }
  311. // convert the form to a ServiceAccountCandidate
  312. ccs, _ := form.ToClusterCandidates(false)
  313. for _, cc := range ccs {
  314. tester.repo.Cluster.CreateClusterCandidate(cc)
  315. }
  316. clusterForm := forms.ResolveClusterForm{
  317. Resolver: &models.ClusterResolverAll{},
  318. ClusterCandidateID: 1,
  319. ProjectID: 1,
  320. UserID: 1,
  321. }
  322. clusterForm.ResolveIntegration(*tester.repo)
  323. clusterForm.ResolveCluster(*tester.repo)
  324. }
  325. func projectClusterCandidateBodyValidator(c *clusterTest, tester *tester, t *testing.T) {
  326. gotBody := make([]*models.ClusterCandidateExternal, 0)
  327. expBody := make([]*models.ClusterCandidateExternal, 0)
  328. json.Unmarshal(tester.rr.Body.Bytes(), &gotBody)
  329. json.Unmarshal([]byte(c.expBody), &expBody)
  330. if diff := deep.Equal(gotBody, expBody); diff != nil {
  331. t.Errorf("handler returned wrong body:\n")
  332. t.Error(diff)
  333. }
  334. }
  335. func projectClusterBodyValidator(c *clusterTest, tester *tester, t *testing.T) {
  336. gotBody := &models.ClusterExternal{}
  337. expBody := &models.ClusterExternal{}
  338. json.Unmarshal(tester.rr.Body.Bytes(), gotBody)
  339. json.Unmarshal([]byte(c.expBody), expBody)
  340. if diff := deep.Equal(gotBody, expBody); diff != nil {
  341. t.Errorf("handler returned wrong body:\n")
  342. t.Error(diff)
  343. }
  344. }
  345. func projectClustersBodyValidator(c *clusterTest, tester *tester, t *testing.T) {
  346. gotBody := make([]*models.ClusterExternal, 0)
  347. expBody := make([]*models.ClusterExternal, 0)
  348. json.Unmarshal(tester.rr.Body.Bytes(), &gotBody)
  349. json.Unmarshal([]byte(c.expBody), &expBody)
  350. if diff := deep.Equal(gotBody, expBody); diff != nil {
  351. t.Errorf("handler returned wrong body:\n")
  352. t.Error(diff)
  353. }
  354. }
  355. const OIDCAuthWithDataForJSON string = `apiVersion: v1\nclusters:\n- cluster:\n server: https://10.10.10.10\n certificate-authority-data: LS0tLS1CRUdJTiBDRVJ=\n name: cluster-test\ncontexts:\n- context:\n cluster: cluster-test\n user: test-admin\n name: context-test\ncurrent-context: context-test\nkind: Config\npreferences: {}\nusers:\n- name: test-admin\n user:\n auth-provider:\n config:\n client-id: porter-api\n id-token: token\n idp-issuer-url: https://10.10.10.10\n idp-certificate-authority-data: LS0tLS1CRUdJTiBDRVJ=\n name: oidc`
  356. const OIDCAuthWithoutDataForJSON string = `apiVersion: v1\nclusters:\n- cluster:\n server: https://10.10.10.10\n certificate-authority-data: LS0tLS1CRUdJTiBDRVJ=\n name: cluster-test\ncontexts:\n- context:\n cluster: cluster-test\n user: test-admin\n name: context-test\ncurrent-context: context-test\nkind: Config\npreferences: {}\nusers:\n- name: test-admin\n user:\n auth-provider:\n config:\n client-id: porter-api\n id-token: token\n idp-issuer-url: https://10.10.10.10\n idp-certificate-authority: /fake/path/to/ca.pem\n name: oidc`
  357. const OIDCAuthWithoutData string = `
  358. apiVersion: v1
  359. clusters:
  360. - cluster:
  361. server: https://10.10.10.10
  362. certificate-authority-data: LS0tLS1CRUdJTiBDRVJ=
  363. name: cluster-test
  364. contexts:
  365. - context:
  366. cluster: cluster-test
  367. user: test-admin
  368. name: context-test
  369. current-context: context-test
  370. kind: Config
  371. preferences: {}
  372. users:
  373. - name: test-admin
  374. user:
  375. auth-provider:
  376. config:
  377. client-id: porter-api
  378. id-token: token
  379. idp-issuer-url: https://10.10.10.10
  380. idp-certificate-authority: /fake/path/to/ca.pem
  381. name: oidc
  382. `
  383. const OIDCAuthWithData string = `
  384. apiVersion: v1
  385. clusters:
  386. - cluster:
  387. server: https://10.10.10.10
  388. certificate-authority-data: LS0tLS1CRUdJTiBDRVJ=
  389. name: cluster-test
  390. contexts:
  391. - context:
  392. cluster: cluster-test
  393. user: test-admin
  394. name: context-test
  395. current-context: context-test
  396. kind: Config
  397. preferences: {}
  398. users:
  399. - name: test-admin
  400. user:
  401. auth-provider:
  402. config:
  403. client-id: porter-api
  404. id-token: token
  405. idp-issuer-url: https://10.10.10.10
  406. idp-certificate-authority-data: LS0tLS1CRUdJTiBDRVJ=
  407. name: oidc
  408. `