// write renders a returned runtime.Object to the response as a stream or an encoded object. If the object // returned by the response implements rest.ResourceStreamer that interface will be used to render the // response. The Accept header and current API version will be passed in, and the output will be copied // directly to the response body. If content type is returned it is used, otherwise the content type will // be "application/octet-stream". All other objects are sent to standard JSON serialization. func write(statusCode int, apiVersion string, codec runtime.Codec, object runtime.Object, w http.ResponseWriter, req *http.Request) { if stream, ok := object.(rest.ResourceStreamer); ok { out, flush, contentType, err := stream.InputStream(apiVersion, req.Header.Get("Accept")) if err != nil { errorJSONFatal(err, codec, w) return } if out == nil { // No output provided - return StatusNoContent w.WriteHeader(http.StatusNoContent) return } defer out.Close() if len(contentType) == 0 { contentType = "application/octet-stream" } w.Header().Set("Content-Type", contentType) w.WriteHeader(statusCode) writer := w.(io.Writer) if flush { writer = flushwriter.Wrap(w) } io.Copy(writer, out) return } writeJSON(statusCode, codec, object, w, isPrettyPrint(req)) }
// getContainerLogs handles containerLogs request against the Kubelet func (s *Server) getContainerLogs(request *restful.Request, response *restful.Response) { podNamespace := request.PathParameter("podNamespace") podID := request.PathParameter("podID") containerName := request.PathParameter("containerName") if len(podID) == 0 { // TODO: Why return JSON when the rest return plaintext errors? response.WriteError(http.StatusBadRequest, fmt.Errorf(`{"message": "Missing podID."}`)) return } if len(containerName) == 0 { // TODO: Why return JSON when the rest return plaintext errors? response.WriteError(http.StatusBadRequest, fmt.Errorf(`{"message": "Missing container name."}`)) return } if len(podNamespace) == 0 { // TODO: Why return JSON when the rest return plaintext errors? response.WriteError(http.StatusBadRequest, fmt.Errorf(`{"message": "Missing podNamespace."}`)) return } follow, _ := strconv.ParseBool(request.QueryParameter("follow")) previous, _ := strconv.ParseBool(request.QueryParameter("previous")) tail := request.QueryParameter("tail") pod, ok := s.host.GetPodByName(podNamespace, podID) if !ok { response.WriteError(http.StatusNotFound, fmt.Errorf("Pod %q does not exist", podID)) return } // Check if containerName is valid. containerExists := false for _, container := range pod.Spec.Containers { if container.Name == containerName { containerExists = true } } if !containerExists { response.WriteError(http.StatusNotFound, fmt.Errorf("Container %q not found in Pod %q", containerName, podID)) return } if _, ok := response.ResponseWriter.(http.Flusher); !ok { response.WriteError(http.StatusInternalServerError, fmt.Errorf("unable to convert %v into http.Flusher", response)) return } fw := flushwriter.Wrap(response) response.Header().Set("Transfer-Encoding", "chunked") response.WriteHeader(http.StatusOK) err := s.host.GetKubeletContainerLogs(kubecontainer.GetPodFullName(pod), containerName, tail, follow, previous, fw, fw) if err != nil { response.WriteError(http.StatusInternalServerError, err) return } }
// write renders a returned runtime.Object to the response as a stream or an encoded object. If the object // returned by the response implements rest.ResourceStreamer that interface will be used to render the // response. The Accept header and current API version will be passed in, and the output will be copied // directly to the response body. If content type is returned it is used, otherwise the content type will // be "application/octet-stream". All other objects are sent to standard JSON serialization. func write(statusCode int, gv schema.GroupVersion, s runtime.NegotiatedSerializer, object runtime.Object, w http.ResponseWriter, req *http.Request) { stream, ok := object.(rest.ResourceStreamer) if !ok { writeNegotiated(s, gv, w, req, statusCode, object) return } out, flush, contentType, err := stream.InputStream(gv.String(), req.Header.Get("Accept")) if err != nil { errorNegotiated(err, s, gv, w, req) return } if out == nil { // No output provided - return StatusNoContent w.WriteHeader(http.StatusNoContent) return } defer out.Close() if wsstream.IsWebSocketRequest(req) { r := wsstream.NewReader(out, true, wsstream.NewDefaultReaderProtocols()) if err := r.Copy(w, req); err != nil { utilruntime.HandleError(fmt.Errorf("error encountered while streaming results via websocket: %v", err)) } return } if len(contentType) == 0 { contentType = "application/octet-stream" } w.Header().Set("Content-Type", contentType) w.WriteHeader(statusCode) writer := w.(io.Writer) if flush { writer = flushwriter.Wrap(w) } io.Copy(writer, out) }
// write renders a returned runtime.Object to the response as a stream or an encoded object. If the object // returned by the response implements rest.ResourceStreamer that interface will be used to render the // response. The Accept header and current API version will be passed in, and the output will be copied // directly to the response body. If content type is returned it is used, otherwise the content type will // be "application/octet-stream". All other objects are sent to standard JSON serialization. func write(statusCode int, apiVersion string, codec runtime.Codec, object runtime.Object, w http.ResponseWriter, req *http.Request) { if stream, ok := object.(rest.ResourceStreamer); ok { out, flush, contentType, err := stream.InputStream(apiVersion, req.Header.Get("Accept")) if err != nil { errorJSONFatal(err, codec, w) return } if out == nil { // No output provided - return StatusNoContent w.WriteHeader(http.StatusNoContent) return } defer out.Close() if wsstream.IsWebSocketRequest(req) { r := wsstream.NewReader(out, true) if err := r.Copy(w, req); err != nil { util.HandleError(fmt.Errorf("error encountered while streaming results via websocket: %v", err)) } return } if len(contentType) == 0 { contentType = "application/octet-stream" } w.Header().Set("Content-Type", contentType) w.WriteHeader(statusCode) writer := w.(io.Writer) if flush { writer = flushwriter.Wrap(w) } io.Copy(writer, out) return } writeJSON(statusCode, codec, object, w, isPrettyPrint(req)) }
// getContainerLogs handles containerLogs request against the Kubelet func (s *Server) getContainerLogs(request *restful.Request, response *restful.Response) { podNamespace := request.PathParameter("podNamespace") podID := request.PathParameter("podID") containerName := request.PathParameter("containerName") if len(podID) == 0 { // TODO: Why return JSON when the rest return plaintext errors? // TODO: Why return plaintext errors? response.WriteError(http.StatusBadRequest, fmt.Errorf(`{"message": "Missing podID."}`)) return } if len(containerName) == 0 { // TODO: Why return JSON when the rest return plaintext errors? response.WriteError(http.StatusBadRequest, fmt.Errorf(`{"message": "Missing container name."}`)) return } if len(podNamespace) == 0 { // TODO: Why return JSON when the rest return plaintext errors? response.WriteError(http.StatusBadRequest, fmt.Errorf(`{"message": "Missing podNamespace."}`)) return } query := request.Request.URL.Query() // backwards compatibility for the "tail" query parameter if tail := request.QueryParameter("tail"); len(tail) > 0 { query["tailLines"] = []string{tail} // "all" is the same as omitting tail if tail == "all" { delete(query, "tailLines") } } // container logs on the kubelet are locked to v1 versioned := &v1.PodLogOptions{} if err := api.Scheme.Convert(&query, versioned); err != nil { response.WriteError(http.StatusBadRequest, fmt.Errorf(`{"message": "Unable to decode query."}`)) return } out, err := api.Scheme.ConvertToVersion(versioned, "") if err != nil { response.WriteError(http.StatusBadRequest, fmt.Errorf(`{"message": "Unable to convert request query."}`)) return } logOptions := out.(*api.PodLogOptions) logOptions.TypeMeta = unversioned.TypeMeta{} if errs := validation.ValidatePodLogOptions(logOptions); len(errs) > 0 { response.WriteError(apierrs.StatusUnprocessableEntity, fmt.Errorf(`{"message": "Invalid request."}`)) return } pod, ok := s.host.GetPodByName(podNamespace, podID) if !ok { response.WriteError(http.StatusNotFound, fmt.Errorf("Pod %q does not exist", podID)) return } // Check if containerName is valid. containerExists := false for _, container := range pod.Spec.Containers { if container.Name == containerName { containerExists = true } } if !containerExists { response.WriteError(http.StatusNotFound, fmt.Errorf("Container %q not found in Pod %q", containerName, podID)) return } if _, ok := response.ResponseWriter.(http.Flusher); !ok { response.WriteError(http.StatusInternalServerError, fmt.Errorf("unable to convert %v into http.Flusher", response)) return } fw := flushwriter.Wrap(response.ResponseWriter) if logOptions.LimitBytes != nil { fw = limitwriter.New(fw, *logOptions.LimitBytes) } response.Header().Set("Transfer-Encoding", "chunked") response.WriteHeader(http.StatusOK) if err := s.host.GetKubeletContainerLogs(kubecontainer.GetPodFullName(pod), containerName, logOptions, fw, fw); err != nil { if err != limitwriter.ErrMaximumWrite { response.WriteError(http.StatusInternalServerError, err) } return } }
// handleContainerLogs handles containerLogs request against the Kubelet func (s *Server) handleContainerLogs(w http.ResponseWriter, req *http.Request) { defer req.Body.Close() u, err := url.ParseRequestURI(req.RequestURI) if err != nil { s.error(w, err) return } parts := strings.Split(u.Path, "/") // req URI: /containerLogs/<podNamespace>/<podID>/<containerName> var podNamespace, podID, containerName string if len(parts) == 5 { podNamespace = parts[2] podID = parts[3] containerName = parts[4] } else { http.Error(w, "Unexpected path for command running", http.StatusBadRequest) return } if len(podID) == 0 { http.Error(w, `{"message": "Missing podID."}`, http.StatusBadRequest) return } if len(containerName) == 0 { http.Error(w, `{"message": "Missing container name."}`, http.StatusBadRequest) return } if len(podNamespace) == 0 { http.Error(w, `{"message": "Missing podNamespace."}`, http.StatusBadRequest) return } uriValues := u.Query() follow, _ := strconv.ParseBool(uriValues.Get("follow")) previous, _ := strconv.ParseBool(uriValues.Get("previous")) tail := uriValues.Get("tail") pod, ok := s.host.GetPodByName(podNamespace, podID) if !ok { http.Error(w, fmt.Sprintf("Pod %q does not exist", podID), http.StatusNotFound) return } // Check if containerName is valid. containerExists := false for _, container := range pod.Spec.Containers { if container.Name == containerName { containerExists = true } } if !containerExists { http.Error(w, fmt.Sprintf("Container %q not found in Pod %q", containerName, podID), http.StatusNotFound) return } if _, ok := w.(http.Flusher); !ok { s.error(w, fmt.Errorf("unable to convert %v into http.Flusher", w)) return } fw := flushwriter.Wrap(w) w.Header().Set("Transfer-Encoding", "chunked") w.WriteHeader(http.StatusOK) err = s.host.GetKubeletContainerLogs(kubecontainer.GetPodFullName(pod), containerName, tail, follow, previous, fw, fw) if err != nil { s.error(w, err) return } }
// getContainerLogs handles containerLogs request against the Kubelet func (s *Server) getContainerLogs(request *restful.Request, response *restful.Response) { podNamespace := request.PathParameter("podNamespace") podID := request.PathParameter("podID") containerName := request.PathParameter("containerName") if len(podID) == 0 { // TODO: Why return JSON when the rest return plaintext errors? // TODO: Why return plaintext errors? response.WriteError(http.StatusBadRequest, fmt.Errorf(`{"message": "Missing podID."}`)) return } if len(containerName) == 0 { // TODO: Why return JSON when the rest return plaintext errors? response.WriteError(http.StatusBadRequest, fmt.Errorf(`{"message": "Missing container name."}`)) return } if len(podNamespace) == 0 { // TODO: Why return JSON when the rest return plaintext errors? response.WriteError(http.StatusBadRequest, fmt.Errorf(`{"message": "Missing podNamespace."}`)) return } query := request.Request.URL.Query() // backwards compatibility for the "tail" query parameter if tail := request.QueryParameter("tail"); len(tail) > 0 { query["tailLines"] = []string{tail} // "all" is the same as omitting tail if tail == "all" { delete(query, "tailLines") } } // container logs on the kubelet are locked to the v1 API version of PodLogOptions logOptions := &v1.PodLogOptions{} if err := api.ParameterCodec.DecodeParameters(query, v1.SchemeGroupVersion, logOptions); err != nil { response.WriteError(http.StatusBadRequest, fmt.Errorf(`{"message": "Unable to decode query."}`)) return } logOptions.TypeMeta = metav1.TypeMeta{} if errs := validation.ValidatePodLogOptions(logOptions); len(errs) > 0 { response.WriteError(apierrs.StatusUnprocessableEntity, fmt.Errorf(`{"message": "Invalid request."}`)) return } pod, ok := s.host.GetPodByName(podNamespace, podID) if !ok { response.WriteError(http.StatusNotFound, fmt.Errorf("pod %q does not exist\n", podID)) return } // Check if containerName is valid. containerExists := false for _, container := range pod.Spec.Containers { if container.Name == containerName { containerExists = true } } if !containerExists { for _, container := range pod.Spec.InitContainers { if container.Name == containerName { containerExists = true } } } if !containerExists { response.WriteError(http.StatusNotFound, fmt.Errorf("container %q not found in pod %q\n", containerName, podID)) return } if _, ok := response.ResponseWriter.(http.Flusher); !ok { response.WriteError(http.StatusInternalServerError, fmt.Errorf("unable to convert %v into http.Flusher, cannot show logs\n", reflect.TypeOf(response))) return } fw := flushwriter.Wrap(response.ResponseWriter) // Byte limit logic is already implemented in kuberuntime. However, we still need this for // old runtime integration. // TODO(random-liu): Remove this once we switch to CRI integration. if logOptions.LimitBytes != nil { fw = limitwriter.New(fw, *logOptions.LimitBytes) } response.Header().Set("Transfer-Encoding", "chunked") if err := s.host.GetKubeletContainerLogs(kubecontainer.GetPodFullName(pod), containerName, logOptions, fw, fw); err != nil { if err != limitwriter.ErrMaximumWrite { response.WriteError(http.StatusBadRequest, err) } return } }