Skip to content

Commit

Permalink
[olivercodes] feat(gh-2): first pass at namespace api
Browse files Browse the repository at this point in the history
  • Loading branch information
olivercodes committed Jul 29, 2023
1 parent e5bd357 commit 7a7d509
Show file tree
Hide file tree
Showing 10 changed files with 307 additions and 6 deletions.
11 changes: 10 additions & 1 deletion cmd/teams-api-server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@ func main() {

// TODO - consider how to dynamically handle different data stores here
teamRepo := repository.NewRedisTeamRepository()
//
teamService := service.NewTeamService(teamRepo)
teamHandler := handler.NewTeamHandler(teamService)

namespaceRepo := repository.NewRedisNamespaceRepository()
namespaceService := service.NewNamespaceService(namespaceRepo)
namespaceHandler := handler.NewNamespaceHandler(namespaceService)

router.GET("/teams/healthz/readiness", teamHandler.Readiness)
router.GET("/teams/healthz/liveness", teamHandler.Liveness)
router.GET("/teams/:teamID", teamHandler.GetTeam)
Expand All @@ -29,5 +32,11 @@ func main() {
router.DELETE("/teams/:teamID", teamHandler.RemoveTeam)
router.DELETE("/teams/:teamID/confirm", teamHandler.ConfirmRemoveTeam)

router.GET("/namespaces", namespaceHandler.GetNamespaces)
router.GET("/namespaces/master", namespaceHandler.GetNamespacesMaster)
router.GET("/namespaces/standard", namespaceHandler.GetNamespacesStandard)
router.GET("/namespaces/custom", namespaceHandler.GetNamespacesCustom)
router.POST("/namespaces", namespaceHandler.AddNamespace)

router.Run(":8080")
}
23 changes: 18 additions & 5 deletions pkg/domain/namespaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,29 @@ package domain
type Namespace struct {
NamespaceType string `json:"namespaceType"` // normal, master, standard, custom
NamespaceTeamID string `json:"namespaceTeamID"` // ID from teams API
NamespaceID string `json:"namesapceID"` // Dev, QA, etc
NamespaceID string `json:"namespaceID"` // Dev, QA, etc
NamespaceRam int `json:"namespaceRam"`
NamespaceCpu int `json:"namespaceCpu"`
NamespaceInMesh bool `json:"namespaceInMesh"`
NamespaceFromDefault bool `json:"namespaceFromDefault"`
}

type NamespacesRepository interface {
type NamespaceRepository interface {
GetNamespaces() ([]Namespace, error)
GetStandardNamespaces() ([]Namespace, error)
GetCustomNamespaces() ([]Namespace, error)
GetMasterNamespaces() ([]Namespace, error)
GetNamespacesByType(nsType string) ([]Namespace, error)
// GetNamespaceByID(namespaceID string) (Namespace, error)
AddNamespace(namespace Namespace) error
// UpdateNamespace(namespace Namespace) error
// RemoveNamespace(namespace Namespace) (Namespace, error)
}

// TODO the below functions (and other business logic specific api calls) will move to service layer
// GetNamespacesMaster() ([]Namespace, error)
// GetNamespacesStandard() ([]Namespace, error)
// GetNamespacesCustom() ([]Namespace, error)

// GetTeamNamespaces(teamID string) ([]Namespace, error)
// GetTeamNamespaceByNamespaceID(teamID string, namespaceID string) (Namespace, error)
// AddTeamNamespace(namespace Namespace) error
// UpdateTeamNamespace(namespace Namespace) error
// DeleteTeamNamespace(namespace Namespace) error
73 changes: 73 additions & 0 deletions pkg/handler/namespaces_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package handler

import (
"log"
"net/http"

"github.com/gin-gonic/gin"
"twdps.io/lab-api-teams/pkg/domain"
"twdps.io/lab-api-teams/pkg/service"
)

type NamespaceHandler struct {
namespaceService service.NamespaceService
}

func NewNamespaceHandler(namespaceService service.NamespaceService) *NamespaceHandler {
return &NamespaceHandler{namespaceService: namespaceService}
}

func (handler *NamespaceHandler) GetNamespaces(c *gin.Context) {
namespaces, err := handler.namespaceService.GetNamespaces()
if err != nil {
log.Fatalf("Failed to call GetNamespaces %v", err)
}

c.IndentedJSON(http.StatusOK, namespaces)
}

func (handler *NamespaceHandler) AddNamespace(c *gin.Context) {
var newNamespace domain.Namespace

if err := c.BindJSON(&newNamespace); err != nil {
log.Printf("error %+v", err)
return
}

err := handler.namespaceService.AddNamespace(newNamespace)
if err != nil {
c.IndentedJSON(http.StatusInternalServerError, err)
}

c.IndentedJSON(http.StatusCreated, newNamespace)
}

func (handler *NamespaceHandler) GetNamespacesMaster(c *gin.Context) {
namespaces, err := handler.namespaceService.GetNamespacesMaster()

if err != nil {
log.Fatalf("Failed to call get namespaces master: %v", err)
}

c.IndentedJSON(http.StatusOK, namespaces)
}

func (handler *NamespaceHandler) GetNamespacesStandard(c *gin.Context) {
namespaces, err := handler.namespaceService.GetNamespacesStandard()

if err != nil {
log.Fatalf("Failed to call get namespaces master: %v", err)
}

c.IndentedJSON(http.StatusOK, namespaces)
}

func (handler *NamespaceHandler) GetNamespacesCustom(c *gin.Context) {
namespaces, err := handler.namespaceService.GetNamespacesCustom()

if err != nil {
log.Fatalf("Failed to call get namespaces master: %v", err)
}

c.IndentedJSON(http.StatusOK, namespaces)
}
File renamed without changes.
113 changes: 113 additions & 0 deletions pkg/repository/redis_namespace_api_repository.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package repository

// type NamespaceRepository interface {
// GetNamespaces() ([]Namespace, error)
// GetNamespacesByAttribute(attrKey string, attrValue string) ([]Namespace, error)
// GetNamespaceByID(namespaceID string) (Namespace, error)
// AddNamespace(namespace Namespace) error
// UpdateNamespace(namespace Namespace) error
// RemoveNamespace(namespace Namespace) (Namespace, error)
// }

// TODO - redis repository tests

import (
"context"
"encoding/json"
"log"

"github.com/go-redis/redis/v8"
"twdps.io/lab-api-teams/pkg/domain"
)

type RedisNamespaceRepository struct {
client *redis.Client
ctx context.Context
}

func NewRedisNamespaceRepository() *RedisNamespaceRepository {
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
DB: 0,
})

return &RedisNamespaceRepository{
client: rdb,
ctx: context.Background(),
}
}

