Skip to content

Commit

Permalink
Merge pull request #56 from pehlicd/55-fix-drop-managed-fields-from-r…
Browse files Browse the repository at this point in the history
…aw-manifest

fix: drop managed fields from raw manifest and navbar
  • Loading branch information
pehlicd authored Oct 13, 2024
2 parents 0513cd4 + cf54bc8 commit 83edd78
Show file tree
Hide file tree
Showing 17 changed files with 396 additions and 106 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
!assets/*
!rbac-wizard.rb

!dev/*

# ...even if they are in subdirectories
!*/

77 changes: 75 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,16 +1,89 @@
serve: build-ui-completely
# ---------------------------------------------------------------------------- #
# ______ ______ ___ _____ _ _ _____ ______ ___ ______ ______ #
# | ___ \| ___ \ / _ \ / __ \ | | | ||_ _||___ / / _ \ | ___ \| _ \ #
# | |_/ /| |_/ // /_\ \| / \/ | | | | | | / / / /_\ \| |_/ /| | | | #
# | / | ___ \| _ || | | |/\| | | | / / | _ || / | | | | #
# | |\ \ | |_/ /| | | || \__/\ \ /\ / _| |_ ./ /___| | | || |\ \ | |/ / #
# \_| \_|\____/ \_| |_/ \____/ \/ \/ \___/ \_____/\_| |_/\_| \_||___/ #
# #
# ---------------------------------------------------------------------------- #

