Skip to content

Commit

Permalink
Merge pull request #2 from PatrickLaabs/development
Browse files Browse the repository at this point in the history
merging from Development branch
  • Loading branch information
PatrickLaabs authored Sep 17, 2024
2 parents 000ad61 + 1062b95 commit bb63425
Show file tree
Hide file tree
Showing 43 changed files with 1,663 additions and 363 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.air.toml
/tmp
6 changes: 5 additions & 1 deletion Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,8 @@ lint:

gen:
@echo "Generating templ files..."
{{TEMPL_CMD}} generate views
{{TEMPL_CMD}} generate ./frontend/views

swagger:
@echo "Generating API documentation..."
swag init -g ./api/start.go -o ./docs
29 changes: 21 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,28 @@
# eros

---
## Goals

* API First Backend
* APIs can be consumed, using the provided frontend, or design custom stuff around it.
*

## requirements

* Docker
* KinD
* Just

## Get started
Start the API Backend:
```shell
eros server start
```

Start the Frontend Server:
```shell
eros frontend start
```

You can now either open up the frontend application via `http://localhost:8080`
or do direct queries against the api, like ` curl localhost:3000/kubernetes/local/create`.

## Documentation
### Backend API
I am using the Swagger Tooling to generate the needed API documentations.
You can access the documentation with:
```shell
http://localhost:3000/swagger/index.html
```
27 changes: 27 additions & 0 deletions api/middleware/router.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
Copyright © 2024 Patrick Laabs patrick.laabs@me.com
*/

package middleware

import (
_ "github.com/PatrickLaabs/eros/docs"
"net/http"
)

func Router(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*") // Allows all origins; adjust as needed
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")

// Handle preflight requests
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusOK)
return
}

// Pass the request to the next handler in the chain
next.ServeHTTP(w, r)
})
}
22 changes: 14 additions & 8 deletions pkg/backend/routes/kubernetes.go → api/routes/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@ package routes

import (
"fmt"
_ "github.com/PatrickLaabs/eros/docs"
"github.com/PatrickLaabs/eros/pkg/kind"
"log"
"net/http"
"strings"
)

