main.go (150 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package main import ( "encoding/json" "errors" "flag" "fmt" "io" "log" "net/http" "os" "gopkg.in/yaml.v3" admissionv1 "k8s.io/api/admission/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func main() { var ( certPath = flag.String("certFile", "/opt/webhook/certs/cert.pem", "path to cert.pem") keyPath = flag.String("keyFile", "/opt/webhook/certs/key.pem", "path to key.pem") configPath = flag.String("config", "/opt/webhook/config/webhook.yaml", "path to config.yaml") ) flag.Parse() cfg, err := parseConfig(*configPath) if err != nil { log.Fatalf("error reading config: %v", err) } ss := &server{ l: log.Default(), c: cfg.Agents, } s := &http.Server{ Addr: ":8443", Handler: ss, } log.Println("listening on :8443") log.Fatal(s.ListenAndServeTLS(*certPath, *keyPath)) } func parseConfig(configPath string) (*config, error) { f, err := os.Open(configPath) if err != nil { return nil, err } defer f.Close() c := new(config) if err := yaml.NewDecoder(f).Decode(c); err != nil { return nil, fmt.Errorf("failed to decode config: %w", err) } for k, ac := range c.Agents { if ac.Image == "" { return nil, fmt.Errorf("custom agent %q is missing 'image' value", k) } if ac.ArtifactPath == "" { return nil, fmt.Errorf("custom agent %q is missing 'artifact' value", k) } } return c, nil } type server struct { l *log.Logger c map[string]agentConfig } type config struct { Agents map[string]agentConfig `yaml:"agents"` } type agentConfig struct { Image string `yaml:"image"` Environment map[string]string `yaml:"environment"` ArtifactPath string `yaml:"artifact"` } func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) { body, err := io.ReadAll(r.Body) if err != nil { s.sendError(err, w) return } admReview := admissionv1.AdmissionReview{} if err := json.Unmarshal(body, &admReview); err != nil { s.sendError(err, w) return } if err := s.mutate(&admReview); err != nil { s.sendError(err, w) return } resp, err := json.Marshal(admReview) if err != nil { s.sendError(err, w) return } if _, err := w.Write(resp); err != nil { s.sendError(err, w) return } } func (s *server) sendError(err error, w http.ResponseWriter) { s.l.Println(err) w.WriteHeader(http.StatusInternalServerError) fmt.Fprintf(w, "%s", err) } func (s *server) mutate(admReview *admissionv1.AdmissionReview) error { var pod *corev1.Pod ar := admReview.Request resp := admissionv1.AdmissionResponse{} if ar == nil { // TODO: Is this right? return nil } if err := json.Unmarshal(ar.Object.Raw, &pod); err != nil { return fmt.Errorf("unable unmarshal pod json object %v", err) } resp.Allowed = true resp.UID = ar.UID config, err := getConfig(s.c, pod.ObjectMeta.GetAnnotations()) if err != nil { resp.Result = &metav1.Status{Message: err.Error()} admReview.Response = &resp return nil } pT := admissionv1.PatchTypeJSONPatch resp.PatchType = &pT patch, err := createPatch(config, pod.Spec) if err != nil { return err } marshaled, err := json.Marshal(patch) if err != nil { return err } resp.Patch = marshaled resp.Result = &metav1.Status{ Status: "Success", } admReview.Response = &resp return nil } const apmAnnotation = "co.elastic.apm/attach" func getConfig(configs map[string]agentConfig, annotations map[string]string) (agentConfig, error) { ac := agentConfig{} if annotations == nil { return ac, errors.New("no annotations present") } // TODO: Do we want to support multiple comma-separated agents? agent, ok := annotations[apmAnnotation] if !ok { return ac, fmt.Errorf("missing annotation `%s`", apmAnnotation) } // TODO: validate the config has a container field config, ok := configs[agent] if !ok { return ac, fmt.Errorf("no config for agent `%s`", agent) } return config, nil }