package main import ( "encoding/json" "fmt" "io/ioutil" "net/http" "github.com/golang/glog" "google.golang.org/protobuf/encoding/prototext" admission "k8s.io/api/admission/v1beta1" meta "k8s.io/apimachinery/pkg/apis/meta/v1" pb "code.hackerspace.pl/hscloud/cluster/admitomatic/config" ) type service struct { ingress ingressFilter } // newService creates an admitomatic service from a given prototext config. func newService(configuration []byte) (*service, error) { var cfg pb.Config if err := prototext.Unmarshal(configuration, &cfg); err != nil { return nil, fmt.Errorf("parsing config: %v", err) } s := service{} for i, ad := range cfg.AllowDomain { if ad.Namespace == "" { ad.Namespace = "default" } if ad.Dns == "" { return nil, fmt.Errorf("config entry %d: dns must be set", i) } if err := s.ingress.allow(ad.Namespace, ad.Dns); err != nil { return nil, fmt.Errorf("config entry %d: %v", i, err) } glog.Infof("Ingress: allowing %s in %s", ad.Dns, ad.Namespace) } return &s, nil } // handler is the main HTTP handler of the admitomatic service. It servers the // AdmissionReview API, and is called by the Kubernetes API server to // permit/deny creation/updating of resources. func (s *service) handler(w http.ResponseWriter, r *http.Request) { var body []byte if r.Body != nil { if data, err := ioutil.ReadAll(r.Body); err == nil { body = data } } if r.Method != "POST" { glog.Errorf("%s %s: invalid method", r.Method, r.URL) return } contentType := r.Header.Get("Content-Type") if contentType != "application/json" { glog.Errorf("%s %s: invalid content-type", r.Method, r.URL) return } var review admission.AdmissionReview if err := json.Unmarshal(body, &review); err != nil { glog.Errorf("%s %s: cannot decode: %v", r.Method, r.URL, err) return } if review.Kind != "AdmissionReview" { glog.Errorf("%s %s: invalid Kind (%q)", r.Method, r.URL, review.Kind) return } var err error req := review.Request resp := &admission.AdmissionResponse{ UID: req.UID, Allowed: true, } switch { case req.Kind.Group == "networking.k8s.io" && req.Kind.Kind == "Ingress": resp, err = s.ingress.admit(req) if err != nil { glog.Errorf("%s %s %s: %v", req.Operation, req.Name, req.Namespace, err) // Fail safe. // TODO(q3k): monitor this? resp = &admission.AdmissionResponse{ UID: req.UID, Allowed: false, Result: &meta.Status{ Code: 500, Message: "admitomatic: internal server error", }, } } } glog.Infof("%s %s %s in %s: %v (%v)", req.Operation, req.Kind.Kind, req.Name, req.Namespace, resp.Allowed, resp.Result) review.Response = resp json.NewEncoder(w).Encode(review) }