|
|
@@ -0,0 +1,449 @@
|
|
|
+package api_test
|
|
|
+
|
|
|
+import (
|
|
|
+ "encoding/json"
|
|
|
+ "net/http"
|
|
|
+ "net/http/httptest"
|
|
|
+ "strings"
|
|
|
+ "testing"
|
|
|
+
|
|
|
+ "github.com/porter-dev/porter/internal/kubernetes/fixtures"
|
|
|
+ "github.com/porter-dev/porter/internal/models/integrations"
|
|
|
+ "gorm.io/gorm"
|
|
|
+
|
|
|
+ "github.com/go-test/deep"
|
|
|
+ "github.com/porter-dev/porter/internal/forms"
|
|
|
+ "github.com/porter-dev/porter/internal/models"
|
|
|
+)
|
|
|
+
|
|
|
+// ------------------------- TEST TYPES AND MAIN LOOP ------------------------- //
|
|
|
+
|
|
|
+type clusterTest struct {
|
|
|
+ initializers []func(t *tester)
|
|
|
+ msg string
|
|
|
+ method string
|
|
|
+ endpoint string
|
|
|
+ body string
|
|
|
+ expStatus int
|
|
|
+ expBody string
|
|
|
+ useCookie bool
|
|
|
+ validators []func(c *clusterTest, tester *tester, t *testing.T)
|
|
|
+}
|
|
|
+
|
|
|
+func testClusterRequests(t *testing.T, tests []*clusterTest, canQuery bool) {
|
|
|
+ for _, c := range tests {
|
|
|
+ // create a new tester
|
|
|
+ tester := newTester(canQuery)
|
|
|
+
|
|
|
+ // if there's an initializer, call it
|
|
|
+ for _, init := range c.initializers {
|
|
|
+ init(tester)
|
|
|
+ }
|
|
|
+
|
|
|
+ req, err := http.NewRequest(
|
|
|
+ c.method,
|
|
|
+ c.endpoint,
|
|
|
+ strings.NewReader(c.body),
|
|
|
+ )
|
|
|
+
|
|
|
+ tester.req = req
|
|
|
+
|
|
|
+ if c.useCookie {
|
|
|
+ req.AddCookie(tester.cookie)
|
|
|
+ }
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+
|
|
|
+ tester.execute()
|
|
|
+ rr := tester.rr
|
|
|
+
|
|
|
+ // first, check that the status matches
|
|
|
+ if status := rr.Code; status != c.expStatus {
|
|
|
+ t.Errorf("%s, handler returned wrong status code: got %v want %v",
|
|
|
+ c.msg, status, c.expStatus)
|
|
|
+ }
|
|
|
+
|
|
|
+ // if there's a validator, call it
|
|
|
+ for _, validate := range c.validators {
|
|
|
+ validate(c, tester, t)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// ------------------------- TEST FIXTURES AND FUNCTIONS ------------------------- //
|
|
|
+
|
|
|
+var createClusterTests = []*clusterTest{
|
|
|
+ &clusterTest{
|
|
|
+ initializers: []func(t *tester){
|
|
|
+ initUserDefault,
|
|
|
+ initProject,
|
|
|
+ },
|
|
|
+ msg: "Create cluster",
|
|
|
+ method: "POST",
|
|
|
+ endpoint: "/api/projects/1/clusters",
|
|
|
+ body: `{"name":"cluster-test","server":"https://10.10.10.10:6443","aws_integration_id":1}`,
|
|
|
+ expStatus: http.StatusCreated,
|
|
|
+ expBody: `{"id":1,"project_id":1,"name":"cluster-test","server":"https://10.10.10.10:6443","service":"eks"}`,
|
|
|
+ useCookie: true,
|
|
|
+ validators: []func(c *clusterTest, tester *tester, t *testing.T){
|
|
|
+ projectClusterBodyValidator,
|
|
|
+ },
|
|
|
+ },
|
|
|
+}
|
|
|
+
|
|
|
+func TestHandleCreateCluster(t *testing.T) {
|
|
|
+ testRegistryRequests(t, createRegistryTests, true)
|
|
|
+}
|
|
|
+
|
|
|
+var readProjectClusterTest = []*clusterTest{
|
|
|
+ &clusterTest{
|
|
|
+ initializers: []func(t *tester){
|
|
|
+ initUserDefault,
|
|
|
+ initProject,
|
|
|
+ initProjectClusterDefault,
|
|
|
+ },
|
|
|
+ msg: "Read project cluster",
|
|
|
+ method: "GET",
|
|
|
+ endpoint: "/api/projects/1/clusters/1",
|
|
|
+ body: ``,
|
|
|
+ expStatus: http.StatusOK,
|
|
|
+ expBody: `{"id":1,"project_id":1,"name":"cluster-test","server":"https://10.10.10.10","service":"kube"}`,
|
|
|
+ useCookie: true,
|
|
|
+ validators: []func(c *clusterTest, tester *tester, t *testing.T){
|
|
|
+ projectClusterBodyValidator,
|
|
|
+ },
|
|
|
+ },
|
|
|
+}
|
|
|
+
|
|
|
+func TestHandleReadProjectCluster(t *testing.T) {
|
|
|
+ testClusterRequests(t, readProjectClusterTest, true)
|
|
|
+}
|
|
|
+
|
|
|
+var listProjectClustersTest = []*clusterTest{
|
|
|
+ &clusterTest{
|
|
|
+ initializers: []func(t *tester){
|
|
|
+ initUserDefault,
|
|
|
+ initProject,
|
|
|
+ initProjectClusterDefault,
|
|
|
+ },
|
|
|
+ msg: "List project clusters",
|
|
|
+ method: "GET",
|
|
|
+ endpoint: "/api/projects/1/clusters",
|
|
|
+ body: ``,
|
|
|
+ expStatus: http.StatusOK,
|
|
|
+ expBody: `[{"id":1,"project_id":1,"name":"cluster-test","server":"https://10.10.10.10","service":"kube"}]`,
|
|
|
+ useCookie: true,
|
|
|
+ validators: []func(c *clusterTest, tester *tester, t *testing.T){
|
|
|
+ projectClustersBodyValidator,
|
|
|
+ },
|
|
|
+ },
|
|
|
+}
|
|
|
+
|
|
|
+func TestHandleListProjectClusters(t *testing.T) {
|
|
|
+ testClusterRequests(t, listProjectClustersTest, true)
|
|
|
+}
|
|
|
+
|
|
|
+var deleteClusterTests = []*clusterTest{
|
|
|
+ &clusterTest{
|
|
|
+ initializers: []func(t *tester){
|
|
|
+ initUserDefault,
|
|
|
+ initProject,
|
|
|
+ initProjectClusterDefault,
|
|
|
+ },
|
|
|
+ msg: "Delete cluster",
|
|
|
+ method: "DELETE",
|
|
|
+ endpoint: "/api/projects/1/clusters/1",
|
|
|
+ body: ``,
|
|
|
+ expStatus: http.StatusOK,
|
|
|
+ expBody: ``,
|
|
|
+ useCookie: true,
|
|
|
+ validators: []func(c *clusterTest, tester *tester, t *testing.T){
|
|
|
+ func(c *clusterTest, tester *tester, t *testing.T) {
|
|
|
+ req, err := http.NewRequest(
|
|
|
+ "GET",
|
|
|
+ "/api/projects/1/clusters/1",
|
|
|
+ strings.NewReader(""),
|
|
|
+ )
|
|
|
+
|
|
|
+ req.AddCookie(tester.cookie)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+
|
|
|
+ rr2 := httptest.NewRecorder()
|
|
|
+
|
|
|
+ tester.router.ServeHTTP(rr2, req)
|
|
|
+
|
|
|
+ if status := rr2.Code; status != 403 {
|
|
|
+ t.Errorf("DELETE cluster validation, handler returned wrong status code: got %v want %v",
|
|
|
+ status, 403)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+}
|
|
|
+
|
|
|
+func TestHandleDeleteCluster(t *testing.T) {
|
|
|
+ testClusterRequests(t, deleteClusterTests, true)
|
|
|
+}
|
|
|
+
|
|
|
+var createProjectClusterCandidatesTests = []*clusterTest{
|
|
|
+ &clusterTest{
|
|
|
+ initializers: []func(t *tester){
|
|
|
+ initUserDefault,
|
|
|
+ initProject,
|
|
|
+ },
|
|
|
+ msg: "Create project cluster candidate w/ no actions -- should create SA by default",
|
|
|
+ method: "POST",
|
|
|
+ endpoint: "/api/projects/1/clusters/candidates",
|
|
|
+ body: `{"kubeconfig":"` + OIDCAuthWithDataForJSON + `"}`,
|
|
|
+ expStatus: http.StatusCreated,
|
|
|
+ expBody: `[{"id":1,"resolvers":[],"created_cluster_id":1,"project_id":1,"context_name":"context-test","name":"cluster-test","server":"https://10.10.10.10"}]`,
|
|
|
+ useCookie: true,
|
|
|
+ validators: []func(c *clusterTest, tester *tester, t *testing.T){
|
|
|
+ projectClusterCandidateBodyValidator,
|
|
|
+ // check that Cluster was created by default
|
|
|
+ func(c *clusterTest, tester *tester, t *testing.T) {
|
|
|
+ clusters, err := tester.repo.Cluster.ListClustersByProjectID(1)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ t.Fatalf("%v\n", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ if len(clusters) != 1 {
|
|
|
+ t.Fatal("Expected cluster to be created by default, but does not exist\n")
|
|
|
+ }
|
|
|
+
|
|
|
+ gotCluster := clusters[0]
|
|
|
+ gotCluster.Model = gorm.Model{}
|
|
|
+
|
|
|
+ expCluster := &models.Cluster{
|
|
|
+ AuthMechanism: models.OIDC,
|
|
|
+ ProjectID: 1,
|
|
|
+ Name: "cluster-test",
|
|
|
+ Server: "https://10.10.10.10",
|
|
|
+ OIDCIntegrationID: 1,
|
|
|
+ TokenCache: integrations.TokenCache{},
|
|
|
+ CertificateAuthorityData: []byte("-----BEGIN CER"),
|
|
|
+ }
|
|
|
+
|
|
|
+ if diff := deep.Equal(gotCluster, expCluster); diff != nil {
|
|
|
+ t.Errorf("handler returned wrong body:\n")
|
|
|
+ t.Error(diff)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ &clusterTest{
|
|
|
+ initializers: []func(t *tester){
|
|
|
+ initUserDefault,
|
|
|
+ initProject,
|
|
|
+ },
|
|
|
+ msg: "Create project SA candidate",
|
|
|
+ method: "POST",
|
|
|
+ endpoint: "/api/projects/1/clusters/candidates",
|
|
|
+ body: `{"kubeconfig":"` + OIDCAuthWithoutDataForJSON + `"}`,
|
|
|
+ expStatus: http.StatusCreated,
|
|
|
+ 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"}]`,
|
|
|
+ useCookie: true,
|
|
|
+ validators: []func(c *clusterTest, tester *tester, t *testing.T){
|
|
|
+ projectClusterCandidateBodyValidator,
|
|
|
+ },
|
|
|
+ },
|
|
|
+}
|
|
|
+
|
|
|
+func TestHandleCreateProjectClusterCandidate(t *testing.T) {
|
|
|
+ testClusterRequests(t, createProjectClusterCandidatesTests, true)
|
|
|
+}
|
|
|
+
|
|
|
+var listProjectClusterCandidatesTests = []*clusterTest{
|
|
|
+ &clusterTest{
|
|
|
+ initializers: []func(t *tester){
|
|
|
+ initUserDefault,
|
|
|
+ initProject,
|
|
|
+ initProjectClusterCandidate,
|
|
|
+ },
|
|
|
+ msg: "List project cluster candidates",
|
|
|
+ method: "GET",
|
|
|
+ endpoint: "/api/projects/1/clusters/candidates",
|
|
|
+ body: ``,
|
|
|
+ expStatus: http.StatusOK,
|
|
|
+ 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"}]`,
|
|
|
+ useCookie: true,
|
|
|
+ validators: []func(c *clusterTest, tester *tester, t *testing.T){
|
|
|
+ projectClusterCandidateBodyValidator,
|
|
|
+ },
|
|
|
+ },
|
|
|
+}
|
|
|
+
|
|
|
+func TestHandleListProjectClusterCandidates(t *testing.T) {
|
|
|
+ testClusterRequests(t, listProjectClusterCandidatesTests, true)
|
|
|
+}
|
|
|
+
|
|
|
+var resolveProjectClusterCandidatesTests = []*clusterTest{
|
|
|
+ &clusterTest{
|
|
|
+ initializers: []func(t *tester){
|
|
|
+ initUserDefault,
|
|
|
+ initProject,
|
|
|
+ initProjectClusterCandidate,
|
|
|
+ },
|
|
|
+ msg: "Resolve project cluster candidate",
|
|
|
+ method: "POST",
|
|
|
+ endpoint: "/api/projects/1/clusters/candidates/1/resolve",
|
|
|
+ body: `{"oidc_idp_issuer_ca_data": "LS0tLS1CRUdJTiBDRVJ="}`,
|
|
|
+ expStatus: http.StatusCreated,
|
|
|
+ expBody: `{"id":1,"project_id":1,"name":"cluster-test","server":"https://10.10.10.10","service":"kube"}`,
|
|
|
+ useCookie: true,
|
|
|
+ validators: []func(c *clusterTest, tester *tester, t *testing.T){
|
|
|
+ projectClusterBodyValidator,
|
|
|
+ },
|
|
|
+ },
|
|
|
+}
|
|
|
+
|
|
|
+func TestHandleResolveProjectClusterCandidate(t *testing.T) {
|
|
|
+ testClusterRequests(t, resolveProjectClusterCandidatesTests, true)
|
|
|
+}
|
|
|
+
|
|
|
+// ------------------------- INITIALIZERS AND VALIDATORS ------------------------- //
|
|
|
+
|
|
|
+func initProjectClusterCandidate(tester *tester) {
|
|
|
+ proj, _ := tester.repo.Project.ReadProject(1)
|
|
|
+
|
|
|
+ form := &forms.CreateClusterCandidatesForm{
|
|
|
+ ProjectID: proj.ID,
|
|
|
+ Kubeconfig: fixtures.OIDCAuthWithoutData,
|
|
|
+ }
|
|
|
+
|
|
|
+ // convert the form to a ServiceAccountCandidate
|
|
|
+ ccs, _ := form.ToClusterCandidates(false)
|
|
|
+
|
|
|
+ for _, cc := range ccs {
|
|
|
+ tester.repo.Cluster.CreateClusterCandidate(cc)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func initProjectClusterDefault(tester *tester) {
|
|
|
+ proj, _ := tester.repo.Project.ReadProject(1)
|
|
|
+
|
|
|
+ form := &forms.CreateClusterCandidatesForm{
|
|
|
+ ProjectID: proj.ID,
|
|
|
+ Kubeconfig: fixtures.OIDCAuthWithData,
|
|
|
+ }
|
|
|
+
|
|
|
+ // convert the form to a ServiceAccountCandidate
|
|
|
+ ccs, _ := form.ToClusterCandidates(false)
|
|
|
+
|
|
|
+ for _, cc := range ccs {
|
|
|
+ tester.repo.Cluster.CreateClusterCandidate(cc)
|
|
|
+ }
|
|
|
+
|
|
|
+ clusterForm := forms.ResolveClusterForm{
|
|
|
+ Resolver: &models.ClusterResolverAll{},
|
|
|
+ ClusterCandidateID: 1,
|
|
|
+ ProjectID: 1,
|
|
|
+ UserID: 1,
|
|
|
+ }
|
|
|
+
|
|
|
+ clusterForm.ResolveIntegration(*tester.repo)
|
|
|
+ clusterForm.ResolveCluster(*tester.repo)
|
|
|
+}
|
|
|
+
|
|
|
+func projectClusterCandidateBodyValidator(c *clusterTest, tester *tester, t *testing.T) {
|
|
|
+ gotBody := make([]*models.ClusterCandidateExternal, 0)
|
|
|
+ expBody := make([]*models.ClusterCandidateExternal, 0)
|
|
|
+
|
|
|
+ json.Unmarshal(tester.rr.Body.Bytes(), &gotBody)
|
|
|
+ json.Unmarshal([]byte(c.expBody), &expBody)
|
|
|
+
|
|
|
+ if diff := deep.Equal(gotBody, expBody); diff != nil {
|
|
|
+ t.Errorf("handler returned wrong body:\n")
|
|
|
+ t.Error(diff)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func projectClusterBodyValidator(c *clusterTest, tester *tester, t *testing.T) {
|
|
|
+ gotBody := &models.ClusterExternal{}
|
|
|
+ expBody := &models.ClusterExternal{}
|
|
|
+
|
|
|
+ json.Unmarshal(tester.rr.Body.Bytes(), gotBody)
|
|
|
+ json.Unmarshal([]byte(c.expBody), expBody)
|
|
|
+
|
|
|
+ if diff := deep.Equal(gotBody, expBody); diff != nil {
|
|
|
+ t.Errorf("handler returned wrong body:\n")
|
|
|
+ t.Error(diff)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func projectClustersBodyValidator(c *clusterTest, tester *tester, t *testing.T) {
|
|
|
+ gotBody := make([]*models.ClusterExternal, 0)
|
|
|
+ expBody := make([]*models.ClusterExternal, 0)
|
|
|
+
|
|
|
+ json.Unmarshal(tester.rr.Body.Bytes(), &gotBody)
|
|
|
+ json.Unmarshal([]byte(c.expBody), &expBody)
|
|
|
+
|
|
|
+ if diff := deep.Equal(gotBody, expBody); diff != nil {
|
|
|
+ t.Errorf("handler returned wrong body:\n")
|
|
|
+ t.Error(diff)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+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`
|
|
|
+
|
|
|
+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`
|
|
|
+
|
|
|
+const OIDCAuthWithoutData string = `
|
|
|
+apiVersion: v1
|
|
|
+clusters:
|
|
|
+- cluster:
|
|
|
+ server: https://10.10.10.10
|
|
|
+ certificate-authority-data: LS0tLS1CRUdJTiBDRVJ=
|
|
|
+ name: cluster-test
|
|
|
+contexts:
|
|
|
+- context:
|
|
|
+ cluster: cluster-test
|
|
|
+ user: test-admin
|
|
|
+ name: context-test
|
|
|
+current-context: context-test
|
|
|
+kind: Config
|
|
|
+preferences: {}
|
|
|
+users:
|
|
|
+- name: test-admin
|
|
|
+ user:
|
|
|
+ auth-provider:
|
|
|
+ config:
|
|
|
+ client-id: porter-api
|
|
|
+ id-token: token
|
|
|
+ idp-issuer-url: https://10.10.10.10
|
|
|
+ idp-certificate-authority: /fake/path/to/ca.pem
|
|
|
+ name: oidc
|
|
|
+`
|
|
|
+
|
|
|
+const OIDCAuthWithData string = `
|
|
|
+apiVersion: v1
|
|
|
+clusters:
|
|
|
+- cluster:
|
|
|
+ server: https://10.10.10.10
|
|
|
+ certificate-authority-data: LS0tLS1CRUdJTiBDRVJ=
|
|
|
+ name: cluster-test
|
|
|
+contexts:
|
|
|
+- context:
|
|
|
+ cluster: cluster-test
|
|
|
+ user: test-admin
|
|
|
+ name: context-test
|
|
|
+current-context: context-test
|
|
|
+kind: Config
|
|
|
+preferences: {}
|
|
|
+users:
|
|
|
+- name: test-admin
|
|
|
+ user:
|
|
|
+ auth-provider:
|
|
|
+ config:
|
|
|
+ client-id: porter-api
|
|
|
+ id-token: token
|
|
|
+ idp-issuer-url: https://10.10.10.10
|
|
|
+ idp-certificate-authority-data: LS0tLS1CRUdJTiBDRVJ=
|
|
|
+ name: oidc
|
|
|
+`
|