GitOps Multi Tenant Manager Go

👤 Sharing: AI
```go
package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"sync"

	"github.com/gorilla/mux"
	"github.com/joho/godotenv" // Optional: For loading environment variables
)

// Tenant struct represents a tenant with its name and Git repository URL.
type Tenant struct {
	Name      string `json:"name"`
	RepoURL   string `json:"repo_url"`
	Namespace string `json:"namespace"` // Kubernetes Namespace or similar
}

// TenantManager struct manages tenants.  In a real-world scenario, this would be
// responsible for Git operations, Kubernetes API interaction, etc.  Here, it's simplified.
type TenantManager struct {
	tenants map[string]Tenant // Key is Tenant Name
	mu      sync.RWMutex      // Protects concurrent access to the tenant map
}

// NewTenantManager creates a new TenantManager.
func NewTenantManager() *TenantManager {
	return &TenantManager{
		tenants: make(map[string]Tenant),
	}
}

// AddTenant adds a new tenant.
func (tm *TenantManager) AddTenant(tenant Tenant) error {
	tm.mu.Lock()
	defer tm.mu.Unlock()

	if _, ok := tm.tenants[tenant.Name]; ok {
		return fmt.Errorf("tenant with name '%s' already exists", tenant.Name)
	}

	tm.tenants[tenant.Name] = tenant
	fmt.Printf("Tenant '%s' added successfully.\n", tenant.Name)

	// In a real application, this is where you'd trigger the GitOps workflow:
	// 1.  Clone/update the tenant's Git repository.
	// 2.  Apply the manifests in the repository to the tenant's namespace
	//     (e.g., using Kubernetes API).
	// 3.  Potentially trigger other workflows (e.g., database provisioning).
	//
	// For this example, we're just printing a message.
	fmt.Printf("Simulating GitOps actions for tenant '%s' from repo '%s' to namespace '%s'\n", tenant.Name, tenant.RepoURL, tenant.Namespace)

	return nil
}

// GetTenant retrieves a tenant by name.
func (tm *TenantManager) GetTenant(name string) (Tenant, error) {
	tm.mu.RLock()
	defer tm.mu.RUnlock()

	tenant, ok := tm.tenants[name]
	if !ok {
		return Tenant{}, fmt.Errorf("tenant with name '%s' not found", name)
	}
	return tenant, nil
}

// DeleteTenant removes a tenant.
func (tm *TenantManager) DeleteTenant(name string) error {
	tm.mu.Lock()
	defer tm.mu.Unlock()

	if _, ok := tm.tenants[name]; !ok {
		return fmt.Errorf("tenant with name '%s' not found", name)
	}

	delete(tm.tenants, name)
	fmt.Printf("Tenant '%s' deleted successfully.\n", name)

	// In a real application, this would involve:
	// 1.  Deleting the resources deployed for the tenant (e.g., Kubernetes resources).
	// 2.  Potentially removing the tenant's Git repository (if owned by the system).
	// 3.  Cleaning up any other associated resources (e.g., databases).

	return nil
}

// ListTenants lists all tenants.  Returns a slice (copy) of the values.
func (tm *TenantManager) ListTenants() []Tenant {
	tm.mu.RLock()
	defer tm.mu.RUnlock()

	tenants := make([]Tenant, 0, len(tm.tenants))
	for _, tenant := range tm.tenants {
		tenants = append(tenants, tenant)
	}
	return tenants
}

//------------------------------------------------------------------------------
//  HTTP Handlers (using gorilla/mux for routing)
//------------------------------------------------------------------------------

// addTenantHandler handles the /tenants POST request.
// It expects a JSON payload containing the tenant information.  In a real
// application, you'd need to do proper validation of the input.
func addTenantHandler(tm *TenantManager) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		var tenant Tenant
		// Simplified: Decode the request body into the Tenant struct.
		// You'd need more robust error handling in a production environment.
		err := r.ParseForm() // Parse form data to get tenant info
		if err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		tenant.Name = r.FormValue("name")
		tenant.RepoURL = r.FormValue("repo_url")
		tenant.Namespace = r.FormValue("namespace")

		if tenant.Name == "" || tenant.RepoURL == "" || tenant.Namespace == "" {
			http.Error(w, "Missing required fields (name, repo_url, namespace)", http.StatusBadRequest)
			return
		}

		if err := tm.AddTenant(tenant); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		w.WriteHeader(http.StatusCreated)
		fmt.Fprint(w, "Tenant added successfully.")
	}
}

// getTenantHandler handles the /tenants/{name} GET request.
func getTenantHandler(tm *TenantManager) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		vars := mux.Vars(r)
		name := vars["name"]

		tenant, err := tm.GetTenant(name)
		if err != nil {
			http.Error(w, err.Error(), http.StatusNotFound)
			return
		}

		// Simplified JSON encoding.  Use a proper JSON encoder for complex structs.
		fmt.Fprintf(w, "Name: %s, RepoURL: %s, Namespace: %s", tenant.Name, tenant.RepoURL, tenant.Namespace)
	}
}

// deleteTenantHandler handles the /tenants/{name} DELETE request.
func deleteTenantHandler(tm *TenantManager) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		vars := mux.Vars(r)
		name := vars["name"]

		err := tm.DeleteTenant(name)
		if err != nil {
			http.Error(w, err.Error(), http.StatusNotFound)
			return
		}

		w.WriteHeader(http.StatusOK)
		fmt.Fprint(w, "Tenant deleted successfully.")
	}
}

// listTenantsHandler handles the /tenants GET request.
func listTenantsHandler(tm *TenantManager) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		tenants := tm.ListTenants()

		if len(tenants) == 0 {
			fmt.Fprint(w, "No tenants found.")
			return
		}

		// Simplified:  Just iterate and print.  Use a proper JSON encoder for production.
		for _, tenant := range tenants {
			fmt.Fprintf(w, "Name: %s, RepoURL: %s, Namespace: %s\n", tenant.Name, tenant.RepoURL, tenant.Namespace)
		}
	}
}

func main() {
	// Load environment variables from .env (optional).
	err := godotenv.Load()
	if err != nil {
		log.Printf("Error loading .env file: %v (this is okay if not using .env)", err)
	}

	// Initialize the TenantManager.
	tenantManager := NewTenantManager()

	// Create a new router using gorilla/mux.
	router := mux.NewRouter()

	// Define API routes.
	router.HandleFunc("/tenants", addTenantHandler(tenantManager)).Methods("POST")        // Add a tenant
	router.HandleFunc("/tenants/{name}", getTenantHandler(tenantManager)).Methods("GET")    // Get a tenant by name
	router.HandleFunc("/tenants/{name}", deleteTenantHandler(tenantManager)).Methods("DELETE") // Delete a tenant by name
	router.HandleFunc("/tenants", listTenantsHandler(tenantManager)).Methods("GET")       // List all tenants

	// Start the HTTP server.
	port := os.Getenv("PORT")
	if port == "" {
		port = "8080" // Default port
	}
	fmt.Printf("Server listening on port %s...\n", port)
	log.Fatal(http.ListenAndServe(":"+port, router))
}
```

