mirror of
https://github.com/openfaas/faasd.git
synced 2025-06-12 18:06:48 +00:00
Adds support for: Get, Create, Update, Delete Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
286 lines
6.6 KiB
Go
286 lines
6.6 KiB
Go
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()
|
|
}
|