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 }