cluster_handler_test.go 14 KB

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