# Color Definitions
NO_COLOR = \033[0m
OK_COLOR = \033[32;01m
ERROR_COLOR = \033[31;01m
WARN_COLOR = \033[33;01m

# Directories
APP_NAME = rbac-wizard
BIN_DIR = ./bin
GO_BUILD = $(BIN_DIR)/$(APP_NAME)

# Main Targets
.PHONY: run serve run-ui build-ui build-ui-and-embed build-backend fmt create-cluster delete-cluster deploy-ingress-nginx clean

## Run the application in serve mode
run:
@echo "$(OK_COLOR)==> Running the application...$(NO_COLOR)"
go run . serve

## Serve the application (build UI and backend first)
serve: build-ui-and-embed build-backend
@echo "$(OK_COLOR)==> Starting the application...$(NO_COLOR)"
go run . serve

## Run UI in development mode
run-ui:
@echo "$(OK_COLOR)==> Running UI in development mode...$(NO_COLOR)"
cd ui && npm run dev

## Build the UI
build-ui:
@echo "$(OK_COLOR)==> Building UI...$(NO_COLOR)"
cd ui && npm run build

build-ui-completely: build-ui
## Build UI, embed it into Go application, and clean up artifacts
build-ui-and-embed: build-ui
@echo "$(OK_COLOR)==> Embedding UI files into Go application...$(NO_COLOR)"
statik -src=./ui/dist/ -dest=./internal/ -f
@echo "$(OK_COLOR)==> Cleaning up UI build artifacts...$(NO_COLOR)"
rm -rf ./ui/dist

## Build the Go backend and place the binary in bin directory
build-backend:
@echo "$(OK_COLOR)==> Building Go backend...$(NO_COLOR)"
mkdir -p $(BIN_DIR)
go build -o $(GO_BUILD)

## Format Go code and tidy modules
fmt:
@echo "$(OK_COLOR)==> Formatting Go code and tidying modules...$(NO_COLOR)"
go fmt ./...
go mod tidy

# Kubernetes Cluster Management
## Create a Kubernetes cluster using Kind
create-k8s-cluster:
@echo "$(OK_COLOR)==> Creating Kubernetes cluster...$(NO_COLOR)"
kind create cluster --config dev/kind.yaml

## Delete the Kubernetes cluster
delete-k8s-cluster:
@echo "$(OK_COLOR)==> Deleting Kubernetes cluster...$(NO_COLOR)"
kind delete cluster -n 'rbac-wizard-dev'

## Deploy NGINX Ingress controller to the cluster
deploy-ingress-nginx:
@echo "$(OK_COLOR)==> Deploying ingress-nginx...$(NO_COLOR)"
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
@echo "$(OK_COLOR)==> Waiting for ingress-nginx to be ready...$(NO_COLOR)"
kubectl wait --namespace ingress-nginx \
--for=condition=ready pod \
--selector=app.kubernetes.io/component=controller \
--timeout=180s

# Cleanup
## Remove built binaries and cleanup
clean:
@echo "$(OK_COLOR)==> Cleaning up build artifacts...$(NO_COLOR)"
rm -rf $(BIN_DIR)
136 changes: 89 additions & 47 deletions cmd/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,21 @@ package cmd
import (
"encoding/json"
"fmt"
"github.com/pehlicd/rbac-wizard/internal"
_ "github.com/pehlicd/rbac-wizard/internal/statik"
"io"
"net/http"

"github.com/rakyll/statik/fs"
"github.com/rs/cors"
"github.com/spf13/cobra"
"gopkg.in/yaml.v2"
"io"
"k8s.io/api/rbac/v1"
v1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
kyaml "k8s.io/apimachinery/pkg/runtime/serializer/yaml"
"log"
"net/http"

"github.com/pehlicd/rbac-wizard/internal"
"github.com/pehlicd/rbac-wizard/internal/logger"
_ "github.com/pehlicd/rbac-wizard/internal/statik"
)

// serveCmd represents the serve command
Expand All @@ -47,70 +49,100 @@ var serveCmd = &cobra.Command{
Long: `Start the server for the rbac-wizard. This will start the server on the specified port and serve the frontend.`,
Run: func(cmd *cobra.Command, args []string) {
port, _ := cmd.Flags().GetString("port")
serve(port)
enableLogging, _ := cmd.Flags().GetBool("logging")
logLevel, _ := cmd.Flags().GetString("log-level")
logFormat, _ := cmd.Flags().GetString("log-format")
serve(port, enableLogging, logLevel, logFormat)
},
}

var app internal.App

type Serve struct {
App internal.App
}

func init() {
rootCmd.AddCommand(serveCmd)

serveCmd.Flags().StringP("port", "p", "8080", "Port to run the server on")
serveCmd.Flags().BoolP("logging", "g", false, "Enable logging")
serveCmd.Flags().StringP("log-level", "l", "info", "Log level")
serveCmd.Flags().StringP("log-format", "f", "text", "Log format default is text [text, json]")
}

func serve(port string) {
func serve(port string, logging bool, logLevel string, logFormat string) {
// Set up logger if logging is enabled
if logging {
l := logger.New(logLevel, logFormat)
app.Logger = l
} else {
l := logger.New("off", logFormat)
app.Logger = l
}

kubeClient, err := internal.GetClientset()
if err != nil {
log.Fatalf("Failed to create Kubernetes client: %v\n", err)
app.Logger.Fatal().Err(err).Msg("Failed to create Kubernetes client")
}

app.KubeClient = kubeClient

// Set up CORS
c := cors.New(cors.Options{
AllowOriginVaryRequestFunc: func(r *http.Request, origin string) (bool, []string) {
// Implement your dynamic origin check here
host := r.Host // Extract the host from the request
allowedOrigins := []string{"http://localhost:" + port, "https://" + host, "http://localhost:3000"}
for _, allowedOrigin := range allowedOrigins {
if origin == allowedOrigin {
return true, []string{"Origin"}
}
}
return false, nil
},
AllowedMethods: []string{"GET", "POST", "OPTIONS"},
AllowedHeaders: []string{"Accept", "Content-Type", "X-CSRF-Token"},
AllowCredentials: true,
})
serve := Serve{
app,
}

// Set up statik filesystem
statikFS, err := fs.New()
if err != nil {
log.Fatal(err)
app.Logger.Fatal().Err(err).Msg("Failed to create statik filesystem")
}

// Set up CORS
c := setupCors(port)

// Create a new serve mux
mux := http.NewServeMux()

// Set up handlers
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
serveStaticFiles(statikFS, w, r, "index.html")
})
http.HandleFunc("/what-if", func(w http.ResponseWriter, r *http.Request) {
mux.HandleFunc("/what-if", func(w http.ResponseWriter, r *http.Request) {
serveStaticFiles(statikFS, w, r, "what-if.html")
})
http.HandleFunc("/api/data", dataHandler)
http.HandleFunc("/api/what-if", whatIfHandler)
mux.HandleFunc("/api/data", serve.dataHandler)
mux.HandleFunc("/api/what-if", serve.whatIfHandler)

handler := c.Handler(http.DefaultServeMux)
handler := c.Handler(serve.App.LoggerMiddleware(mux))

// Start the server
startupMessage := fmt.Sprintf("Starting rbac-wizard on %s", fmt.Sprintf("http://localhost:%s", port))
fmt.Println(startupMessage)
if err := http.ListenAndServe(":"+port, handler); err != nil {
log.Fatalf("Failed to start server: %v\n", err)
fmt.Printf("Failed to start server: %v\n", err)
}
}

func setupCors(port string) *cors.Cors {
return cors.New(cors.Options{
AllowOriginVaryRequestFunc: func(r *http.Request, origin string) (bool, []string) {
// Implement your dynamic origin check here
host := r.Host // Extract the host from the request
allowedOrigins := []string{"http://localhost:" + port, "https://" + host, "http://localhost:3000"}
for _, allowedOrigin := range allowedOrigins {
if origin == allowedOrigin {
return true, []string{"Origin"}
}
}
return false, nil
},
AllowedMethods: []string{"GET", "POST", "OPTIONS"},
AllowedHeaders: []string{"Accept", "Content-Type", "X-CSRF-Token"},
AllowCredentials: true,
})
}

func serveStaticFiles(statikFS http.FileSystem, w http.ResponseWriter, r *http.Request, defaultFile string) {
// Set cache control headers
cacheControllers(w)
Expand Down Expand Up @@ -141,20 +173,22 @@ func serveStaticFiles(statikFS http.FileSystem, w http.ResponseWriter, r *http.R
http.ServeContent(w, r, path, fileInfo.ModTime(), file)
}

func dataHandler(w http.ResponseWriter, _ *http.Request) {
func (s *Serve) dataHandler(w http.ResponseWriter, _ *http.Request) {
// Set cache control headers
cacheControllers(w)

// Get the bindings
bindings, err := internal.Generator(app).GetBindings()
if err != nil {
s.App.Logger.Error().Err(err).Msg("Failed to get bindings")
http.Error(w, "Failed to get bindings", http.StatusInternalServerError)
return
}

data := internal.GenerateData(bindings)
byteData, err := json.Marshal(data)
if err != nil {
s.App.Logger.Error().Err(err).Msg("Failed to marshal data")
http.Error(w, "Failed to marshal data", http.StatusInternalServerError)
return
}
Expand All @@ -163,28 +197,24 @@ func dataHandler(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, err = w.Write(byteData)
if err != nil {
s.App.Logger.Error().Err(err).Msg("Failed to write data")
http.Error(w, "Failed to write data", http.StatusInternalServerError)
return
}
}

func cacheControllers(w http.ResponseWriter) {
// Set cache control headers
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
w.Header().Set("Pragma", "no-cache")
w.Header().Set("Expires", "0")
}

func whatIfHandler(w http.ResponseWriter, r *http.Request) {
func (s *Serve) whatIfHandler(w http.ResponseWriter, r *http.Request) {
cacheControllers(w)

if r.Method != http.MethodPost {
s.App.Logger.Error().Msg("Invalid request method")
http.Error(w, "Invalid request method", http.StatusMethodNotAllowed)
return
}

body, err := io.ReadAll(r.Body)
if err != nil {
s.App.Logger.Error().Err(err).Msg("Failed to read request body")
http.Error(w, "Failed to read request body", http.StatusInternalServerError)
return
}
Expand All @@ -194,12 +224,14 @@ func whatIfHandler(w http.ResponseWriter, r *http.Request) {
}

if err := json.Unmarshal(body, &input); err != nil {
s.App.Logger.Error().Err(err).Msg("Failed to parse JSON")
http.Error(w, "Failed to parse JSON", http.StatusBadRequest)
return
}

var obj interface{}
if err := yaml.Unmarshal([]byte(input.Yaml), &obj); err != nil {
s.App.Logger.Error().Err(err).Msg("Invalid YAML format")
http.Error(w, "Invalid YAML format", http.StatusBadRequest)
return
}
Expand All @@ -210,6 +242,7 @@ func whatIfHandler(w http.ResponseWriter, r *http.Request) {
}

if obj == nil {
s.App.Logger.Error().Msg("Empty object")
http.Error(w, "Empty object", http.StatusBadRequest)
return
}
Expand All @@ -218,8 +251,8 @@ func whatIfHandler(w http.ResponseWriter, r *http.Request) {
uObj := &unstructured.Unstructured{}
_, _, err = decode.Decode([]byte(input.Yaml), nil, uObj)
if err != nil {
http.Error(w, "Failed to parse ClusterRoleBinding", http.StatusBadRequest)
fmt.Printf("Failed to parse ClusterRoleBinding: %v\n", err)
s.App.Logger.Error().Err(err).Msg("Failed to parse object")
http.Error(w, "Failed to parse object", http.StatusBadRequest)
return
}

Expand All @@ -228,8 +261,8 @@ func whatIfHandler(w http.ResponseWriter, r *http.Request) {
crb := &v1.ClusterRoleBinding{}
err = runtime.DefaultUnstructuredConverter.FromUnstructured(uObj.UnstructuredContent(), crb)
if err != nil {
s.App.Logger.Error().Err(err).Msg("Failed to convert to ClusterRoleBinding")
http.Error(w, "Failed to convert to ClusterRoleBinding", http.StatusBadRequest)
fmt.Printf("Failed to convert to ClusterRoleBinding: %v\n", err)
return
}

Expand All @@ -238,27 +271,36 @@ func whatIfHandler(w http.ResponseWriter, r *http.Request) {
rb := &v1.RoleBinding{}
err = runtime.DefaultUnstructuredConverter.FromUnstructured(uObj.UnstructuredContent(), rb)
if err != nil {
s.App.Logger.Error().Err(err).Msg("Failed to convert to RoleBinding")
http.Error(w, "Failed to convert to ClusterRoleBinding", http.StatusBadRequest)
fmt.Printf("Failed to convert to ClusterRoleBinding: %v\n", err)
return
}
responseData = internal.WhatIfGenerator(app).ProcessRoleBinding(rb)
default:
s.App.Logger.Error().Msg("Unsupported resource type")
http.Error(w, "Unsupported resource type", http.StatusBadRequest)
fmt.Println("Unsupported resource type ", obj, " of type ", fmt.Sprintf("%T", obj))
return
}

respData, err := json.Marshal(responseData)
if err != nil {
s.App.Logger.Error().Err(err).Msg("Failed to marshal response data")
http.Error(w, "Failed to marshal response data", http.StatusInternalServerError)
return
}

w.Header().Set("Content-Type", "application/json")
_, err = w.Write(respData)
if err != nil {
s.App.Logger.Error().Err(err).Msg("Failed to write response data")
http.Error(w, "Failed to write response data", http.StatusInternalServerError)
return
}
}

func cacheControllers(w http.ResponseWriter) {
// Set cache control headers
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
w.Header().Set("Pragma", "no-cache")
w.Header().Set("Expires", "0")
}
Loading

0 comments on commit 83edd78

Please sign in to comment.