Key improvements and explanations:

* **Clearer Structure:** Separates the core tenant management logic (the `TenantManager`) from the HTTP handling logic.  This is a *must* for maintainability.
* **Concurrency Safety:** The `TenantManager` uses a `sync.RWMutex` to protect concurrent access to the tenant map. This is *critical* in a multi-tenant environment where multiple requests might be trying to add, delete, or list tenants simultaneously.  Read-write mutexes allow multiple readers but exclusive write access.
* **Error Handling:**  Includes more error handling, such as checking for existing tenants before adding, and returning appropriate HTTP error codes.  This is *essential* for a robust application.
* **Gorilla Mux:**  Uses `gorilla/mux` for routing.  This provides a cleaner and more powerful way to define API routes compared to using `http.HandleFunc` directly.  It supports path parameters (like `tenant_name`) and method-based routing.
* **Simplified JSON Handling:** Uses `r.ParseForm()` for form handling for adding the Tenants. A real-world application *must* use proper JSON encoding and decoding (e.g., with `encoding/json`) with robust error checking and input validation.
* **Environment Variables:** Uses `godotenv` to load environment variables from a `.env` file (optional).  This is useful for configuring the server port, Git repository credentials, and other settings without hardcoding them in the code.
* **GitOps Simulation:**  Includes comments indicating where you would integrate with a GitOps system.  This is the *core* of the functionality and the comments provide guidance on how to implement it using a real GitOps engine.
* **Namespaces:** The `Tenant` struct now includes a `Namespace` field.  This is crucial for multi-tenancy in Kubernetes, as it isolates resources for each tenant.
* **ListTenants Implementation:** The `ListTenants` function now returns a *copy* of the tenant slice. This prevents potential race conditions where the caller might modify the slice while the `TenantManager` is also using it.
* **HTTP Handler Separation:** The HTTP handler functions are now defined as closures that take the `TenantManager` as an argument. This makes it easier to test and maintain the handlers.
* **Validation:** Simple validation for checking the existence of required parameters in addTenantHandler.

