github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/cmd/build/admission.go (about)

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package main
    18  
    19  import (
    20  	"crypto/tls"
    21  	"encoding/json"
    22  	"fmt"
    23  	"io"
    24  	"io/ioutil"
    25  	"net/http"
    26  
    27  	"github.com/sirupsen/logrus"
    28  
    29  	admissionapi "k8s.io/api/admission/v1beta1"
    30  	admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
    31  	"k8s.io/apimachinery/pkg/api/equality"
    32  	meta "k8s.io/apimachinery/pkg/apis/meta/v1"
    33  	"k8s.io/apimachinery/pkg/runtime"
    34  	"k8s.io/apimachinery/pkg/runtime/serializer"
    35  	prowjobv1 "k8s.io/test-infra/prow/apis/prowjobs/v1"
    36  
    37  	prowjobscheme "k8s.io/test-infra/prow/client/clientset/versioned/scheme"
    38  )
    39  
    40  var (
    41  	vscheme = runtime.NewScheme()
    42  	codecs  = serializer.NewCodecFactory(vscheme)
    43  )
    44  
    45  func init() {
    46  	prowjobscheme.AddToScheme(vscheme)
    47  	admissionapi.AddToScheme(vscheme)
    48  	admissionregistrationv1beta1.AddToScheme(vscheme)
    49  }
    50  
    51  // runServer starts an http server on :8443, responding to /validate requests
    52  func runServer(cert, privateKey string) {
    53  	http.HandleFunc("/validate", handle)
    54  	s := http.Server{
    55  		Addr: ":8443",
    56  		TLSConfig: &tls.Config{
    57  			ClientAuth: tls.NoClientCert,
    58  		},
    59  	}
    60  	logrus.Error(s.ListenAndServeTLS(cert, privateKey))
    61  }
    62  
    63  const contentTypeJSON = "application/json"
    64  
    65  // readRequest extracts the request from the AdmissionReview reader
    66  func readRequest(r io.Reader, contentType string) (*admissionapi.AdmissionRequest, error) {
    67  	if contentType != contentTypeJSON {
    68  		return nil, fmt.Errorf("Content-Type=%s, expected %s", contentType, contentTypeJSON)
    69  	}
    70  
    71  	// Can we read the body?
    72  	if r == nil {
    73  		return nil, fmt.Errorf("no body")
    74  	}
    75  	body, err := ioutil.ReadAll(r)
    76  	if err != nil {
    77  		return nil, fmt.Errorf("read body: %v", err)
    78  	}
    79  
    80  	// Can we convert the body into an AdmissionReview?
    81  	var ar admissionapi.AdmissionReview
    82  	deserializer := codecs.UniversalDeserializer()
    83  	if _, _, err := deserializer.Decode(body, nil, &ar); err != nil {
    84  		return nil, fmt.Errorf("decode body: %v", err)
    85  	}
    86  	return ar.Request, nil
    87  }
    88  
    89  // handle reads the request and writes the response
    90  func handle(w http.ResponseWriter, r *http.Request) {
    91  	req, err := readRequest(r.Body, r.Header.Get("Content-Type"))
    92  	if err != nil {
    93  		logrus.WithError(err).Error("read")
    94  	}
    95  
    96  	if err := writeResponse(*req, w, onlyUpdateStatus); err != nil {
    97  		logrus.WithError(err).Error("write")
    98  	}
    99  }
   100  
   101  type decider func(admissionapi.AdmissionRequest) (*admissionapi.AdmissionResponse, error)
   102  
   103  // writeResponse gets the response from onlyUpdateStatus and writes it to w.
   104  func writeResponse(ar admissionapi.AdmissionRequest, w io.Writer, decide decider) error {
   105  	response, err := decide(ar)
   106  	if err != nil {
   107  		logrus.WithError(err).Error("failed decision")
   108  		response = &admissionapi.AdmissionResponse{
   109  			Result: &meta.Status{
   110  				Message: err.Error(),
   111  			},
   112  		}
   113  	}
   114  	var result admissionapi.AdmissionReview
   115  	result.Response = response
   116  	result.Response.UID = ar.UID
   117  	out, err := json.Marshal(result)
   118  	if err != nil {
   119  		return fmt.Errorf("encode response: %v", err)
   120  	}
   121  	if _, err := w.Write(out); err != nil {
   122  		return fmt.Errorf("write response: %v", err)
   123  	}
   124  	return nil
   125  }
   126  
   127  var (
   128  	allow = admissionapi.AdmissionResponse{
   129  		Allowed: true,
   130  	}
   131  	reject = admissionapi.AdmissionResponse{
   132  		Result: &meta.Status{
   133  			Reason:  meta.StatusReasonForbidden,
   134  			Message: "ProwJobs may only update status",
   135  		},
   136  	}
   137  )
   138  
   139  // onlyUpdateStatus returns the response to the request
   140  func onlyUpdateStatus(req admissionapi.AdmissionRequest) (*admissionapi.AdmissionResponse, error) {
   141  	logger := logrus.WithFields(logrus.Fields{
   142  		"resource":    req.Resource,
   143  		"subresource": req.SubResource,
   144  		"name":        req.Name,
   145  		"namespace":   req.Namespace,
   146  		"operation":   req.Operation,
   147  	})
   148  
   149  	// Does this only update status?
   150  	if req.SubResource == "status" {
   151  		logrus.Info("accept status update")
   152  		return &allow, nil
   153  	}
   154  
   155  	// Otherwise, do the specs match?
   156  	var new prowjobv1.ProwJob
   157  	if _, _, err := codecs.UniversalDeserializer().Decode(req.Object.Raw, nil, &new); err != nil {
   158  		return nil, fmt.Errorf("decode new: %v", err)
   159  	}
   160  	var old prowjobv1.ProwJob
   161  	if _, _, err := codecs.UniversalDeserializer().Decode(req.OldObject.Raw, nil, &old); err != nil {
   162  		return nil, fmt.Errorf("decode old: %v", err)
   163  	}
   164  	if equality.Semantic.DeepEqual(old.Spec, new.Spec) {
   165  		logrus.Info("accept update with equivalent spec")
   166  		return &allow, nil // yes
   167  	}
   168  	logger.Info("reject") // no
   169  	return &reject, nil
   170  }