From 08cc1cbb772b78564f1f96c024d75431e3055693 Mon Sep 17 00:00:00 2001 From: Anton Bronnikov Date: Sat, 10 Aug 2024 14:46:33 +0200 Subject: [PATCH] feat: inject volumes and volume mounts --- LICENSE | 18 ++ Makefile | 15 +- cert/cert.go | 8 +- cert/self_signer.go | 22 +-- config/config.go | 8 +- config/container.go | 150 ---------------- config/inject.go | 107 ++++++++--- config/inject_container.go | 126 +++++++++++++ config/inject_container_port.go | 43 +++++ .../inject_container_resource_requirements.go | 73 ++++++++ config/inject_label_selector.go | 56 ++++++ config/inject_match_expression.go | 34 ++++ config/inject_volume.go | 44 +++++ config/inject_volume_config_map.go | 78 ++++++++ config/inject_volume_key_to_path.go | 37 ++++ config/inject_volume_mount.go | 87 +++++++++ config/label_selector.go | 64 ------- logutils/setup.go | 12 +- patch/add_container_volume_mounts.go | 43 +++++ patch/add_pod_volumes.go | 40 +++++ server/k8s.go | 170 ++++++++++++++---- {deploy => test}/cluster-role.yaml | 6 +- test/configmap.yaml | 11 ++ {deploy => test}/deployment-fargate.yaml | 8 +- .../deployment-node-exporter.yaml | 21 ++- {deploy => test}/dummy.yaml | 0 26 files changed, 966 insertions(+), 315 deletions(-) create mode 100644 LICENSE delete mode 100644 config/container.go create mode 100644 config/inject_container.go create mode 100644 config/inject_container_port.go create mode 100644 config/inject_container_resource_requirements.go create mode 100644 config/inject_label_selector.go create mode 100644 config/inject_match_expression.go create mode 100644 config/inject_volume.go create mode 100644 config/inject_volume_config_map.go create mode 100644 config/inject_volume_key_to_path.go create mode 100644 config/inject_volume_mount.go delete mode 100644 config/label_selector.go create mode 100644 patch/add_container_volume_mounts.go create mode 100644 patch/add_pod_volumes.go rename {deploy => test}/cluster-role.yaml (79%) create mode 100644 test/configmap.yaml rename {deploy => test}/deployment-fargate.yaml (91%) rename {deploy => test}/deployment-node-exporter.yaml (85%) rename {deploy => test}/dummy.yaml (100%) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6d272e4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,18 @@ +Copyright (c) 2024 Flashbots + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the “Software”), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile index 6d1dfea..4342e1f 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,10 @@ build: -o ./bin/kube-sidecar-injector \ github.com/flashbots/kube-sidecar-injector/cmd +.PHONY: docker +docker: + docker build -t kube-sidecar-injector:${VERSION} . + .PHONY: snapshot snapshot: @goreleaser release --snapshot --clean @@ -20,11 +24,12 @@ image: . .PHONY: deploy -deploy: +deploy: image @kubectl \ --context orbstack \ apply \ - --filename deploy/cluster-role.yaml \ - --filename deploy/dummy.yaml \ - --filename deploy/deployment-fargate.yaml \ - --filename deploy/deployment-node-exporter.yaml + --filename test/cluster-role.yaml \ + --filename test/configmap.yaml \ + --filename test/dummy.yaml \ + --filename test/deployment-fargate.yaml \ + --filename test/deployment-node-exporter.yaml diff --git a/cert/cert.go b/cert/cert.go index 97efa8b..b1e045a 100644 --- a/cert/cert.go +++ b/cert/cert.go @@ -15,8 +15,8 @@ type Source interface { } var ( - ErrFailedToGenerateCert = errors.New("failed to generate certificate") - ErrFailedToGeneratePrivateKey = errors.New("failed to generate new private key") - ErrFailedToRegenerateCA = errors.New("failed to (re-)generate ca") - ErrUnspecifiedHosts = errors.New("no hosts specified for the certificate") + errFailedToGenerateCert = errors.New("failed to generate certificate") + errFailedToGeneratePrivateKey = errors.New("failed to generate new private key") + errFailedToRegenerateCA = errors.New("failed to (re-)generate ca") + errUnspecifiedHosts = errors.New("no hosts specified for the certificate") ) diff --git a/cert/self_signer.go b/cert/self_signer.go index 5a0b35f..e26823a 100644 --- a/cert/self_signer.go +++ b/cert/self_signer.go @@ -29,7 +29,7 @@ type selfSigner struct { func NewSelfSigner(organisation string, hosts []string) (Source, error) { if len(hosts) == 0 { - return nil, ErrUnspecifiedHosts + return nil, errUnspecifiedHosts } serial, err := rand.Int(rand.Reader, (&big.Int{}).Exp(big.NewInt(2), big.NewInt(128), nil)) @@ -66,18 +66,18 @@ func (s *selfSigner) NewBundle() (*Bundle, error) { func (s *selfSigner) newEcPrivateKey() (string, crypto.Signer, error) { key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { - return "", nil, fmt.Errorf("%w: %w", ErrFailedToGeneratePrivateKey, err) + return "", nil, fmt.Errorf("%w: %w", errFailedToGeneratePrivateKey, err) } bts, err := x509.MarshalECPrivateKey(key) if err != nil { - return "", nil, fmt.Errorf("%w: %w", ErrFailedToGeneratePrivateKey, err) + return "", nil, fmt.Errorf("%w: %w", errFailedToGeneratePrivateKey, err) } var buf bytes.Buffer err = pem.Encode(&buf, &pem.Block{Type: "EC PRIVATE KEY", Bytes: bts}) if err != nil { - return "", nil, fmt.Errorf("%w: %w", ErrFailedToGeneratePrivateKey, err) + return "", nil, fmt.Errorf("%w: %w", errFailedToGeneratePrivateKey, err) } return buf.String(), key, nil @@ -88,7 +88,7 @@ func (s *selfSigner) regenerateCA() error { _, sgn, err := s.newEcPrivateKey() if err != nil { - return fmt.Errorf("%w: %w", ErrFailedToRegenerateCA, err) + return fmt.Errorf("%w: %w", errFailedToRegenerateCA, err) } tpl := &x509.Certificate{ @@ -106,13 +106,13 @@ func (s *selfSigner) regenerateCA() error { bts, err := x509.CreateCertificate(rand.Reader, tpl, tpl, sgn.Public(), sgn) if err != nil { - return fmt.Errorf("%w: %w", ErrFailedToRegenerateCA, err) + return fmt.Errorf("%w: %w", errFailedToRegenerateCA, err) } var buf bytes.Buffer err = pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: bts}) if err != nil { - return fmt.Errorf("%w: %w", ErrFailedToRegenerateCA, err) + return fmt.Errorf("%w: %w", errFailedToRegenerateCA, err) } s.caSigner = sgn @@ -127,7 +127,7 @@ func (s *selfSigner) generateCert() (*tls.Certificate, error) { key, sgn, err := s.newEcPrivateKey() if err != nil { - return nil, fmt.Errorf("%w: %w", ErrFailedToGenerateCert, err) + return nil, fmt.Errorf("%w: %w", errFailedToGenerateCert, err) } cert := &x509.Certificate{ @@ -152,18 +152,18 @@ func (s *selfSigner) generateCert() (*tls.Certificate, error) { bts, err := x509.CreateCertificate(rand.Reader, cert, s.caTemplate, sgn.Public(), s.caSigner) if err != nil { - return nil, fmt.Errorf("%w: %w", ErrFailedToGenerateCert, err) + return nil, fmt.Errorf("%w: %w", errFailedToGenerateCert, err) } var buf bytes.Buffer err = pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: bts}) if err != nil { - return nil, fmt.Errorf("%w: %w", ErrFailedToGenerateCert, err) + return nil, fmt.Errorf("%w: %w", errFailedToGenerateCert, err) } pair, err := tls.X509KeyPair(buf.Bytes(), []byte(key)) if err != nil { - return nil, fmt.Errorf("%w: %w", ErrFailedToGenerateCert, err) + return nil, fmt.Errorf("%w: %w", errFailedToGenerateCert, err) } return &pair, nil diff --git a/config/config.go b/config/config.go index d4e6f68..7f95942 100644 --- a/config/config.go +++ b/config/config.go @@ -18,8 +18,8 @@ type Config struct { } var ( - ErrConfigFailedToReadFromFile = errors.New("failed to read configuration from file") - ErrConfigurationFailedToDecode = errors.New("failed to decode configuration") + errConfigFailedToReadFromFile = errors.New("failed to read configuration from file") + errConfigurationFailedToDecode = errors.New("failed to decode configuration") ) func ReadFrom(file string) ( @@ -28,7 +28,7 @@ func ReadFrom(file string) ( f, err := os.OpenFile(file, os.O_RDONLY, 0) if err != nil { return nil, fmt.Errorf("%w: %s: %w", - ErrConfigFailedToReadFromFile, file, err, + errConfigFailedToReadFromFile, file, err, ) } d := yaml.NewDecoder(f) @@ -36,7 +36,7 @@ func ReadFrom(file string) ( var _cfg Config if err := d.Decode(&_cfg); err != nil { return nil, fmt.Errorf("%w: %s: %w", - ErrConfigurationFailedToDecode, file, err, + errConfigurationFailedToDecode, file, err, ) } return &Config{ diff --git a/config/container.go b/config/container.go deleted file mode 100644 index 2c8a43f..0000000 --- a/config/container.go +++ /dev/null @@ -1,150 +0,0 @@ -package config - -import ( - "fmt" - "hash" - "unsafe" - - core_v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" -) - -type Container struct { - Name string `yaml:"name,omitempty"` - Image string `yaml:"image,omitempty"` - Command []string `yaml:"command,omitempty"` - Args []string `yaml:"args,omitempty"` - Ports []ContainerPort `yaml:"ports,omitempty"` - Resources *ResourceRequirements `yaml:"resources,omitempty"` -} - -type ContainerPort struct { - Name string `yaml:"name,omitempty"` - HostPort int32 `yaml:"hostPort,omitempty"` - ContainerPort int32 `yaml:"containerPort,omitempty"` - Protocol string `yaml:"protocol,omitempty"` - HostIP string `yaml:"hostIP,omitempty"` -} - -type ResourceRequirements struct { - Limits map[string]string `yaml:"limits,omitempty"` - Requests map[string]string `yaml:"requests,omitempty"` -} - -func (c Container) hash(sum hash.Hash64) { - sum.Write([]byte("name:")) - sum.Write([]byte(c.Name)) - sum.Write([]byte{255}) - - sum.Write([]byte("image:")) - sum.Write([]byte(c.Image)) - sum.Write([]byte{255}) - - sum.Write([]byte("command:")) - for _, cmd := range c.Command { - sum.Write([]byte(cmd)) - sum.Write([]byte{255}) - } - - sum.Write([]byte("args:")) - for _, arg := range c.Args { - sum.Write([]byte(arg)) - sum.Write([]byte{255}) - } - - sum.Write([]byte("ports:")) - for _, p := range c.Ports { - sum.Write([]byte("name:")) - sum.Write([]byte(p.Name)) - sum.Write([]byte{255}) - - sum.Write([]byte("hostPort:")) - sum.Write(unsafe.Slice( - (*byte)(unsafe.Pointer(&p.ContainerPort)), - unsafe.Sizeof(p.ContainerPort), - )) - sum.Write([]byte{255}) - - sum.Write([]byte("protocol:")) - sum.Write([]byte(p.Protocol)) - sum.Write([]byte{255}) - - sum.Write([]byte("hostIP:")) - sum.Write([]byte(p.HostIP)) - sum.Write([]byte{255}) - } - - if c.Resources != nil { - sum.Write([]byte("resources:")) - - sum.Write([]byte("limits:")) - for k, v := range c.Resources.Limits { - sum.Write([]byte("key:")) - sum.Write([]byte(k)) - sum.Write([]byte{255}) - - sum.Write([]byte("value:")) - sum.Write([]byte(v)) - sum.Write([]byte{255}) - } - - sum.Write([]byte("requests:")) - for k, v := range c.Resources.Requests { - sum.Write([]byte("key:")) - sum.Write([]byte(k)) - sum.Write([]byte{255}) - - sum.Write([]byte("value:")) - sum.Write([]byte(v)) - sum.Write([]byte{255}) - } - } -} - -func (c Container) Container() (*core_v1.Container, error) { - ports := make([]core_v1.ContainerPort, 0, len(c.Ports)) - for _, p := range c.Ports { - ports = append(ports, core_v1.ContainerPort{ - Name: p.Name, - HostPort: p.HostPort, - ContainerPort: p.ContainerPort, - Protocol: core_v1.Protocol(p.Protocol), - HostIP: p.HostIP, - }) - } - - limits := make(map[core_v1.ResourceName]resource.Quantity) - if c.Resources != nil { - for k, v := range c.Resources.Limits { - q, err := resource.ParseQuantity(v) - if err != nil { - return nil, fmt.Errorf("%w: %s", err, v) - } - limits[core_v1.ResourceName(k)] = q - } - } - - requests := make(map[core_v1.ResourceName]resource.Quantity) - if c.Resources != nil { - for k, v := range c.Resources.Requests { - q, err := resource.ParseQuantity(v) - if err != nil { - return nil, fmt.Errorf("%w: %s", err, v) - } - requests[core_v1.ResourceName(k)] = q - } - } - - return &core_v1.Container{ - Name: c.Name, - Image: c.Image, - Command: c.Command, - Args: c.Args, - - Ports: ports, - Resources: core_v1.ResourceRequirements{ - Limits: limits, - Requests: requests, - }, - }, nil -} diff --git a/config/inject.go b/config/inject.go index 3ee3c55..75ee7cb 100644 --- a/config/inject.go +++ b/config/inject.go @@ -6,49 +6,104 @@ import ( ) type Inject struct { - LabelSelector *LabelSelector `yaml:"labelSelector,omitempty"` - NamespaceSelector *LabelSelector `yaml:"namespaceSelector,omitempty"` + Name string `yaml:"name,omitempty"` + + LabelSelector *InjectLabelSelector `yaml:"labelSelector,omitempty"` + NamespaceSelector *InjectLabelSelector `yaml:"namespaceSelector,omitempty"` Annotations map[string]string `yaml:"annotations,omitempty"` Labels map[string]string `yaml:"labels,omitempty"` - Containers []Container `yaml:"containers,omitempty"` + Containers []InjectContainer `yaml:"containers,omitempty"` + VolumeMounts []InjectVolumeMount `yaml:"volumeMounts,omitempty"` + Volumes []InjectVolume `yaml:"volumes,omitempty"` } func (i Inject) Fingerprint() string { sum := fnv.New64a() - sum.Write([]byte("labelSelector:")) - i.LabelSelector.hash(sum) + { // name + sum.Write([]byte("name:")) + sum.Write([]byte(i.Name)) + sum.Write([]byte{255}) + } - sum.Write([]byte("namespaceSelector:")) - i.NamespaceSelector.hash(sum) + { // labelSelector + if i.LabelSelector != nil { + sum.Write([]byte("labelSelector:")) + i.LabelSelector.hash(sum) + sum.Write([]byte{255}) + } + } - sum.Write([]byte("annotations:")) - for k, v := range i.Annotations { - sum.Write([]byte("key:")) - sum.Write([]byte(k)) - sum.Write([]byte{255}) + { // namespaceSelector + if i.NamespaceSelector != nil { + sum.Write([]byte("namespaceSelector:")) + i.NamespaceSelector.hash(sum) + sum.Write([]byte{255}) + } + } - sum.Write([]byte("value:")) - sum.Write([]byte(v)) - sum.Write([]byte{255}) + { // annotations + if len(i.Annotations) > 0 { + sum.Write([]byte("annotations:")) + for k, v := range i.Annotations { + sum.Write([]byte("key:")) + sum.Write([]byte(k)) + sum.Write([]byte{255}) + + sum.Write([]byte("value:")) + sum.Write([]byte(v)) + sum.Write([]byte{255}) + } + sum.Write([]byte{255}) + } } - sum.Write([]byte("labels:")) - for k, v := range i.Labels { - sum.Write([]byte("key:")) - sum.Write([]byte(k)) - sum.Write([]byte{255}) + { // labels + if len(i.Labels) > 0 { + sum.Write([]byte("labels:")) + for k, v := range i.Labels { + sum.Write([]byte("key:")) + sum.Write([]byte(k)) + sum.Write([]byte{255}) - sum.Write([]byte("value:")) - sum.Write([]byte(v)) - sum.Write([]byte{255}) + sum.Write([]byte("value:")) + sum.Write([]byte(v)) + sum.Write([]byte{255}) + } + sum.Write([]byte{255}) + } + } + + { // containers + if len(i.Containers) > 0 { + sum.Write([]byte("containers:")) + for _, c := range i.Containers { + c.hash(sum) + } + sum.Write([]byte{255}) + } + } + + { // volumeMounts + if len(i.VolumeMounts) > 0 { + sum.Write([]byte("volumeMounts:")) + for _, vm := range i.VolumeMounts { + vm.hash(sum) + } + sum.Write([]byte{255}) + } } - sum.Write([]byte("containers:")) - for _, c := range i.Containers { - c.hash(sum) + { // volumes + if len(i.Volumes) > 0 { + sum.Write([]byte("volumes:")) + for _, v := range i.Volumes { + v.hash(sum) + } + sum.Write([]byte{255}) + } } return fmt.Sprintf("%016x", sum.Sum64()) diff --git a/config/inject_container.go b/config/inject_container.go new file mode 100644 index 0000000..eaf0475 --- /dev/null +++ b/config/inject_container.go @@ -0,0 +1,126 @@ +package config + +import ( + "hash" + + core_v1 "k8s.io/api/core/v1" +) + +type InjectContainer struct { + Name string `yaml:"name,omitempty"` + + Image string `yaml:"image,omitempty"` + Command []string `yaml:"command,omitempty"` + Args []string `yaml:"args,omitempty"` + + Ports []InjectContainerPort `yaml:"ports,omitempty"` + Resources *InjectContainerResourceRequirements `yaml:"resources,omitempty"` + VolumeMounts []InjectVolumeMount `yaml:"volumeMounts,omitempty"` +} + +func (c InjectContainer) hash(sum hash.Hash64) { + { // name + sum.Write([]byte("name:")) + sum.Write([]byte(c.Name)) + sum.Write([]byte{255}) + } + + { // image + sum.Write([]byte("image:")) + sum.Write([]byte(c.Image)) + sum.Write([]byte{255}) + } + + { // command + if len(c.Command) > 0 { + sum.Write([]byte("command:")) + for _, cmd := range c.Command { + sum.Write([]byte(cmd)) + sum.Write([]byte{255}) + } + sum.Write([]byte{255}) + } + } + + { // args + if len(c.Args) > 0 { + sum.Write([]byte("args:")) + for _, arg := range c.Args { + sum.Write([]byte(arg)) + sum.Write([]byte{255}) + } + sum.Write([]byte{255}) + } + } + + { // ports + if len(c.Ports) > 0 { + sum.Write([]byte("ports:")) + for _, p := range c.Ports { + p.hash(sum) + } + sum.Write([]byte{255}) + } + } + + { // resources + if c.Resources != nil { + sum.Write([]byte("resources:")) + c.Resources.hash(sum) + sum.Write([]byte{255}) + } + } + + { // volumeMounts + if len(c.VolumeMounts) > 0 { + sum.Write([]byte("volumeMounts:")) + for _, vm := range c.VolumeMounts { + vm.hash(sum) + } + sum.Write([]byte{255}) + } + } +} + +func (c InjectContainer) Container() (*core_v1.Container, error) { + ports := make([]core_v1.ContainerPort, 0, len(c.Ports)) + for _, p := range c.Ports { + ports = append(ports, core_v1.ContainerPort{ + Name: p.Name, + HostPort: p.HostPort, + ContainerPort: p.ContainerPort, + Protocol: core_v1.Protocol(p.Protocol), + HostIP: p.HostIP, + }) + } + + var resources core_v1.ResourceRequirements + if c.Resources != nil { + _resources, err := c.Resources.ResourceRequirements() + if err != nil { + return nil, err + } + resources = *_resources + } + + volumeMounts := make([]core_v1.VolumeMount, 0, len(c.VolumeMounts)) + for _, vm := range c.VolumeMounts { + volumeMount, err := vm.VolumeMount() + if err != nil { + return nil, err + } + volumeMounts = append(volumeMounts, *volumeMount) + } + + return &core_v1.Container{ + Name: c.Name, + Image: c.Image, + + Command: c.Command, + Args: c.Args, + + Ports: ports, + Resources: resources, + VolumeMounts: volumeMounts, + }, nil +} diff --git a/config/inject_container_port.go b/config/inject_container_port.go new file mode 100644 index 0000000..9e98490 --- /dev/null +++ b/config/inject_container_port.go @@ -0,0 +1,43 @@ +package config + +import ( + "hash" + "unsafe" +) + +type InjectContainerPort struct { + Name string `yaml:"name,omitempty"` + HostPort int32 `yaml:"hostPort,omitempty"` + ContainerPort int32 `yaml:"containerPort,omitempty"` + Protocol string `yaml:"protocol,omitempty"` + HostIP string `yaml:"hostIP,omitempty"` +} + +func (cp InjectContainerPort) hash(sum hash.Hash64) { + { // name + sum.Write([]byte("name:")) + sum.Write([]byte(cp.Name)) + sum.Write([]byte{255}) + } + + { // hostPort + sum.Write([]byte("hostPort:")) + sum.Write(unsafe.Slice( + (*byte)(unsafe.Pointer(&cp.ContainerPort)), + unsafe.Sizeof(cp.ContainerPort), + )) + sum.Write([]byte{255}) + } + + { // protocol + sum.Write([]byte("protocol:")) + sum.Write([]byte(cp.Protocol)) + sum.Write([]byte{255}) + } + + { // hostIP + sum.Write([]byte("hostIP:")) + sum.Write([]byte(cp.HostIP)) + sum.Write([]byte{255}) + } +} diff --git a/config/inject_container_resource_requirements.go b/config/inject_container_resource_requirements.go new file mode 100644 index 0000000..6881187 --- /dev/null +++ b/config/inject_container_resource_requirements.go @@ -0,0 +1,73 @@ +package config + +import ( + "fmt" + "hash" + + core_v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" +) + +type InjectContainerResourceRequirements struct { + Limits map[string]string `yaml:"limits,omitempty"` + Requests map[string]string `yaml:"requests,omitempty"` +} + +func (crr InjectContainerResourceRequirements) hash(sum hash.Hash64) { + { // limits + if len(crr.Limits) > 0 { + sum.Write([]byte("limits:")) + for k, v := range crr.Limits { + sum.Write([]byte("key:")) + sum.Write([]byte(k)) + sum.Write([]byte{255}) + + sum.Write([]byte("value:")) + sum.Write([]byte(v)) + sum.Write([]byte{255}) + } + sum.Write([]byte{255}) + } + } + + { // requests + if len(crr.Requests) > 0 { + sum.Write([]byte("requests:")) + for k, v := range crr.Requests { + sum.Write([]byte("key:")) + sum.Write([]byte(k)) + sum.Write([]byte{255}) + + sum.Write([]byte("value:")) + sum.Write([]byte(v)) + sum.Write([]byte{255}) + } + sum.Write([]byte{255}) + } + } +} + +func (crr InjectContainerResourceRequirements) ResourceRequirements() (*core_v1.ResourceRequirements, error) { + limits := make(map[core_v1.ResourceName]resource.Quantity) + for k, v := range crr.Limits { + q, err := resource.ParseQuantity(v) + if err != nil { + return nil, fmt.Errorf("%w: %s", err, v) + } + limits[core_v1.ResourceName(k)] = q + } + + requests := make(map[core_v1.ResourceName]resource.Quantity) + for k, v := range crr.Requests { + q, err := resource.ParseQuantity(v) + if err != nil { + return nil, fmt.Errorf("%w: %s", err, v) + } + requests[core_v1.ResourceName(k)] = q + } + + return &core_v1.ResourceRequirements{ + Limits: limits, + Requests: requests, + }, nil +} diff --git a/config/inject_label_selector.go b/config/inject_label_selector.go new file mode 100644 index 0000000..ffd26cb --- /dev/null +++ b/config/inject_label_selector.go @@ -0,0 +1,56 @@ +package config + +import ( + "hash" + + meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type InjectLabelSelector struct { + MatchLabels map[string]string `yaml:"matchLabels,omitempty"` + MatchExpressions []InjectMatchExpression `yaml:"matchExpressions,omitempty"` +} + +func (ls InjectLabelSelector) hash(sum hash.Hash64) { + { // matchLabels + if len(ls.MatchLabels) > 0 { + sum.Write([]byte("matchLabels:")) + for k, v := range ls.MatchLabels { + sum.Write([]byte("key:")) + sum.Write([]byte(k)) + sum.Write([]byte{255}) + + sum.Write([]byte("value:")) + sum.Write([]byte(v)) + sum.Write([]byte{255}) + } + sum.Write([]byte{255}) + } + } + + { // matchExpressions + if len(ls.MatchExpressions) > 0 { + sum.Write([]byte("matchExpressions:")) + for _, me := range ls.MatchExpressions { + me.hash(sum) + } + sum.Write([]byte{255}) + } + } +} + +func (ls InjectLabelSelector) LabelSelector() (*meta_v1.LabelSelector, error) { + matchExpressions := make([]meta_v1.LabelSelectorRequirement, 0, len(ls.MatchExpressions)) + for _, me := range ls.MatchExpressions { + matchExpressions = append(matchExpressions, meta_v1.LabelSelectorRequirement{ + Key: me.Key, + Operator: meta_v1.LabelSelectorOperator(me.Operator), + Values: me.Values, + }) + } + + return &meta_v1.LabelSelector{ + MatchLabels: ls.MatchLabels, + MatchExpressions: matchExpressions, + }, nil +} diff --git a/config/inject_match_expression.go b/config/inject_match_expression.go new file mode 100644 index 0000000..5734f24 --- /dev/null +++ b/config/inject_match_expression.go @@ -0,0 +1,34 @@ +package config + +import "hash" + +type InjectMatchExpression struct { + Key string `yaml:"key,omitempty"` + Operator string `yaml:"operator,omitempty"` + Values []string `yaml:"values,omitempty"` +} + +func (me InjectMatchExpression) hash(sum hash.Hash64) { + { // key + sum.Write([]byte("key:")) + sum.Write([]byte(me.Key)) + sum.Write([]byte{255}) + } + + { // operator + sum.Write([]byte("operator:")) + sum.Write([]byte(me.Operator)) + sum.Write([]byte{255}) + } + + { // values + if len(me.Values) > 0 { + sum.Write([]byte("values:")) + for _, v := range me.Values { + sum.Write([]byte(v)) + sum.Write([]byte{255}) + } + sum.Write([]byte{255}) + } + } +} diff --git a/config/inject_volume.go b/config/inject_volume.go new file mode 100644 index 0000000..4f31e41 --- /dev/null +++ b/config/inject_volume.go @@ -0,0 +1,44 @@ +package config + +import ( + "hash" + + core_v1 "k8s.io/api/core/v1" +) + +type InjectVolume struct { + Name string `yaml:"name,omitempty"` + + ConfigMap *InjectVolumeConfigMap `yaml:"configMap,omitempty"` +} + +func (v InjectVolume) hash(sum hash.Hash64) { + sum.Write([]byte("name:")) + sum.Write([]byte(v.Name)) + sum.Write([]byte{255}) + + if v.ConfigMap != nil { + sum.Write([]byte("configMap:")) + v.ConfigMap.hash(sum) + sum.Write([]byte{255}) + } +} + +func (v InjectVolume) Volume() (*core_v1.Volume, error) { + res := &core_v1.Volume{ + Name: v.Name, + VolumeSource: core_v1.VolumeSource{}, + } + + { // configMap + if v.ConfigMap != nil { + configMap, err := v.ConfigMap.ConfigMapVolumeSource() + if err != nil { + return nil, err + } + res.VolumeSource.ConfigMap = configMap + } + } + + return res, nil +} diff --git a/config/inject_volume_config_map.go b/config/inject_volume_config_map.go new file mode 100644 index 0000000..a6a24c7 --- /dev/null +++ b/config/inject_volume_config_map.go @@ -0,0 +1,78 @@ +package config + +import ( + "hash" + "unsafe" + + core_v1 "k8s.io/api/core/v1" +) + +type InjectVolumeConfigMap struct { + Name string `yaml:"name,omitempty"` + + DefaultMode *int32 `yaml:"defaultMode,omitempty"` + Items []InjectVolumeKeyToPath `yaml:"items,omitempty"` + Optional *bool `yaml:"optional,omitempty"` +} + +func (vcm InjectVolumeConfigMap) hash(sum hash.Hash64) { + { // name + sum.Write([]byte("name:")) + sum.Write([]byte(vcm.Name)) + sum.Write([]byte{255}) + } + + { // defaultMode + if vcm.DefaultMode != nil { + sum.Write([]byte("defaultMode:")) + sum.Write(unsafe.Slice( + (*byte)(unsafe.Pointer(vcm.DefaultMode)), + unsafe.Sizeof(*vcm.DefaultMode), + )) + sum.Write([]byte{255}) + } + } + + { // items + if len(vcm.Items) > 0 { + sum.Write([]byte("items:")) + for _, item := range vcm.Items { + item.hash(sum) + } + sum.Write([]byte{255}) + } + } + + { // optional + if vcm.Optional != nil { + sum.Write([]byte("optional:")) + if *vcm.Optional { + sum.Write([]byte{255}) + } else { + sum.Write([]byte{0}) + } + sum.Write([]byte{255}) + } + } +} + +func (vcm InjectVolumeConfigMap) ConfigMapVolumeSource() (*core_v1.ConfigMapVolumeSource, error) { + items := make([]core_v1.KeyToPath, 0, len(vcm.Items)) + for _, item := range vcm.Items { + items = append(items, core_v1.KeyToPath{ + Key: item.Key, + Path: item.Path, + Mode: item.Mode, + }) + } + + return &core_v1.ConfigMapVolumeSource{ + LocalObjectReference: core_v1.LocalObjectReference{ + Name: vcm.Name, + }, + + DefaultMode: vcm.DefaultMode, + Items: items, + Optional: vcm.Optional, + }, nil +} diff --git a/config/inject_volume_key_to_path.go b/config/inject_volume_key_to_path.go new file mode 100644 index 0000000..ccab5b2 --- /dev/null +++ b/config/inject_volume_key_to_path.go @@ -0,0 +1,37 @@ +package config + +import ( + "hash" + "unsafe" +) + +type InjectVolumeKeyToPath struct { + Key string `yaml:"key"` + Path string `yaml:"path"` + Mode *int32 `yaml:"mode"` +} + +func (ktp InjectVolumeKeyToPath) hash(sum hash.Hash64) { + { // key + sum.Write([]byte("key:")) + sum.Write([]byte(ktp.Key)) + sum.Write([]byte{255}) + } + + { // path + sum.Write([]byte("path:")) + sum.Write([]byte(ktp.Path)) + sum.Write([]byte{255}) + } + + { // mode + if ktp.Mode != nil { + sum.Write([]byte("mode:")) + sum.Write(unsafe.Slice( + (*byte)(unsafe.Pointer(ktp.Mode)), + unsafe.Sizeof(*ktp.Mode), + )) + sum.Write([]byte{255}) + } + } +} diff --git a/config/inject_volume_mount.go b/config/inject_volume_mount.go new file mode 100644 index 0000000..60ed27a --- /dev/null +++ b/config/inject_volume_mount.go @@ -0,0 +1,87 @@ +package config + +import ( + "hash" + + core_v1 "k8s.io/api/core/v1" +) + +type InjectVolumeMount struct { + Name string `yaml:"name"` + + MountPath string `yaml:"mountPath"` + SubPath string `yaml:"subPath,omitempty"` + SubPathExpr string `yaml:"subPathExpr,omitempty"` + + ReadOnly bool `yaml:"readOnly,omitempty"` + RecursiveReadOnly *string `yaml:"recursiveReadOnly,omitempty"` + + MountPropagation *string `yaml:"mountPropagation,omitempty"` +} + +func (vm InjectVolumeMount) hash(sum hash.Hash64) { + { // name + sum.Write([]byte("name:")) + sum.Write([]byte(vm.Name)) + sum.Write([]byte{255}) + } + + { // mountPath + sum.Write([]byte("mountPath:")) + sum.Write([]byte(vm.MountPath)) + sum.Write([]byte{255}) + } + + { // subPath + sum.Write([]byte("subPath:")) + sum.Write([]byte(vm.SubPath)) + sum.Write([]byte{255}) + } + + { // subPathExpr + sum.Write([]byte("subPathExpr:")) + sum.Write([]byte(vm.SubPathExpr)) + sum.Write([]byte{255}) + } + + { // readOnly + sum.Write([]byte("readOnly:")) + if vm.ReadOnly { + sum.Write([]byte{255}) + } else { + sum.Write([]byte{0}) + } + sum.Write([]byte{255}) + } + + { // recursiveReadOnly + if vm.RecursiveReadOnly != nil { + sum.Write([]byte("recursiveReadOnly:")) + sum.Write([]byte(*vm.RecursiveReadOnly)) + sum.Write([]byte{255}) + } + } + + { // mountPropagation + if vm.MountPropagation != nil { + sum.Write([]byte("mountPropagation:")) + sum.Write([]byte(*vm.MountPropagation)) + sum.Write([]byte{255}) + } + } +} + +func (vm InjectVolumeMount) VolumeMount() (*core_v1.VolumeMount, error) { + return &core_v1.VolumeMount{ + Name: vm.Name, + + MountPath: vm.MountPath, + SubPath: vm.SubPath, + SubPathExpr: vm.SubPathExpr, + + ReadOnly: vm.ReadOnly, + RecursiveReadOnly: (*core_v1.RecursiveReadOnlyMode)(vm.RecursiveReadOnly), + + MountPropagation: (*core_v1.MountPropagationMode)(vm.MountPropagation), + }, nil +} diff --git a/config/label_selector.go b/config/label_selector.go deleted file mode 100644 index d5251e8..0000000 --- a/config/label_selector.go +++ /dev/null @@ -1,64 +0,0 @@ -package config - -import ( - "hash" - - meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type LabelSelector struct { - MatchLabels map[string]string `yaml:"matchLabels,omitempty"` - MatchExpressions []MatchExpression `yaml:"matchExpressions,omitempty"` -} - -type MatchExpression struct { - Key string `yaml:"key,omitempty"` - Operator string `yaml:"operator,omitempty"` - Values []string `yaml:"values,omitempty"` -} - -func (l LabelSelector) hash(sum hash.Hash64) { - sum.Write([]byte("matchLabels:")) - for k, v := range l.MatchLabels { - sum.Write([]byte("key:")) - sum.Write([]byte(k)) - sum.Write([]byte{255}) - - sum.Write([]byte("value:")) - sum.Write([]byte(v)) - sum.Write([]byte{255}) - } - - sum.Write([]byte("matchExpressions:")) - for _, m := range l.MatchExpressions { - sum.Write([]byte("key:")) - sum.Write([]byte(m.Key)) - sum.Write([]byte{255}) - - sum.Write([]byte("operator:")) - sum.Write([]byte(m.Operator)) - sum.Write([]byte{255}) - - sum.Write([]byte("values:")) - for _, v := range m.Values { - sum.Write([]byte(v)) - sum.Write([]byte{255}) - } - } -} - -func (l LabelSelector) LabelSelector() (*meta_v1.LabelSelector, error) { - matchExpressions := make([]meta_v1.LabelSelectorRequirement, 0, len(l.MatchExpressions)) - for _, m := range l.MatchExpressions { - matchExpressions = append(matchExpressions, meta_v1.LabelSelectorRequirement{ - Key: m.Key, - Operator: meta_v1.LabelSelectorOperator(m.Operator), - Values: m.Values, - }) - } - - return &meta_v1.LabelSelector{ - MatchLabels: l.MatchLabels, - MatchExpressions: matchExpressions, - }, nil -} diff --git a/logutils/setup.go b/logutils/setup.go index f4dc546..01e2409 100644 --- a/logutils/setup.go +++ b/logutils/setup.go @@ -11,9 +11,9 @@ import ( ) var ( - ErrLoggerFailedToBuild = errors.New("failed to build the logger") - ErrLoggerInvalidLevel = errors.New("invalid log-level") - ErrLoggerInvalidMode = errors.New("invalid log-mode") + errLoggerFailedToBuild = errors.New("failed to build the logger") + errLoggerInvalidLevel = errors.New("invalid log-level") + errLoggerInvalidMode = errors.New("invalid log-mode") ) func NewLogger(cfg *config.Log) ( @@ -27,7 +27,7 @@ func NewLogger(cfg *config.Log) ( config = zap.NewProductionConfig() default: return nil, fmt.Errorf("%w: %s", - ErrLoggerInvalidMode, cfg.Mode, + errLoggerInvalidMode, cfg.Mode, ) } config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder @@ -35,7 +35,7 @@ func NewLogger(cfg *config.Log) ( logLevel, err := zap.ParseAtomicLevel(cfg.Level) if err != nil { return nil, fmt.Errorf("%w: %s: %w", - ErrLoggerInvalidLevel, cfg.Level, err, + errLoggerInvalidLevel, cfg.Level, err, ) } config.Level = logLevel @@ -43,7 +43,7 @@ func NewLogger(cfg *config.Log) ( l, err := config.Build() if err != nil { return nil, fmt.Errorf("%w: %w", - ErrLoggerFailedToBuild, err, + errLoggerFailedToBuild, err, ) } diff --git a/patch/add_container_volume_mounts.go b/patch/add_container_volume_mounts.go new file mode 100644 index 0000000..fde85e9 --- /dev/null +++ b/patch/add_container_volume_mounts.go @@ -0,0 +1,43 @@ +package patch + +import ( + "strconv" + + json_patch "github.com/evanphx/json-patch" + "github.com/flashbots/kube-sidecar-injector/operation" + core_v1 "k8s.io/api/core/v1" +) + +func AddContainerVolumeMounts( + idx int, + container *core_v1.Container, + volumeMounts []core_v1.VolumeMount, +) (json_patch.Patch, error) { + if len(volumeMounts) == 0 { + return nil, nil + } + + res := make(json_patch.Patch, 0, len(volumeMounts)) + + notEmpty := len(container.VolumeMounts) > 0 + for _, vm := range volumeMounts { + var ( + op json_patch.Operation + err error + ) + + if notEmpty { + op, err = operation.Add("/spec/containers/"+strconv.Itoa(idx)+"/volumeMounts/-", vm) + } else { + notEmpty = true + op, err = operation.Add("/spec/containers/"+strconv.Itoa(idx)+"/volumeMounts", []core_v1.VolumeMount{vm}) + } + + if err != nil { + return nil, err + } + res = append(res, op) + } + + return res, nil +} diff --git a/patch/add_pod_volumes.go b/patch/add_pod_volumes.go new file mode 100644 index 0000000..34817b3 --- /dev/null +++ b/patch/add_pod_volumes.go @@ -0,0 +1,40 @@ +package patch + +import ( + json_patch "github.com/evanphx/json-patch" + "github.com/flashbots/kube-sidecar-injector/operation" + core_v1 "k8s.io/api/core/v1" +) + +func AddPodVolumes( + pod *core_v1.Pod, + volumes []core_v1.Volume, +) (json_patch.Patch, error) { + if len(volumes) == 0 { + return nil, nil + } + + res := make(json_patch.Patch, 0, len(volumes)) + + notEmpty := len(pod.Spec.Volumes) > 0 + for _, v := range volumes { + var ( + op json_patch.Operation + err error + ) + + if notEmpty { + op, err = operation.Add("/spec/volumes/-", v) + } else { + notEmpty = true + op, err = operation.Add("/spec/volumes", []core_v1.Volume{v}) + } + + if err != nil { + return nil, err + } + res = append(res, op) + } + + return res, nil +} diff --git a/server/k8s.go b/server/k8s.go index f8036d0..d18f95f 100644 --- a/server/k8s.go +++ b/server/k8s.go @@ -20,8 +20,7 @@ import ( ) var ( - ErrFailedToUpsertMutatingWebhookConfiguration = errors.New("failed to upsert mutating webhook configuration") - ErrUnexpectedPreExistingWebhook = errors.New("unexpected pre-existing webhook configuration") + errFailedToUpsertMutatingWebhookConfiguration = errors.New("failed to upsert mutating webhook configuration") ) func (s *Server) upsertMutatingWebhookConfiguration(ctx context.Context) error { @@ -38,7 +37,7 @@ func (s *Server) upsertMutatingWebhookConfiguration(ctx context.Context) error { if k8s_errors.IsNotFound(err) { present = nil } else { - return fmt.Errorf("%w: %s", ErrFailedToUpsertMutatingWebhookConfiguration, err) + return fmt.Errorf("%w: %w", errFailedToUpsertMutatingWebhookConfiguration, err) } } @@ -48,13 +47,21 @@ func (s *Server) upsertMutatingWebhookConfiguration(ctx context.Context) error { webhooks := make([]admission_registration_v1.MutatingWebhook, 0, len(s.cfg.Inject)) for _, i := range s.cfg.Inject { - objectSelector, err := i.LabelSelector.LabelSelector() - if err != nil { - return err + var ( + objectSelector, namespaceSelector *meta_v1.LabelSelector + err error + ) + + if i.LabelSelector != nil { + if objectSelector, err = i.LabelSelector.LabelSelector(); err != nil { + return err + } } - namespaceSelector, err := i.NamespaceSelector.LabelSelector() - if err != nil { - return err + + if i.NamespaceSelector != nil { + if namespaceSelector, err = i.NamespaceSelector.LabelSelector(); err != nil { + return err + } } fingerprint := i.Fingerprint() @@ -110,14 +117,14 @@ func (s *Server) upsertMutatingWebhookConfiguration(ctx context.Context) error { zap.String("mutatingWebhookConfigurationName", s.cfg.K8S.MutatingWebhookConfigurationName), ) if _, err := cli.MutatingWebhookConfigurations().Update(ctx, desired, meta_v1.UpdateOptions{}); err != nil { - return fmt.Errorf("%w: %s", ErrFailedToUpsertMutatingWebhookConfiguration, err) + return fmt.Errorf("%w: %w", errFailedToUpsertMutatingWebhookConfiguration, err) } } else { l.Info("Creating new mutating webhook configuration", zap.String("mutatingWebhookConfigurationName", s.cfg.K8S.MutatingWebhookConfigurationName), ) if _, err := cli.MutatingWebhookConfigurations().Create(ctx, desired, meta_v1.CreateOptions{}); err != nil { - return fmt.Errorf("%w: %s", ErrFailedToUpsertMutatingWebhookConfiguration, err) + return fmt.Errorf("%w: %w", errFailedToUpsertMutatingWebhookConfiguration, err) } } @@ -138,31 +145,45 @@ func (s *Server) mutate( pod := &core_v1.Pod{} if err := json.Unmarshal(req.Object.Raw, pod); err != nil { - l.Error("Failed to decode raw object for pod", zap.Error(err)) + l.Error("Failed to decode raw object for pod", + zap.Error(err), + ) res.Result = &meta_v1.Status{Message: err.Error()} return res } + podName := pod.ObjectMeta.Name + if podName == "" { + podName = pod.ObjectMeta.GenerateName + "?????" + } + l = l.With( + zap.String("namespace", pod.ObjectMeta.Namespace), + zap.String("pod", podName), + zap.String("webhookFingerprint", fingerprint), + ) + ctx = logutils.ContextWithLogger(ctx, l) + l.Info("Handling admission request", - zap.String("fingerprint", fingerprint), zap.String("kind", req.Kind.Kind), - zap.String("name", req.Name), - zap.String("namespace", req.Namespace), zap.String("operation", string(req.Operation)), - zap.String("pod", pod.Name), zap.String("uid", string(req.UID)), zap.String("username", req.UserInfo.Username), ) patches, err := s.mutatePod(ctx, pod, fingerprint) if err != nil { + l.Error("Failed to mutate pod", + zap.Error(err), + ) res.Result = &meta_v1.Status{Message: err.Error()} return res } if len(patches) > 0 { b, err := json.Marshal(patches) if err != nil { - l.Error("Failed to encode pod patches", zap.Error(err)) + l.Error("Failed to encode pod patches", + zap.Error(err), + ) res.Result = &meta_v1.Status{Message: err.Error()} return res } @@ -178,30 +199,101 @@ func (s *Server) mutatePod( ctx context.Context, pod *core_v1.Pod, fingerprint string, -) (json_patch.Patch, error) { +) ( + json_patch.Patch, error, +) { l := logutils.LoggerFromContext(ctx) + inject, exists := s.inject[fingerprint] + if !exists { + l.Warn("Unknown inject-configuration fingerprint => skipping...") + return nil, nil + } + + if inject.Name != "" { + l = l.With( + zap.String("webhookInjectName", inject.Name), + ) + } + annotationProcessed := s.cfg.K8S.ServiceName + "." + global.OrgDomain + "/" + fingerprint if timestamp, alreadyProcessed := pod.Annotations[annotationProcessed]; alreadyProcessed { l.Info("Pod was already processed by inject-configuration with the same fingerprint => skipping...", - zap.String("fingerprint", fingerprint), - zap.String("fingerprintTimestamp", timestamp), - zap.String("namespace", pod.Namespace), - zap.String("pod", pod.Name), + zap.String("webhookFingerprintTimestamp", timestamp), ) return nil, nil } res := make(json_patch.Patch, 0) - inject, exists := s.inject[fingerprint] - if !exists { - l.Warn("Unknown inject-configuration fingerprint => skipping...", - zap.String("fingerprint", fingerprint), - zap.String("namespace", pod.Namespace), - zap.String("pod", pod.Name), - ) - return nil, nil + // inject volumes + if len(inject.Volumes) > 0 { + existing := make(map[string]struct{}, len(pod.Spec.Volumes)) + for _, v := range pod.Spec.Volumes { + existing[v.Name] = struct{}{} + } + + volumes := make([]core_v1.Volume, 0, len(inject.Volumes)) + for _, v := range inject.Volumes { + if _, collision := existing[v.Name]; collision { + l.Warn("Volume with the same name already exists => skipping...", + zap.String("volume", v.Name), + ) + continue + } + + l.Info("Injecting volume", + zap.String("volume", v.Name), + ) + volume, err := v.Volume() + if err != nil { + return nil, err + } + volumes = append(volumes, *volume) + } + + p, err := patch.AddPodVolumes(pod, volumes) + if err != nil { + return nil, err + } + res = append(res, p...) + } + + // inject volume mounts + if len(inject.VolumeMounts) > 0 { + for idx, c := range pod.Spec.Containers { + existing := make(map[string]struct{}, len(c.VolumeMounts)) + for _, vm := range c.VolumeMounts { + existing[vm.Name] = struct{}{} + } + + volumeMounts := make([]core_v1.VolumeMount, 0, len(inject.VolumeMounts)) + for _, vm := range inject.VolumeMounts { + if _, collision := existing[vm.Name]; collision { + l.Warn("Volume mount with the same name already exists => skipping...", + zap.String("container", c.Name), + zap.String("volumeMount", vm.Name), + ) + continue + } + + l.Info("Injecting volume mount", + zap.String("container", c.Name), + zap.String("volumeMount", vm.Name), + ) + volumeMount, err := vm.VolumeMount() + if err != nil { + return nil, err + } + volumeMounts = append(volumeMounts, *volumeMount) + } + + p, err := patch.AddContainerVolumeMounts(idx, &c, volumeMounts) + if err != nil { + return nil, err + } + res = append(res, p...) + } } // inject containers @@ -215,17 +307,13 @@ func (s *Server) mutatePod( for _, c := range inject.Containers { if _, collision := existing[c.Name]; collision { l.Warn("Container with the same name already exists => skipping...", - zap.String("containerName", c.Name), - zap.String("namespace", pod.Namespace), - zap.String("pod", pod.Name), + zap.String("container", c.Name), ) continue } l.Info("Injecting container", - zap.String("containerName", c.Name), - zap.String("namespace", pod.Namespace), - zap.String("pod", pod.Name), + zap.String("container", c.Name), ) container, err := c.Container() if err != nil { @@ -259,13 +347,21 @@ func (s *Server) mutatePod( // mark pod as processed if len(res) > 0 { + l.Debug("Created patch for pod", + zap.Any("pod", pod), + zap.Any("patch", res), + ) + + timestamp := time.Now().Format(time.RFC3339) p, err := patch.UpdatePodAnnotations(pod, map[string]string{ - annotationProcessed: time.Now().Format(time.RFC3339), + annotationProcessed: timestamp, }) if err != nil { return nil, err } res = append(res, p...) + + l.Info("Processed pod") } return res, nil diff --git a/deploy/cluster-role.yaml b/test/cluster-role.yaml similarity index 79% rename from deploy/cluster-role.yaml rename to test/cluster-role.yaml index e3835d1..418f4db 100644 --- a/deploy/cluster-role.yaml +++ b/test/cluster-role.yaml @@ -16,9 +16,9 @@ metadata: labels: app.kubernetes.io/name: kube-sidecar-injector rules: -- apiGroups: ["admissionregistration.k8s.io"] - resources: ["mutatingwebhookconfigurations"] - verbs: ["create", "get", "delete", "list", "patch", "update", "watch"] + - apiGroups: ["admissionregistration.k8s.io"] + resources: ["mutatingwebhookconfigurations"] + verbs: ["create", "get", "delete", "list", "patch", "update", "watch"] --- diff --git a/test/configmap.yaml b/test/configmap.yaml new file mode 100644 index 0000000..3c00692 --- /dev/null +++ b/test/configmap.yaml @@ -0,0 +1,11 @@ +--- + +kind: ConfigMap +apiVersion: v1 +metadata: + name: dummy + labels: + app.kubernetes.io/name: dummy +data: + dummy.txt: |- + dummy diff --git a/deploy/deployment-fargate.yaml b/test/deployment-fargate.yaml similarity index 91% rename from deploy/deployment-fargate.yaml rename to test/deployment-fargate.yaml index a4bfc7a..45b59d7 100644 --- a/deploy/deployment-fargate.yaml +++ b/test/deployment-fargate.yaml @@ -35,8 +35,10 @@ spec: serviceAccountName: kube-sidecar-injector containers: - name: kube-sidecar-injector-fargate - image: kube-sidecar-injector:0.0.4-dev + image: kube-sidecar-injector:0.0.8-dev args: [ + "--log-level", "info", + "--log-mode", "dev", "serve", "--mutating-webhook-configuration-name", "kube-sidecar-injector-fargate", "--service-name", "kube-sidecar-injector-fargate", @@ -67,7 +69,9 @@ metadata: data: config.yaml: |- inject: - - labelSelector: + - name: inject-fargate-label + + labelSelector: matchLabels: app.kubernetes.io/name: dummy-injected-via-deployment diff --git a/deploy/deployment-node-exporter.yaml b/test/deployment-node-exporter.yaml similarity index 85% rename from deploy/deployment-node-exporter.yaml rename to test/deployment-node-exporter.yaml index f916154..b4577a2 100644 --- a/deploy/deployment-node-exporter.yaml +++ b/test/deployment-node-exporter.yaml @@ -35,8 +35,10 @@ spec: serviceAccountName: kube-sidecar-injector containers: - name: kube-sidecar-injector-node-exporter - image: kube-sidecar-injector:0.0.4-dev + image: kube-sidecar-injector:0.0.8-dev args: [ + "--log-level", "info", + "--log-mode", "dev", "serve", "--mutating-webhook-configuration-name", "kube-sidecar-injector-node-exporter", "--service-name", "kube-sidecar-injector-node-exporter", @@ -67,7 +69,9 @@ metadata: data: config.yaml: |- inject: - - labelSelector: + - name: inject-node-exporter + + labelSelector: matchExpressions: - key: eks.amazonaws.com/fargate-profile operator: Exists @@ -89,9 +93,20 @@ data: "--web.listen-address", ":9100", ] ports: - - name: http-metrics + - name: node-exporter containerPort: 9100 resources: requests: cpu: 10m memory: 64Mi + + - name: inject-dummy-volume + + volumes: + - name: dummy + configMap: + name: dummy + + volumeMounts: + - name: dummy + mountPath: /var/dummy diff --git a/deploy/dummy.yaml b/test/dummy.yaml similarity index 100% rename from deploy/dummy.yaml rename to test/dummy.yaml