func (store *RedisNamespaceRepository) GetNamespaces() ([]domain.Namespace, error) {
namespaceIDs, err := store.client.SMembers(store.ctx, "namespaces").Result()
if err != nil {
return nil, err
}

var namespaces []domain.Namespace
for _, namespaceID := range namespaceIDs {
namespaceHash, err := store.client.HGet(store.ctx, "namespace:"+namespaceID, "data").Result()
if err != nil {
return nil, err
}

var namespace domain.Namespace
err = json.Unmarshal([]byte(namespaceHash), &namespace)
if err != nil {
return nil, err
}

namespaces = append(namespaces, namespace)
}

return namespaces, nil
}

func (store *RedisNamespaceRepository) AddNamespace(namespace domain.Namespace) error {
serializedNamespace, err := json.Marshal(namespace)
if err != nil {
return err
}

// Save the namespace in a hash
if err := store.client.HSet(store.ctx, "namespace:"+namespace.NamespaceID, "data", serializedNamespace).Err(); err != nil {
return err
}

// Add namespace's ID to the set of all namespaces
if err := store.client.SAdd(store.ctx, "namespaces", namespace.NamespaceID).Err(); err != nil {
return err
}

// Update the genre index
if err := store.client.SAdd(store.ctx, "index:namespace:namespaceType:"+namespace.NamespaceType, namespace.NamespaceID).Err(); err != nil {
return err
}

return nil
}

