Work on mock server.
diff --git a/README.md b/README.md index 134b6bd..86cfed4 100644 --- a/README.md +++ b/README.md
@@ -1,3 +1,7 @@ # Istio Apigee Adapter This workspace holds an apigee adapter for Istio. + +## Installation + +In order to use this, right now, you need a build of Istio \ No newline at end of file
diff --git a/WORKSPACE b/WORKSPACE index c29e9ff..a7ddb8c 100644 --- a/WORKSPACE +++ b/WORKSPACE
@@ -45,6 +45,13 @@ importpath = "github.com/golang/protobuf", ) +new_git_repository( + name = "com_github_googleapis_googleapis", + build_file = "bazel/BUILD.googleapis", + commit = "13ac2436c5e3d568bd0e938f6ed58b77a48aba15", + remote = "https://github.com/googleapis/googleapis.git", +) + new_go_repository( name = "com_github_hashicorp_go_multierror", commit = "ed905158d87462226a13fe39ddf685ea65f1c11f", @@ -57,9 +64,8 @@ importpath = "github.com/hashicorp/errwrap", ) -new_git_repository( - name = "com_github_googleapis_googleapis", - build_file = "bazel/BUILD.googleapis", - commit = "13ac2436c5e3d568bd0e938f6ed58b77a48aba15", - remote = "https://github.com/googleapis/googleapis.git", -) \ No newline at end of file +new_go_repository( + name = "com_github_julienschmidt_httprouter", + commit = "975b5c4c7c21c0e3d2764200bf2aa8e34657ae6e", + importpath = "github.com/julienschmidt/httprouter" +)
diff --git a/common/BUILD b/common/BUILD new file mode 100644 index 0000000..5c09cc7 --- /dev/null +++ b/common/BUILD
@@ -0,0 +1,12 @@ +package(default_visibility = ["//visibility:public"]) + +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "types.go", + ], + deps = [ + ], +)
diff --git a/common/types.go b/common/types.go new file mode 100644 index 0000000..da90279 --- /dev/null +++ b/common/types.go
@@ -0,0 +1,25 @@ +package common + +type Attribute struct { + Name string `json:"name"` + Value string `json:"value"` +} + +type APIProduct struct { + APIResources []string `json:"apiResources"` + ApprovalType string `json:"approvalType"` + Attributes []Attribute `json:"attributes"` + CreatedAt int64 `json:"createdAt"` + CreatedBy string `json:"createdBy"` + Description string `json:"description"` + DisplayName string `json:"displayName"` + Environments []string `json:"environments"` + LastModifiedAt int64 `json:"lastModifiedAt"` + LastModifiedBy string `json:"lastModifiedBy"` + Name string `json:"name"` + Proxies []string `json:"proxies"` + Quota string `json:"quota"` + QuotaInterval string `json:"quotaInterval"` + QuotaTimeUnit string `json:"quotaTimeUnit"` + Scopes []string `json:"scopes"` +}
diff --git a/mock/BUILD b/mock/BUILD new file mode 100644 index 0000000..84be835 --- /dev/null +++ b/mock/BUILD
@@ -0,0 +1,24 @@ +package(default_visibility = ["//visibility:public"]) + +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "keys.go", + "mockserver.go", + ], + deps = [ + "//common:go_default_library", + "@com_github_julienschmidt_httprouter//:go_default_library", + ], +) + +go_test( + name = "small_tests", + size = "small", + srcs = [ + "mockserver_test.go", + ], + library = ":go_default_library", +)
diff --git a/mock/keys.go b/mock/keys.go new file mode 100644 index 0000000..d5170cf --- /dev/null +++ b/mock/keys.go
@@ -0,0 +1,55 @@ +package mock + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "time" + "encoding/pem" + "math/big" +) + +var mockKeyPEM, mockCertPEM []byte + +func makeKeys() { + key, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + panic(err.Error()) + } + + mockKeyPEM = pem.EncodeToMemory(&pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(key), + }) + + templ := makeCertTemplate() + + certBytes, err := x509.CreateCertificate(rand.Reader, templ, templ, key.Public(), key) + if err != nil { + panic(err.Error()) + } + + mockCertPEM = pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: certBytes, + }) +} + +func makeCertTemplate() *x509.Certificate { + now := time.Now() + + return &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + Country: []string{"US"}, + Organization: []string{"Google"}, + OrganizationalUnit: []string{"Cloud"}, + Locality: []string{"Mountain View"}, + Province: []string{"CA"}, + CommonName: "mockserver", + }, + NotBefore: now, + NotAfter: now.Add(time.Hour), + } +} \ No newline at end of file
diff --git a/mock/mockserver.go b/mock/mockserver.go new file mode 100644 index 0000000..1d4392d --- /dev/null +++ b/mock/mockserver.go
@@ -0,0 +1,88 @@ +package mock + +import ( + "fmt" + "net" + "net/http" + "sync" + "encoding/json" + "time" + "github.com/julienschmidt/httprouter" + "github.com/apid/istioApigeeAdapter/common" +) + +var mockInit = &sync.Once{} + +type MockServer struct { + listener net.Listener +} + +func StartMockServer(port int) (*MockServer, error) { + mockInit.Do(makeKeys) + + l, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) + if err != nil { + return nil, err + } + + ms := &MockServer{ + listener: l, + } + + router := httprouter.New() + router.GET("/publicKey", ms.getPublicKey) + router.GET("/products", ms.getProducts) + + go func() { + http.Serve(l, router) + }() + + return ms, nil +} + +func (m *MockServer) Address() string { + return m.listener.Addr().String() +} + +func (m *MockServer) Stop() { + m.listener.Close() +} + +func (m *MockServer) getPublicKey(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + w.Header().Set("content-type", "text/plain") + w.Write(mockCertPEM) +} + +func (m *MockServer) getProducts(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + now := time.Now() + p := []common.APIProduct{ + { + Name: "First", + DisplayName: "First Product", + Description: "First test product", + CreatedAt: now.UnixNano(), + CreatedBy: "foo@bar.com", + LastModifiedAt: now.UnixNano(), + LastModifiedBy: "foo@bar.com", + + APIResources: []string{"/**"}, + ApprovalType: "auto", + Attributes: []common.Attribute{ + { + Name: "access", + Value: "public", + }, + }, + Environments: []string{"test", "prod"}, + Proxies: []string{"ProxyOne"}, + Quota: "10", + QuotaInterval: "1", + QuotaTimeUnit: "minute", + }, + } + + w.Header().Set("content-type", "application/json") + + enc := json.NewEncoder(w) + enc.Encode(p) +} \ No newline at end of file
diff --git a/mock/mockserver_test.go b/mock/mockserver_test.go new file mode 100644 index 0000000..8f8fd23 --- /dev/null +++ b/mock/mockserver_test.go
@@ -0,0 +1,88 @@ +package mock + +import ( + "fmt" + "net/http" + "os" + "testing" + "encoding/pem" + "encoding/json" + "io/ioutil" + "crypto/x509" + "github.com/apid/istioApigeeAdapter/common" +) + +var testMockServer *MockServer + +func TestMain(m *testing.M) { + var err error + testMockServer, err = StartMockServer(0) + if err != nil { + panic(err.Error()) + } + + result := m.Run() + testMockServer.Stop() + os.Exit(result) +} + +func TestServerSanity(t *testing.T) { + if mockKeyPEM == nil { + t.Fatal("Expected mock key to be generated") + } + if mockCertPEM == nil { + t.Fatal("Expected mock cert to be generated") + } +} + +func TestPublicKey(t *testing.T) { + resp, err := http.Get(fmt.Sprintf("http://%s/publicKey", testMockServer.Address())) + if err != nil { + t.Fatalf("Network error: %s", err) + } + defer resp.Body.Close() + if resp.StatusCode != 200 { + t.Fatalf("Got HTTP status code %d", resp.StatusCode) + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatalf("Error reading body: %s", err) + } + pb, _ := pem.Decode(body) + if pb == nil { + t.Fatalf("Failed decoding public key body \"%s\"", string(body)) + } + if pb.Type != "CERTIFICATE" { + t.Fatalf("Got back invalid PEM type %s", pb.Type) + } + + cert, err := x509.ParseCertificate(pb.Bytes) + if err != nil { + t.Fatalf("Error decoding certificate: %s", err) + } + if cert.Subject.CommonName != "mockserver" { + t.Fatalf("Common name does not match \"mockserver\": \"%s\"", cert.Subject.CommonName) + } +} + +func TestProducts(t *testing.T) { + resp, err := http.Get(fmt.Sprintf("http://%s/products", testMockServer.Address())) + if err != nil { + t.Fatalf("Network error: %s", err) + } + defer resp.Body.Close() + if resp.StatusCode != 200 { + t.Fatalf("Got HTTP status code %d", resp.StatusCode) + } + if resp.Header.Get("content-type") != "application/json" { + t.Fatalf("Invalid content typs %s", resp.Header.Get("content-type")) + } + + dec := json.NewDecoder(resp.Body) + var products []common.APIProduct + err = dec.Decode(&products) + if err != nil { + t.Fatalf("Error decoding response json: %s", err) + } +} \ No newline at end of file