| 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 ad.Regexp { |
| if err := s.ingress.allowRegexp(ad.Namespace, ad.Dns); err != nil { |
| return nil, fmt.Errorf("config entry (regexp) %d: %v", i, err) |
| } |
| } else { |
| 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) |
| } |
| s.ingress.anythingGoesNamespaces = cfg.AnythingGoesNamespace |
| 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) |
| } |