github.com/kyma-project/kyma-environment-broker@v0.0.1/internal/kubeconfig/handler.go (about)

     1  package kubeconfig
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"strings"
     7  
     8  	"github.com/kyma-project/kyma-environment-broker/internal/broker"
     9  
    10  	"github.com/kennygrant/sanitize"
    11  
    12  	"github.com/kyma-project/kyma-environment-broker/common/orchestration"
    13  	"github.com/kyma-project/kyma-environment-broker/internal"
    14  	"github.com/kyma-project/kyma-environment-broker/internal/httputil"
    15  	"github.com/kyma-project/kyma-environment-broker/internal/storage"
    16  	"github.com/kyma-project/kyma-environment-broker/internal/storage/dberr"
    17  
    18  	"github.com/gorilla/mux"
    19  	"github.com/pivotal-cf/brokerapi/v8/domain"
    20  	"github.com/sirupsen/logrus"
    21  )
    22  
    23  const attachmentName = "kubeconfig.yaml"
    24  
    25  //go:generate mockery --name=KcBuilder --output=automock --outpkg=automock --case=underscore
    26  
    27  type KcBuilder interface {
    28  	Build(*internal.Instance) (string, error)
    29  	BuildFromAdminKubeconfig(instance *internal.Instance, adminKubeconfig string) (string, error)
    30  }
    31  
    32  type Handler struct {
    33  	kubeconfigBuilder KcBuilder
    34  	allowOrigins      string
    35  	instanceStorage   storage.Instances
    36  	operationStorage  storage.Operations
    37  	log               logrus.FieldLogger
    38  }
    39  
    40  func NewHandler(storage storage.BrokerStorage, b KcBuilder, origins string, log logrus.FieldLogger) *Handler {
    41  	return &Handler{
    42  		instanceStorage:   storage.Instances(),
    43  		operationStorage:  storage.Operations(),
    44  		kubeconfigBuilder: b,
    45  		allowOrigins:      origins,
    46  		log:               log,
    47  	}
    48  }
    49  
    50  func (h *Handler) AttachRoutes(router *mux.Router) {
    51  	router.HandleFunc("/kubeconfig/{instance_id}", h.GetKubeconfig).Methods(http.MethodGet)
    52  	router.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    53  		h.handleResponse(w, http.StatusNotFound, fmt.Errorf("instanceID is required"))
    54  	})
    55  }
    56  
    57  type ErrorResponse struct {
    58  	Error string
    59  }
    60  
    61  func (h *Handler) GetKubeconfig(w http.ResponseWriter, r *http.Request) {
    62  	vars := mux.Vars(r)
    63  	instanceID := vars["instance_id"]
    64  
    65  	h.specifyAllowOriginHeader(r, w)
    66  
    67  	instance, err := h.instanceStorage.GetByID(instanceID)
    68  	switch {
    69  	case err == nil:
    70  	case dberr.IsNotFound(err):
    71  		h.handleResponse(w, http.StatusNotFound, fmt.Errorf("instance with ID %s does not exist", instanceID))
    72  		return
    73  	default:
    74  		h.handleResponse(w, http.StatusInternalServerError, err)
    75  		return
    76  	}
    77  
    78  	if broker.IsOwnClusterPlan(instance.ServicePlanID) {
    79  		h.handleResponse(w, http.StatusNotFound, fmt.Errorf("kubeconfig for instance %s does not exist", instanceID))
    80  		return
    81  	}
    82  
    83  	if instance.RuntimeID == "" {
    84  		h.handleResponse(w, http.StatusNotFound, fmt.Errorf("kubeconfig for instance %s does not exist. Provisioning could be in progress, please try again later", instanceID))
    85  		return
    86  	}
    87  
    88  	operation, err := h.operationStorage.GetProvisioningOperationByInstanceID(instanceID)
    89  	switch {
    90  	case err == nil:
    91  	case dberr.IsNotFound(err):
    92  		h.handleResponse(w, http.StatusNotFound, fmt.Errorf("provisioning operation for instance with ID %s does not exist", instanceID))
    93  		return
    94  	default:
    95  		h.handleResponse(w, http.StatusInternalServerError, err)
    96  		return
    97  	}
    98  
    99  	if operation.InstanceID != instanceID {
   100  		h.handleResponse(w, http.StatusBadRequest, fmt.Errorf("mismatch between operation and instance"))
   101  		return
   102  	}
   103  
   104  	switch operation.State {
   105  	case domain.InProgress, orchestration.Pending:
   106  		h.handleResponse(w, http.StatusNotFound, fmt.Errorf("provisioning operation for instance %s is in progress state, kubeconfig not exist yet, please try again later", instanceID))
   107  		return
   108  	case domain.Failed:
   109  		h.handleResponse(w, http.StatusNotFound, fmt.Errorf("provisioning operation for instance %s failed, kubeconfig does not exist", instanceID))
   110  		return
   111  	}
   112  
   113  	var newKubeconfig string
   114  	if instance.ServicePlanID == broker.OwnClusterPlanID {
   115  		newKubeconfig, err = h.kubeconfigBuilder.BuildFromAdminKubeconfig(instance, instance.InstanceDetails.Kubeconfig)
   116  	} else {
   117  		newKubeconfig, err = h.kubeconfigBuilder.Build(instance)
   118  	}
   119  	if err != nil {
   120  		h.handleResponse(w, http.StatusInternalServerError, fmt.Errorf("cannot fetch SKR kubeconfig: %s", err))
   121  		return
   122  	}
   123  
   124  	writeToResponse(w, newKubeconfig, h.log)
   125  }
   126  
   127  func (h *Handler) handleResponse(w http.ResponseWriter, code int, err error) {
   128  	errEncode := httputil.JSONEncodeWithCode(w, &ErrorResponse{Error: err.Error()}, code)
   129  	if errEncode != nil {
   130  		h.log.Errorf("cannot encode error response: %s", errEncode)
   131  	}
   132  }
   133  
   134  func (h *Handler) specifyAllowOriginHeader(r *http.Request, w http.ResponseWriter) {
   135  	origin := r.Header.Get("Origin")
   136  	origin = strings.ReplaceAll(origin, "\r", "")
   137  	origin = strings.ReplaceAll(origin, "\n", "")
   138  	if origin == "" {
   139  		return
   140  	}
   141  
   142  	if h.allowOrigins == "*" {
   143  		w.Header().Set("Access-Control-Allow-Origin", "*")
   144  		return
   145  	}
   146  
   147  	for _, o := range strings.Split(h.allowOrigins, ",") {
   148  		if o == origin {
   149  			w.Header().Set("Access-Control-Allow-Origin", sanitize.HTML(origin))
   150  			return
   151  		}
   152  	}
   153  }
   154  
   155  func writeToResponse(w http.ResponseWriter, data string, l logrus.FieldLogger) {
   156  	w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", attachmentName))
   157  	w.Header().Add("Content-Type", "application/x-yaml")
   158  	_, err := w.Write([]byte(data))
   159  	if err != nil {
   160  		l.Errorf("cannot write response with new kubeconfig: %s", err)
   161  	}
   162  }