mirror of
https://github.com/openfaas/faasd.git
synced 2025-06-21 14:23:34 +00:00
Add support for namespace mutations
Adds support for: Get, Create, Update, Delete Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
This commit is contained in:
committed by
Alex Ellis
parent
c83b649301
commit
57baf34f5a
285
pkg/provider/handlers/mutate_namespaces.go
Normal file
285
pkg/provider/handlers/mutate_namespaces.go
Normal file
@ -0,0 +1,285 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/containerd"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/openfaas/faas-provider/types"
|
||||
)
|
||||
|
||||
func MakeMutateNamespace(client *containerd.Client) func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Body != nil {
|
||||
defer r.Body.Close()
|
||||
}
|
||||
|
||||
switch r.Method {
|
||||
case http.MethodPost:
|
||||
createNamespace(client, w, r)
|
||||
case http.MethodGet:
|
||||
getNamespace(client, w, r)
|
||||
case http.MethodDelete:
|
||||
deleteNamespace(client, w, r)
|
||||
case http.MethodPut:
|
||||
updateNamespace(client, w, r)
|
||||
|
||||
default:
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNamespace(client *containerd.Client, w http.ResponseWriter, r *http.Request) {
|
||||
req, err := parseNamespaceRequest(r)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), err.(*HttpError).Status)
|
||||
return
|
||||
}
|
||||
|
||||
namespaceExists, err := namespaceExists(r.Context(), client, req.Name)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if !namespaceExists {
|
||||
http.Error(w, fmt.Sprintf("namespace %s not found", req.Name), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
originalLabels, err := client.NamespaceService().Labels(r.Context(), req.Name)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if !hasOpenFaaSLabel(originalLabels) {
|
||||
http.Error(w, fmt.Sprintf("namespace %s is not an openfaas namespace", req.Name), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var exclusions []string
|
||||
|
||||
// build exclusions
|
||||
for key, _ := range originalLabels {
|
||||
if _, ok := req.Labels[key]; !ok {
|
||||
exclusions = append(exclusions, key)
|
||||
}
|
||||
}
|
||||
|
||||
// Call SetLabel with empty string if label is to be removed
|
||||
for _, key := range exclusions {
|
||||
if err := client.NamespaceService().SetLabel(r.Context(), req.Name, key, ""); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Now add the new labels
|
||||
for key, value := range req.Labels {
|
||||
if err := client.NamespaceService().SetLabel(r.Context(), req.Name, key, value); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
}
|
||||
|
||||
func deleteNamespace(client *containerd.Client, w http.ResponseWriter, r *http.Request) {
|
||||
req, err := parseNamespaceRequest(r)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), err.(*HttpError).Status)
|
||||
return
|
||||
}
|
||||
|
||||
if err := client.NamespaceService().Delete(r.Context(), req.Name); err != nil {
|
||||
if strings.Contains(err.Error(), "not found") {
|
||||
http.Error(w, fmt.Sprintf("namespace %s not found", req.Name), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
}
|
||||
|
||||
func namespaceExists(ctx context.Context, client *containerd.Client, name string) (bool, error) {
|
||||
ns, err := client.NamespaceService().List(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, namespace := range ns {
|
||||
if namespace == name {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return found, nil
|
||||
}
|
||||
|
||||
func getNamespace(client *containerd.Client, w http.ResponseWriter, r *http.Request) {
|
||||
req, err := parseNamespaceRequest(r)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), err.(*HttpError).Status)
|
||||
return
|
||||
}
|
||||
|
||||
namespaceExists, err := namespaceExists(r.Context(), client, req.Name)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if !namespaceExists {
|
||||
http.Error(w, fmt.Sprintf("namespace %s not found", req.Name), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
labels, err := client.NamespaceService().Labels(r.Context(), req.Name)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if !hasOpenFaaSLabel(labels) {
|
||||
http.Error(w, fmt.Sprintf("namespace %s not found", req.Name), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
res := types.FunctionNamespace{
|
||||
Name: req.Name,
|
||||
Labels: labels,
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if err := json.NewEncoder(w).Encode(res); err != nil {
|
||||
log.Printf("Get Namespace error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func createNamespace(client *containerd.Client, w http.ResponseWriter, r *http.Request) {
|
||||
req, err := parseNamespaceRequest(r)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), err.(*HttpError).Status)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if namespace exists, and it has the openfaas label
|
||||
namespaces, err := client.NamespaceService().List(r.Context())
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, namespace := range namespaces {
|
||||
if namespace == req.Name {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
http.Error(w, fmt.Sprintf("namespace %s already exists", req.Name), http.StatusConflict)
|
||||
return
|
||||
}
|
||||
|
||||
if err := client.NamespaceService().Create(r.Context(), req.Name, req.Labels); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
}
|
||||
|
||||
// getNamespace returns a namespace object or an error
|
||||
func parseNamespaceRequest(r *http.Request) (types.FunctionNamespace, error) {
|
||||
var req types.FunctionNamespace
|
||||
|
||||
vars := mux.Vars(r)
|
||||
namespaceInPath := vars["name"]
|
||||
|
||||
if r.Method == http.MethodGet {
|
||||
if namespaceInPath == "" {
|
||||
return req, &HttpError{
|
||||
Err: fmt.Errorf("namespace not specified in URL"),
|
||||
Status: http.StatusBadRequest,
|
||||
}
|
||||
}
|
||||
|
||||
return types.FunctionNamespace{
|
||||
Name: namespaceInPath,
|
||||
}, nil
|
||||
}
|
||||
|
||||
body, _ := io.ReadAll(r.Body)
|
||||
|
||||
if err := json.Unmarshal(body, &req); err != nil {
|
||||
return req, &HttpError{
|
||||
Err: fmt.Errorf("error parsing request body: %s", err.Error()),
|
||||
Status: http.StatusBadRequest,
|
||||
}
|
||||
}
|
||||
|
||||
if r.Method != http.MethodPost {
|
||||
if namespaceInPath == "" {
|
||||
return req, &HttpError{
|
||||
Err: fmt.Errorf("namespace not specified in URL"),
|
||||
Status: http.StatusBadRequest,
|
||||
}
|
||||
}
|
||||
if req.Name != namespaceInPath {
|
||||
return req, &HttpError{
|
||||
Err: fmt.Errorf("namespace in request body does not match namespace in URL"),
|
||||
Status: http.StatusBadRequest,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if req.Name == "" {
|
||||
return req, &HttpError{
|
||||
Err: fmt.Errorf("namespace not specified in request body"),
|
||||
Status: http.StatusBadRequest,
|
||||
}
|
||||
}
|
||||
|
||||
if ok := hasOpenFaaSLabel(req.Labels); !ok {
|
||||
return req, &HttpError{
|
||||
Err: fmt.Errorf("request does not have openfaas=1 label"),
|
||||
Status: http.StatusBadRequest,
|
||||
}
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func hasOpenFaaSLabel(labels map[string]string) bool {
|
||||
if v, ok := labels["openfaas"]; ok && v == "1" {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
type HttpError struct {
|
||||
Err error
|
||||
Status int
|
||||
}
|
||||
|
||||
func (e *HttpError) Error() string {
|
||||
return e.Err.Error()
|
||||
}
|
Reference in New Issue
Block a user