// Kubernetes is a Mocking and Test API Implementation
func Kubernetes(w http.ResponseWriter, r *http.Request) {
flavor := strings.TrimPrefix(r.URL.Path, "/kubernetes/")

clustername := "eros-bootstrap-cluster"
// Validate flavor input
if flavor == "" {
http.Error(w, "Invalid flavor", http.StatusBadRequest)
Expand All @@ -23,18 +25,22 @@ func Kubernetes(w http.ResponseWriter, r *http.Request) {

// Handle different flavors
switch flavor {
case "local":
fmt.Fprint(w, "Starting local cluster")
kind.Create()
case "local/create":
fmt.Fprint(w, "Starting local cluster:\n")
kind.Create(clustername, w, r)
case "local/delete":
fmt.Fprint(w, "Deleting local cluster")
kind.Delete()
fmt.Fprint(w, "Deleting local cluster:\n")
err := kind.Delete(clustername)
if err != nil {
log.Printf("error deleting cluster: %v", err)
}
case "local/getclusters":
fmt.Fprint(w, "Getting local clusters")
fmt.Fprint(w, "Getting local clusters:\n")
kind.GetClusters(w, r)
case "gcp":
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, "Starting gcp cluster")
default:
http.Error(w, "Unsupported flavor", http.StatusBadRequest)
}

}
130 changes: 130 additions & 0 deletions api/routes/kubernetes_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
Copyright © 2024 Patrick Laabs patrick.laabs@me.com
*/

package routes

import (
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"
)

// Mock the kind package and its GetClusters function
var clusterMock = struct {
GetClusters func(w http.ResponseWriter, r *http.Request)
}{
GetClusters: func(w http.ResponseWriter, r *http.Request) {
// Simulate response from the real GetClusters function
fmt.Fprint(w, "Mocked clusters data")
},
}

func TestKubernetes(t *testing.T) {
t.Run("runs on no given flavor, should error", func(t *testing.T) {
req, err := http.NewRequest("GET", "/kubernetes/", nil)
if err != nil {
t.Fatalf("Failed to create request: %v", err)
}

// Create a ResponseRecorder to record the response
rr := httptest.NewRecorder()

// Call the Kubernetes function with the ResponseRecorder and the request
handler := http.HandlerFunc(Kubernetes)
handler.ServeHTTP(rr, req)

// Check the response status code
if status := rr.Code; status != http.StatusBadRequest {
t.Errorf("Handler returned wrong status code: got %v want %v", status, http.StatusBadRequest)
}

// Check the response body
expected := "Invalid flavor"
if !strings.Contains(rr.Body.String(), expected) {
t.Errorf("Handler returned unexpected body: got %v want %v", rr.Body.String(), expected)
}
})
t.Run("runs kubernetes endpoint using local flavor", func(t *testing.T) {

//// ToDo: Needs to be adjusted to the new way we do things
//request := testhelper.KubernetesFlavorRequest("local/create")
//response := httptest.NewRecorder()
//
//// ToDo: currently our test creates a cluster. We should mock this
//Kubernetes(response, request)
////assertStatus(t, response.Code, http.StatusOK)
//testhelper.AssertResponseBody(t, response.Body.String(), "Starting local cluster")
})
t.Run("running test for getclusters endpoint", func(t *testing.T) {
req, err := http.NewRequest("GET", "/kubernetes/local/getclusters", nil)
if err != nil {
t.Fatalf("Failed to create request: %v", err)
}

rr := httptest.NewRecorder()

handler := http.HandlerFunc(Kubernetes)
handler.ServeHTTP(rr, req)

if status := rr.Code; status != http.StatusOK {
t.Errorf("Handler returned wrong status code: got %v want %v", status, http.StatusOK)
}

expectedBody := "Getting local clusters:\n[\"eros-bootstrap-cluster\"]"
if strings.TrimSpace(rr.Body.String()) != expectedBody {
t.Errorf("Handler returned unexpected body: got: %v want: %v", rr.Body.String(), expectedBody)
}
})
t.Run("run tests on gcp flavor", func(t *testing.T) {
// Create a new request with the "gcp" flavor (e.g., "/kubernetes/gcp")
req, err := http.NewRequest("GET", "/kubernetes/gcp", nil)
if err != nil {
t.Fatalf("Failed to create request: %v", err)
}

// Create a ResponseRecorder to record the response
rr := httptest.NewRecorder()

// Call the Kubernetes function with the ResponseRecorder and the request
handler := http.HandlerFunc(Kubernetes)
handler.ServeHTTP(rr, req)

// Check the response status code
if status := rr.Code; status != http.StatusOK {
t.Errorf("Handler returned wrong status code: got %v want %v", status, http.StatusOK)
}

// Check the response body
expected := "Starting gcp cluster"
if !strings.Contains(rr.Body.String(), expected) {
t.Errorf("Handler returned unexpected body: got %v want %v", rr.Body.String(), expected)
}
})
t.Run("runs kubernetes endpoint with the default case", func(t *testing.T) {
// Create a new request with an unsupported flavor (e.g., "/kubernetes/unsupported")
req, err := http.NewRequest("GET", "/kubernetes/unsupported", nil)
if err != nil {
t.Fatalf("Failed to create request: %v", err)
}

// Create a ResponseRecorder to record the response
rr := httptest.NewRecorder()

handler := http.HandlerFunc(Kubernetes)
handler.ServeHTTP(rr, req)

// Check the response status code
if status := rr.Code; status != http.StatusBadRequest {
t.Errorf("Handler returned wrong status code: got %v want %v", status, http.StatusBadRequest)
}

// Check the response body
expected := "Unsupported flavor"
if !strings.Contains(rr.Body.String(), expected) {
t.Errorf("Handler returned unexpected body: got %v want %v", rr.Body.String(), expected)
}
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,16 @@ Copyright © 2024 Patrick Laabs patrick.laabs@me.com
package routes

import (
_ "github.com/PatrickLaabs/eros/docs"
"net/http"
)

var routeMap = map[string]http.HandlerFunc{
"/version": Version,
"/kubernetes/": Kubernetes,
"/swagger/": Swagger,
}

//component := hello("John")
//
// http.Handle("/", templ.Handler(component))

// RegisterRoutes registers all routes with the HTTP server
func RegisterRoutes(mux *http.ServeMux) {
for path, handler := range routeMap {
Expand Down
15 changes: 15 additions & 0 deletions api/routes/swagger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
Copyright © 2024 Patrick Laabs patrick.laabs@me.com
*/

package routes

import (
_ "github.com/PatrickLaabs/eros/docs"
"github.com/swaggo/http-swagger"
"net/http"
)

func Swagger(w http.ResponseWriter, r *http.Request) {
httpSwagger.WrapHandler(w, r)
}
28 changes: 28 additions & 0 deletions api/routes/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
Copyright © 2024 Patrick Laabs patrick.laabs@me.com
*/

package routes

import (
"encoding/json"
_ "github.com/PatrickLaabs/eros/docs"
"net/http"
)

// @Summary Get API Version
// @Description Retrieves the current version of the API, including major, minor, and patch numbers.
// @Tags general
// @Produce json
// @Success 200 {object} api.VersionResponse "API version response"
// @Router /version [get]

// Version returns the current version of the API Server
func Version(w http.ResponseWriter, r *http.Request) {
version := "0.1.0"
err := json.NewEncoder(w).Encode(version)
if err != nil {
http.Error(w, "failed to decode json", http.StatusBadRequest)
return
}
}
Loading

0 comments on commit bb63425

Please sign in to comment.