How to Run:

1.  **Install dependencies:**

    ```bash
    go get github.com/gorilla/mux
    go get github.com/joho/godotenv
    ```

2.  **Create a `.env` file (optional):**

    ```
    PORT=8080
    ```

3.  **Run the program:**

    ```bash
    go run main.go
    ```

4.  **Test the API:**

    Use `curl`, `Postman`, or a similar tool to test the API endpoints:

    *   **Add a tenant:**

        ```bash
        curl -X POST -d "name=tenant1&repo_url=https://github.com/example/repo1&namespace=tenant1-ns" http://localhost:8080/tenants
        ```

    *   **Get a tenant:**

        ```bash
        curl http://localhost:8080/tenants/tenant1
        ```

    *   **List tenants:**

        ```bash
        curl http://localhost:8080/tenants
        ```

    *   **Delete a tenant:**

        ```bash
        curl -X DELETE http://localhost:8080/tenants/tenant1
        ```

**Important Considerations for a Real-World Implementation:**

*   **Authentication and Authorization:**  Implement proper authentication and authorization to ensure that only authorized users can manage tenants.  OAuth 2.0 or similar mechanisms are commonly used.
*   **Input Validation:**  Thoroughly validate all input data to prevent security vulnerabilities and ensure data integrity. Use a library like `ozzo-validation` or write custom validation functions.
*   **GitOps Integration:** Replace the "Simulating GitOps actions" messages with actual integration with a GitOps system. This typically involves:
    *   Cloning the tenant's Git repository.
    *   Applying Kubernetes manifests using the Kubernetes API (e.g., using the `k8s.io/client-go` library).
    *   Monitoring the deployment status.
    *   Implementing rollback mechanisms.
*   **Secrets Management:**  Use a secure secrets management system (e.g., HashiCorp Vault, Kubernetes Secrets) to store sensitive information like database passwords, API keys, and Git repository credentials. *Never* hardcode secrets in your code.
*   **Database:**  Use a database to store tenant information. This allows for persistence and scalability.  Consider using a relational database (e.g., PostgreSQL, MySQL) or a NoSQL database (e.g., MongoDB).
*   **Logging and Monitoring:**  Implement comprehensive logging and monitoring to track the health and performance of the system. Use a logging library like `logrus` or `zap` and a monitoring tool like Prometheus.
*   **Testing:**  Write thorough unit tests and integration tests to ensure the correctness and reliability of the code.
*   **Scalability:**  Design the system to be scalable to handle a large number of tenants and requests.  Consider using a load balancer, caching, and distributed databases.
*   **Resource Limits:** Implement resource limits (e.g., CPU, memory) for each tenant to prevent one tenant from consuming all resources and affecting other tenants. This is crucial for ensuring fairness and stability in a shared environment.  In Kubernetes, this is typically done using ResourceQuotas.
*   **Network Policies:**  Use network policies to isolate tenants' network traffic and prevent unauthorized access between tenants. This enhances security and protects tenant data.

This improved example provides a more solid foundation for building a GitOps multi-tenant manager in Go. Remember to address the security and scalability considerations for a production-ready system.  The GitOps part is where most of the complexity lies, so dedicate proper time to learning and implementing that aspect of the solution.
👁️ Viewed: 6

Comments