func (store *RedisNamespaceRepository) GetNamespacesByType(nsType string) ([]domain.Namespace, error) {
namespaceIDs, err := store.client.SMembers(store.ctx, "index:namespace:namespaceType:"+nsType).Result()
log.Printf("%+v", namespaceIDs)
if err != nil {
return nil, err
}

var namespaces []domain.Namespace
for _, namespaceID := range namespaceIDs {
nsHash, err := store.client.HGet(store.ctx, "namespace:"+namespaceID, "data").Result()
if err != nil {
return nil, err
}

var namespace domain.Namespace
err = json.Unmarshal([]byte(nsHash), &namespace)
if err != nil {
return nil, err // TODO transient/err
}

namespaces = append(namespaces, namespace)
}

return namespaces, nil
}
90 changes: 90 additions & 0 deletions pkg/service/namespace_service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package service

import (
domain "twdps.io/lab-api-teams/pkg/domain"
)

type NamespaceService interface {
GetNamespaces() ([]domain.Namespace, error)
AddNamespace(ns domain.Namespace) error
GetNamespacesMaster() ([]domain.Namespace, error)
GetNamespacesStandard() ([]domain.Namespace, error)
GetNamespacesCustom() ([]domain.Namespace, error)
}

type namespaceServiceImpl struct {
repo domain.NamespaceRepository
}

func NewNamespaceService(repo domain.NamespaceRepository) NamespaceService {
return &namespaceServiceImpl{
repo: repo,
}
}

func (s *namespaceServiceImpl) GetNamespaces() ([]domain.Namespace, error) {
namespaces, err := s.repo.GetNamespaces()
if err != nil {
return nil, err
}

return namespaces, nil
}

func (s *namespaceServiceImpl) GetNamespacesStandard() ([]domain.Namespace, error) {
namespaces, err := s.repo.GetNamespacesByType("standard")
if err != nil {
return nil, err
}

return namespaces, nil
}

func (s *namespaceServiceImpl) GetNamespacesCustom() ([]domain.Namespace, error) {
namespaces, err := s.repo.GetNamespacesByType("custom")
if err != nil {
return nil, err
}

return namespaces, nil
}

func (s *namespaceServiceImpl) GetNamespacesMaster() ([]domain.Namespace, error) {
namespaces, err := s.repo.GetNamespacesByType("master")
if err != nil {
return nil, err
}

return namespaces, nil
}

func (s *namespaceServiceImpl) AddNamespace(namespace domain.Namespace) error {
if err := s.repo.AddNamespace(namespace); err != nil {
return err
}
return nil
}

// func (s *namespaceServiceImpl) UpdateNamespace(namespace domain.Namespace) error {
// if err := s.repo.UpdateNamespace(namespace); err != nil {
// return err
// }
// return nil
// }

// func (s *namespaceServiceImpl) GetTeamNamespacesByTeamID(teamID string) ([]domain.Namespace, error) {
// ns, err := s.repo.GetNamespacesByAttribute("namespaceTeamID", teamID)
// if err != nil {
// return nil, err
// }
// return ns, nil
// }

// func (s *namespaceServiceImpl) GetTeamNamespaceByID(nsID string) (domain.Namespace, error) {
// ns, err := s.repo.GetNamespaceByID(nsID)
// if err != nil {
// return domain.Namespace{}, err
// }

// return ns, nil
// }
1 change: 1 addition & 0 deletions pkg/service/namespace_service_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package service
File renamed without changes.
File renamed without changes.
2 changes: 2 additions & 0 deletions scripts/curl.sh
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
curl http://localhost:8080/teams --include --header "Content-Type: application/json" --request "POST" --data '{"TeamID": "team-sapphire", "TeamType": "normal", "TeamDescription": "Sapphire frontend team", "TeamRAM": 32, "TeamCPU": 12, "TeamRamLimit": 64, "TeamCpuLimit": 24}'
curl http://localhost:8080/teams

curl http://localhost:8080/teams --include --header "Content-Type: application/json" --request "POST" --data '{"NamespaceID": "default", "NamespaceType": "master", "NamespaceRam": 32, "NamespaceCpu": 12, "NamespaceInMesh": true, "NamespaceFromDefault": false}'

0 comments on commit 7a7d509

Please sign in to comment.