mirror of https://github.com/linkerd/linkerd2.git
Have the Webhook react to pod creation/update only (#2472)
Have the Webhook react to pod creation/update only This was already working almost out-of-the-box, just had to: - Change the webhook config so it watches pods instead of deployments - Grant some extra ClusterRole permissions - Add the piece that figures what's the OwnerReference and add the label for it - Manually inject service account mount paths - Readd volumes tests Fixes #2342 and #1751 Signed-off-by: Alejandro Pedraza <alejandro@buoyant.io>
This commit is contained in:
parent
9c442f6885
commit
7efe385feb
|
@ -80,7 +80,13 @@ rules:
|
|||
verbs: ["create", "get", "delete"]
|
||||
- apiGroups: [""]
|
||||
resources: ["namespaces"]
|
||||
verbs: ["get"]
|
||||
verbs: ["list", "get", "watch"]
|
||||
- apiGroups: [""]
|
||||
resources: ["pods"]
|
||||
verbs: ["list"]
|
||||
- apiGroups: ["apps"]
|
||||
resources: ["replicasets"]
|
||||
verbs: ["list", "get", "watch"]
|
||||
---
|
||||
kind: ClusterRoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
|
|
|
@ -142,7 +142,11 @@ func (rt resourceTransformerInject) transform(bytes []byte) ([]byte, []inject.Re
|
|||
if patchJSON == nil {
|
||||
return bytes, reports, nil
|
||||
}
|
||||
log.Infof("patch generated for: %s", conf)
|
||||
// TODO: refactor GetPatch() for it to return just one report item
|
||||
if len(reports) > 0 {
|
||||
r := reports[0]
|
||||
log.Infof("patch generated for: %s", r.ResName())
|
||||
}
|
||||
log.Debugf("patch: %s", patchJSON)
|
||||
patch, err := jsonpatch.DecodePatch(patchJSON)
|
||||
if err != nil {
|
||||
|
|
|
@ -1491,7 +1491,13 @@ rules:
|
|||
verbs: ["create", "get", "delete"]
|
||||
- apiGroups: [""]
|
||||
resources: ["namespaces"]
|
||||
verbs: ["get"]
|
||||
verbs: ["list", "get", "watch"]
|
||||
- apiGroups: [""]
|
||||
resources: ["pods"]
|
||||
verbs: ["list"]
|
||||
- apiGroups: ["apps"]
|
||||
resources: ["replicasets"]
|
||||
verbs: ["list", "get", "watch"]
|
||||
---
|
||||
kind: ClusterRoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
|
|
|
@ -1424,7 +1424,13 @@ rules:
|
|||
verbs: ["create", "get", "delete"]
|
||||
- apiGroups: [""]
|
||||
resources: ["namespaces"]
|
||||
verbs: ["get"]
|
||||
verbs: ["list", "get", "watch"]
|
||||
- apiGroups: [""]
|
||||
resources: ["pods"]
|
||||
verbs: ["list"]
|
||||
- apiGroups: ["apps"]
|
||||
resources: ["replicasets"]
|
||||
verbs: ["list", "get", "watch"]
|
||||
---
|
||||
kind: ClusterRoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
|
|
|
@ -27,9 +27,9 @@ func main() {
|
|||
defer close(stop)
|
||||
signal.Notify(stop, os.Interrupt, os.Kill)
|
||||
|
||||
k8sClient, err := k8s.NewClientSet(*kubeconfig)
|
||||
k8sAPI, err := k8s.InitializeAPI(*kubeconfig, k8s.NS, k8s.RS)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to initialize Kubernetes client: %s", err)
|
||||
log.Fatalf("failed to initialize Kubernetes API: %s", err)
|
||||
}
|
||||
|
||||
rootCA, err := tls.GenerateRootCAWithDefaults("Proxy Injector Mutating Webhook Admission Controller CA")
|
||||
|
@ -37,7 +37,7 @@ func main() {
|
|||
log.Fatalf("failed to create root CA: %s", err)
|
||||
}
|
||||
|
||||
webhookConfig, err := injector.NewWebhookConfig(k8sClient, *controllerNamespace, *webhookServiceName, rootCA)
|
||||
webhookConfig, err := injector.NewWebhookConfig(k8sAPI, *controllerNamespace, *webhookServiceName, rootCA)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to read the trust anchor file: %s", err)
|
||||
}
|
||||
|
@ -48,11 +48,13 @@ func main() {
|
|||
}
|
||||
log.Infof("created mutating webhook configuration: %s", mwc.ObjectMeta.SelfLink)
|
||||
|
||||
s, err := injector.NewWebhookServer(k8sClient, *addr, *controllerNamespace, *noInitContainer, rootCA)
|
||||
s, err := injector.NewWebhookServer(k8sAPI, *addr, *controllerNamespace, *noInitContainer, rootCA)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to initialize the webhook server: %s", err)
|
||||
}
|
||||
|
||||
k8sAPI.Sync()
|
||||
|
||||
go func() {
|
||||
log.Infof("listening at %s", *addr)
|
||||
if err := s.ListenAndServeTLS("", ""); err != nil {
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
name: linkerd-secrets
|
||||
secret:
|
||||
secretName: nginx-deployment-tls-linkerd-io
|
||||
optional: true
|
|
@ -8,9 +8,9 @@ import (
|
|||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/linkerd/linkerd2/controller/k8s"
|
||||
pkgTls "github.com/linkerd/linkerd2/pkg/tls"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
// WebhookServer is the webhook's HTTP server. It has an embedded webhook which
|
||||
|
@ -21,7 +21,7 @@ type WebhookServer struct {
|
|||
}
|
||||
|
||||
// NewWebhookServer returns a new instance of the WebhookServer.
|
||||
func NewWebhookServer(client kubernetes.Interface, addr, controllerNamespace string, noInitContainer bool, rootCA *pkgTls.CA) (*WebhookServer, error) {
|
||||
func NewWebhookServer(api *k8s.API, addr, controllerNamespace string, noInitContainer bool, rootCA *pkgTls.CA) (*WebhookServer, error) {
|
||||
c, err := tlsConfig(rootCA, controllerNamespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -32,7 +32,7 @@ func NewWebhookServer(client kubernetes.Interface, addr, controllerNamespace str
|
|||
TLSConfig: c,
|
||||
}
|
||||
|
||||
webhook, err := NewWebhook(client, controllerNamespace, noInitContainer)
|
||||
webhook, err := NewWebhook(api, controllerNamespace, noInitContainer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/linkerd/linkerd2/controller/k8s"
|
||||
"github.com/linkerd/linkerd2/controller/proxy-injector/fake"
|
||||
"github.com/linkerd/linkerd2/pkg/tls"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
@ -20,9 +21,12 @@ var (
|
|||
|
||||
func init() {
|
||||
// create a webhook which uses its fake client to seed the sidecar configmap
|
||||
fakeClient := fake.NewClient("")
|
||||
k8sAPI, err := k8s.NewFakeAPI()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
webhook, err := NewWebhook(fakeClient, fake.DefaultControllerNamespace, false)
|
||||
webhook, err := NewWebhook(k8sAPI, fake.DefaultControllerNamespace, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -73,13 +77,12 @@ func TestNewWebhookServer(t *testing.T) {
|
|||
log.Fatalf("failed to create root CA: %s", err)
|
||||
}
|
||||
|
||||
var (
|
||||
addr = ":7070"
|
||||
kubeconfig = ""
|
||||
)
|
||||
fakeClient := fake.NewClient(kubeconfig)
|
||||
|
||||
server, err := NewWebhookServer(fakeClient, addr, fake.DefaultControllerNamespace, false, rootCA)
|
||||
addr := ":7070"
|
||||
k8sAPI, err := k8s.NewFakeAPI()
|
||||
if err != nil {
|
||||
t.Fatalf("NewFakeAPI returned an error: %s", err)
|
||||
}
|
||||
server, err := NewWebhookServer(k8sAPI, addr, fake.DefaultControllerNamespace, false, rootCA)
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected error: ", err)
|
||||
}
|
||||
|
|
|
@ -17,6 +17,6 @@ webhooks:
|
|||
caBundle: {{ .CABundle }}
|
||||
rules:
|
||||
- operations: [ "CREATE" , "UPDATE" ]
|
||||
apiGroups: ["apps", "extensions"]
|
||||
apiVersions: ["v1", "v1beta1", "v1beta2"]
|
||||
resources: ["deployments"]`
|
||||
apiGroups: [""]
|
||||
apiVersions: ["v1"]
|
||||
resources: ["pods"]`
|
||||
|
|
|
@ -4,16 +4,17 @@ import (
|
|||
"fmt"
|
||||
|
||||
pb "github.com/linkerd/linkerd2/controller/gen/config"
|
||||
"github.com/linkerd/linkerd2/controller/k8s"
|
||||
"github.com/linkerd/linkerd2/pkg/config"
|
||||
"github.com/linkerd/linkerd2/pkg/inject"
|
||||
"github.com/linkerd/linkerd2/pkg/k8s"
|
||||
pkgK8s "github.com/linkerd/linkerd2/pkg/k8s"
|
||||
"github.com/linkerd/linkerd2/pkg/version"
|
||||
log "github.com/sirupsen/logrus"
|
||||
admissionv1beta1 "k8s.io/api/admission/v1beta1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
|
@ -21,21 +22,21 @@ import (
|
|||
// requests by injecting sidecar container spec into the pod spec during pod
|
||||
// creation.
|
||||
type Webhook struct {
|
||||
client kubernetes.Interface
|
||||
k8sAPI *k8s.API
|
||||
deserializer runtime.Decoder
|
||||
controllerNamespace string
|
||||
noInitContainer bool
|
||||
}
|
||||
|
||||
// NewWebhook returns a new instance of Webhook.
|
||||
func NewWebhook(client kubernetes.Interface, controllerNamespace string, noInitContainer bool) (*Webhook, error) {
|
||||
func NewWebhook(api *k8s.API, controllerNamespace string, noInitContainer bool) (*Webhook, error) {
|
||||
var (
|
||||
scheme = runtime.NewScheme()
|
||||
codecs = serializer.NewCodecFactory(scheme)
|
||||
)
|
||||
|
||||
return &Webhook{
|
||||
client: client,
|
||||
k8sAPI: api,
|
||||
deserializer: codecs.UniversalDeserializer(),
|
||||
controllerNamespace: controllerNamespace,
|
||||
noInitContainer: noInitContainer,
|
||||
|
@ -87,17 +88,17 @@ func (w *Webhook) decode(data []byte) (*admissionv1beta1.AdmissionReview, error)
|
|||
func (w *Webhook) inject(request *admissionv1beta1.AdmissionRequest) (*admissionv1beta1.AdmissionResponse, error) {
|
||||
log.Debugf("request object bytes: %s", request.Object.Raw)
|
||||
|
||||
globalConfig, err := config.Global(k8s.MountPathGlobalConfig)
|
||||
globalConfig, err := config.Global(pkgK8s.MountPathGlobalConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
proxyConfig, err := config.Proxy(k8s.MountPathProxyConfig)
|
||||
proxyConfig, err := config.Proxy(pkgK8s.MountPathProxyConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
namespace, err := w.client.CoreV1().Namespaces().Get(request.Namespace, metav1.GetOptions{})
|
||||
namespace, err := w.k8sAPI.NS().Lister().Get(request.Namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -105,6 +106,7 @@ func (w *Webhook) inject(request *admissionv1beta1.AdmissionRequest) (*admission
|
|||
|
||||
configs := &pb.All{Global: globalConfig, Proxy: proxyConfig}
|
||||
conf := inject.NewResourceConfig(configs).
|
||||
WithOwnerRetriever(w.ownerRetriever(request.Namespace)).
|
||||
WithNsAnnotations(nsAnnotations).
|
||||
WithKind(request.Kind.Kind)
|
||||
nonEmpty, err := conf.ParseMeta(request.Object.Raw)
|
||||
|
@ -120,7 +122,7 @@ func (w *Webhook) inject(request *admissionv1beta1.AdmissionRequest) (*admission
|
|||
return admissionResponse, nil
|
||||
}
|
||||
|
||||
p, _, err := conf.GetPatch(request.Object.Raw, inject.ShouldInjectWebhook)
|
||||
p, reports, err := conf.GetPatch(request.Object.Raw, inject.ShouldInjectWebhook)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -131,17 +133,15 @@ func (w *Webhook) inject(request *admissionv1beta1.AdmissionRequest) (*admission
|
|||
|
||||
p.AddCreatedByPodAnnotation(fmt.Sprintf("linkerd/proxy-injector %s", version.Version))
|
||||
|
||||
// When adding workloads through `kubectl apply` the spec template labels are
|
||||
// automatically copied to the workload's main metadata section.
|
||||
// This doesn't happen when adding labels through the webhook. So we manually
|
||||
// add them to remain consistent.
|
||||
conf.AddRootLabels(p)
|
||||
|
||||
patchJSON, err := p.Marshal()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Infof("patch generated for: %s", conf)
|
||||
// TODO: refactor GetPatch() so it only returns one report item
|
||||
if len(reports) > 0 {
|
||||
r := reports[0]
|
||||
log.Infof("patch generated for: %s", r.ResName())
|
||||
}
|
||||
log.Debugf("patch: %s", patchJSON)
|
||||
|
||||
patchType := admissionv1beta1.PatchTypeJSONPatch
|
||||
|
@ -150,3 +150,10 @@ func (w *Webhook) inject(request *admissionv1beta1.AdmissionRequest) (*admission
|
|||
|
||||
return admissionResponse, nil
|
||||
}
|
||||
|
||||
func (w *Webhook) ownerRetriever(ns string) inject.OwnerRetrieverFunc {
|
||||
return func(p *v1.Pod) (string, string) {
|
||||
p.SetNamespace(ns)
|
||||
return w.k8sAPI.GetOwnerKindAndName(p)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"encoding/base64"
|
||||
"text/template"
|
||||
|
||||
"github.com/linkerd/linkerd2/controller/k8s"
|
||||
"github.com/linkerd/linkerd2/controller/proxy-injector/tmpl"
|
||||
k8sPkg "github.com/linkerd/linkerd2/pkg/k8s"
|
||||
"github.com/linkerd/linkerd2/pkg/tls"
|
||||
|
@ -12,7 +13,6 @@ import (
|
|||
arv1beta1 "k8s.io/api/admissionregistration/v1beta1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
|
@ -22,11 +22,11 @@ type WebhookConfig struct {
|
|||
webhookServiceName string
|
||||
trustAnchor []byte
|
||||
configTemplate *template.Template
|
||||
k8sAPI kubernetes.Interface
|
||||
k8sAPI *k8s.API
|
||||
}
|
||||
|
||||
// NewWebhookConfig returns a new instance of initiator.
|
||||
func NewWebhookConfig(client kubernetes.Interface, controllerNamespace, webhookServiceName string, rootCA *tls.CA) (*WebhookConfig, error) {
|
||||
func NewWebhookConfig(api *k8s.API, controllerNamespace, webhookServiceName string, rootCA *tls.CA) (*WebhookConfig, error) {
|
||||
trustAnchor := rootCA.Cred.EncodeCertificatePEM()
|
||||
|
||||
t := template.New(k8sPkg.ProxyInjectorWebhookConfig)
|
||||
|
@ -36,7 +36,7 @@ func NewWebhookConfig(client kubernetes.Interface, controllerNamespace, webhookS
|
|||
webhookServiceName: webhookServiceName,
|
||||
trustAnchor: []byte(trustAnchor),
|
||||
configTemplate: template.Must(t.Parse(tmpl.MutatingWebhookConfigurationSpec)),
|
||||
k8sAPI: client,
|
||||
k8sAPI: api,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -97,15 +97,18 @@ func (w *WebhookConfig) create() (*arv1beta1.MutatingWebhookConfiguration, error
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return w.k8sAPI.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().Create(&config)
|
||||
return w.k8sAPI.Client.
|
||||
AdmissionregistrationV1beta1().MutatingWebhookConfigurations().Create(&config)
|
||||
}
|
||||
|
||||
func (w *WebhookConfig) get() (*arv1beta1.MutatingWebhookConfiguration, error) {
|
||||
return w.k8sAPI.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().
|
||||
return w.k8sAPI.Client.
|
||||
AdmissionregistrationV1beta1().MutatingWebhookConfigurations().
|
||||
Get(k8sPkg.ProxyInjectorWebhookConfig, metav1.GetOptions{})
|
||||
}
|
||||
|
||||
func (w *WebhookConfig) delete() error {
|
||||
return w.k8sAPI.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().Delete(
|
||||
k8sPkg.ProxyInjectorWebhookConfig, &metav1.DeleteOptions{})
|
||||
return w.k8sAPI.Client.
|
||||
AdmissionregistrationV1beta1().MutatingWebhookConfigurations().
|
||||
Delete(k8sPkg.ProxyInjectorWebhookConfig, &metav1.DeleteOptions{})
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"log"
|
||||
"testing"
|
||||
|
||||
"github.com/linkerd/linkerd2/controller/k8s"
|
||||
"github.com/linkerd/linkerd2/controller/proxy-injector/fake"
|
||||
"github.com/linkerd/linkerd2/pkg/tls"
|
||||
)
|
||||
|
@ -16,14 +17,17 @@ func TestCreate(t *testing.T) {
|
|||
)
|
||||
log.SetOutput(ioutil.Discard)
|
||||
|
||||
client := fake.NewClient("")
|
||||
k8sAPI, err := k8s.NewFakeAPI()
|
||||
if err != nil {
|
||||
t.Fatalf("NewFakeAPI returned an error: %s", err)
|
||||
}
|
||||
|
||||
rootCA, err := tls.GenerateRootCAWithDefaults("Test CA")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create root CA: %s", err)
|
||||
}
|
||||
|
||||
webhookConfig, err := NewWebhookConfig(client, namespace, webhookServiceName, rootCA)
|
||||
webhookConfig, err := NewWebhookConfig(k8sAPI, namespace, webhookServiceName, rootCA)
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected error: ", err)
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
"github.com/linkerd/linkerd2/controller/gen/config"
|
||||
"github.com/linkerd/linkerd2/controller/proxy-injector/fake"
|
||||
"github.com/linkerd/linkerd2/pkg/inject"
|
||||
"github.com/linkerd/linkerd2/pkg/k8s"
|
||||
pkgK8s "github.com/linkerd/linkerd2/pkg/k8s"
|
||||
admissionv1beta1 "k8s.io/api/admission/v1beta1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
@ -42,7 +42,7 @@ var (
|
|||
func confNsEnabled() *inject.ResourceConfig {
|
||||
return inject.
|
||||
NewResourceConfig(configs).
|
||||
WithNsAnnotations(map[string]string{k8s.ProxyInjectAnnotation: k8s.ProxyInjectEnabled})
|
||||
WithNsAnnotations(map[string]string{pkgK8s.ProxyInjectAnnotation: pkgK8s.ProxyInjectEnabled})
|
||||
}
|
||||
|
||||
func confNsDisabled() *inject.ResourceConfig {
|
||||
|
|
|
@ -73,6 +73,10 @@ var injectableKinds = []string{
|
|||
k8s.StatefulSet,
|
||||
}
|
||||
|
||||
// OwnerRetrieverFunc is a function that returns a pod's owner reference
|
||||
// kind and name
|
||||
type OwnerRetrieverFunc func(*v1.Pod) (string, string)
|
||||
|
||||
// ResourceConfig contains the parsed information for a given workload
|
||||
type ResourceConfig struct {
|
||||
configs *config.All
|
||||
|
@ -80,6 +84,7 @@ type ResourceConfig struct {
|
|||
destinationDNSOverride string
|
||||
identityDNSOverride string
|
||||
proxyOutboundCapacity map[string]uint
|
||||
ownerRetriever OwnerRetrieverFunc
|
||||
|
||||
workload struct {
|
||||
obj runtime.Object
|
||||
|
@ -108,20 +113,6 @@ func NewResourceConfig(configs *config.All) *ResourceConfig {
|
|||
return config
|
||||
}
|
||||
|
||||
// String satisfies the Stringer interface
|
||||
func (conf *ResourceConfig) String() string {
|
||||
l := []string{}
|
||||
|
||||
if conf.workload.metaType.Kind != "" {
|
||||
l = append(l, conf.workload.metaType.Kind)
|
||||
}
|
||||
if conf.workload.meta != nil {
|
||||
l = append(l, fmt.Sprintf("%s.%s", conf.workload.meta.GetName(), conf.workload.meta.GetNamespace()))
|
||||
}
|
||||
|
||||
return strings.Join(l, "/")
|
||||
}
|
||||
|
||||
// WithKind enriches ResourceConfig with the workload kind
|
||||
func (conf *ResourceConfig) WithKind(kind string) *ResourceConfig {
|
||||
conf.workload.metaType = metav1.TypeMeta{Kind: kind}
|
||||
|
@ -143,6 +134,13 @@ func (conf *ResourceConfig) WithProxyOutboundCapacity(m map[string]uint) *Resour
|
|||
return conf
|
||||
}
|
||||
|
||||
// WithOwnerRetriever enriches ResourceConfig with a function that allows to retrieve
|
||||
// the kind and name of the workload's owner reference
|
||||
func (conf *ResourceConfig) WithOwnerRetriever(f OwnerRetrieverFunc) *ResourceConfig {
|
||||
conf.ownerRetriever = f
|
||||
return conf
|
||||
}
|
||||
|
||||
// YamlMarshalObj returns the yaml for the workload in conf
|
||||
func (conf *ResourceConfig) YamlMarshalObj() ([]byte, error) {
|
||||
return yaml.Marshal(conf.workload.obj)
|
||||
|
@ -158,7 +156,7 @@ func (conf *ResourceConfig) ParseMetaAndYaml(bytes []byte) (*Report, error) {
|
|||
}
|
||||
|
||||
// ParseMeta extracts metadata from bytes.
|
||||
// It returns false if the workload's payload is empty
|
||||
// It returns false if the workload's payload is empty.
|
||||
func (conf *ResourceConfig) ParseMeta(bytes []byte) (bool, error) {
|
||||
if err := yaml.Unmarshal(bytes, &conf.workload.metaType); err != nil {
|
||||
return false, err
|
||||
|
@ -181,12 +179,7 @@ func (conf *ResourceConfig) GetPatch(
|
|||
return nil, nil, err
|
||||
}
|
||||
|
||||
var patch *Patch
|
||||
if strings.ToLower(conf.workload.metaType.Kind) == k8s.Pod {
|
||||
patch = NewPatchPod()
|
||||
} else {
|
||||
patch = NewPatchDeployment()
|
||||
}
|
||||
patch := NewPatch(conf.workload.metaType.Kind)
|
||||
|
||||
// If we don't inject anything into the pod template then output the
|
||||
// original serialization of the original object. Otherwise, output the
|
||||
|
@ -246,6 +239,8 @@ func (conf *ResourceConfig) JSONToYAML(bytes []byte) ([]byte, error) {
|
|||
return yaml.Marshal(obj)
|
||||
}
|
||||
|
||||
// parse parses the bytes payload, filling the gaps in ResourceConfig
|
||||
// depending on the workload kind
|
||||
func (conf *ResourceConfig) parse(bytes []byte) error {
|
||||
// The Kubernetes API is versioned and each version has an API modeled
|
||||
// with its own distinct Go types. If we tell `yaml.Unmarshal()` which
|
||||
|
@ -352,6 +347,24 @@ func (conf *ResourceConfig) parse(bytes []byte) error {
|
|||
conf.workload.obj = v
|
||||
conf.pod.spec = &v.Spec
|
||||
conf.pod.Meta = &v.ObjectMeta
|
||||
|
||||
if conf.ownerRetriever != nil {
|
||||
kind, name := conf.ownerRetriever(v)
|
||||
switch kind {
|
||||
case k8s.Deployment:
|
||||
conf.pod.labels[k8s.ProxyDeploymentLabel] = name
|
||||
case k8s.ReplicationController:
|
||||
conf.pod.labels[k8s.ProxyReplicationControllerLabel] = name
|
||||
case k8s.ReplicaSet:
|
||||
conf.pod.labels[k8s.ProxyReplicaSetLabel] = name
|
||||
case k8s.Job:
|
||||
conf.pod.labels[k8s.ProxyJobLabel] = name
|
||||
case k8s.DaemonSet:
|
||||
conf.pod.labels[k8s.ProxyDaemonSetLabel] = name
|
||||
case k8s.StatefulSet:
|
||||
conf.pod.labels[k8s.ProxyStatefulSetLabel] = name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -364,8 +377,9 @@ func (conf *ResourceConfig) complete(template *v1.PodTemplateSpec) {
|
|||
|
||||
// injectPodSpec adds linkerd sidecars to the provided PodSpec.
|
||||
func (conf *ResourceConfig) injectPodSpec(patch *Patch) {
|
||||
saVolumeMount := conf.serviceAccountVolumeMount()
|
||||
if !conf.configs.GetGlobal().GetCniEnabled() {
|
||||
conf.injectProxyInit(patch)
|
||||
conf.injectProxyInit(patch, saVolumeMount)
|
||||
}
|
||||
|
||||
proxyUID := conf.proxyUID()
|
||||
|
@ -455,6 +469,10 @@ func (conf *ResourceConfig) injectPodSpec(patch *Patch) {
|
|||
}
|
||||
}
|
||||
|
||||
if saVolumeMount != nil {
|
||||
sidecar.VolumeMounts = []v1.VolumeMount{*saVolumeMount}
|
||||
}
|
||||
|
||||
idctx := conf.configs.GetGlobal().GetIdentityContext()
|
||||
if idctx == nil {
|
||||
sidecar.Env = append(sidecar.Env, v1.EnvVar{
|
||||
|
@ -527,7 +545,7 @@ func (conf *ResourceConfig) injectPodSpec(patch *Patch) {
|
|||
patch.addContainer(&sidecar)
|
||||
}
|
||||
|
||||
func (conf *ResourceConfig) injectProxyInit(patch *Patch) {
|
||||
func (conf *ResourceConfig) injectProxyInit(patch *Patch, saVolumeMount *v1.VolumeMount) {
|
||||
nonRoot := false
|
||||
runAsUser := int64(0)
|
||||
initContainer := &v1.Container{
|
||||
|
@ -545,12 +563,28 @@ func (conf *ResourceConfig) injectProxyInit(patch *Patch) {
|
|||
RunAsUser: &runAsUser,
|
||||
},
|
||||
}
|
||||
if saVolumeMount != nil {
|
||||
initContainer.VolumeMounts = []v1.VolumeMount{*saVolumeMount}
|
||||
}
|
||||
if len(conf.pod.spec.InitContainers) == 0 {
|
||||
patch.addInitContainerRoot()
|
||||
}
|
||||
patch.addInitContainer(initContainer)
|
||||
}
|
||||
|
||||
func (conf *ResourceConfig) serviceAccountVolumeMount() *v1.VolumeMount {
|
||||
// Probably always true, but wanna be super-safe
|
||||
if containers := conf.pod.spec.Containers; len(containers) > 0 {
|
||||
for _, vm := range containers[0].VolumeMounts {
|
||||
if vm.MountPath == k8s.MountPathServiceAccount {
|
||||
vm := vm // pin
|
||||
return &vm
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Given a ObjectMeta, update ObjectMeta in place with the new labels and
|
||||
// annotations.
|
||||
func (conf *ResourceConfig) injectObjectMeta(patch *Patch) {
|
||||
|
@ -565,15 +599,13 @@ func (conf *ResourceConfig) injectObjectMeta(patch *Patch) {
|
|||
patch.addPodAnnotation(k8s.IdentityModeAnnotation, k8s.IdentityModeDisabled)
|
||||
}
|
||||
|
||||
for k, v := range conf.pod.labels {
|
||||
patch.addPodLabel(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// AddRootLabels adds all the pod labels into the root workload (e.g. Deployment)
|
||||
func (conf *ResourceConfig) AddRootLabels(patch *Patch) {
|
||||
for k, v := range conf.pod.labels {
|
||||
patch.addRootLabel(k, v)
|
||||
if len(conf.pod.labels) > 0 {
|
||||
if len(conf.pod.Meta.Labels) == 0 {
|
||||
patch.addPodLabelsRoot()
|
||||
}
|
||||
for k, v := range conf.pod.labels {
|
||||
patch.addPodLabel(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -227,7 +227,7 @@ func TestConfigAccessors(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := resourceConfig.parse(data); err != nil {
|
||||
if _, err := resourceConfig.ParseMetaAndYaml(data); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
|
|
@ -24,8 +24,21 @@ type Patch struct {
|
|||
patchPathPodAnnotations string
|
||||
}
|
||||
|
||||
// NewPatchDeployment returns a new instance of Patch for Deployment-like workloads
|
||||
func NewPatchDeployment() *Patch {
|
||||
// NewPatch returns a new instance of Patch for any kind of workload kind
|
||||
func NewPatch(kind string) *Patch {
|
||||
if strings.ToLower(kind) == k8s.Pod {
|
||||
return &Patch{
|
||||
patchOps: []*patchOp{},
|
||||
patchPathContainer: "/spec/containers/-",
|
||||
patchPathInitContainerRoot: "/spec/initContainers",
|
||||
patchPathInitContainer: "/spec/initContainers/-",
|
||||
patchPathVolumeRoot: "/spec/volumes",
|
||||
patchPathVolume: "/spec/volumes/-",
|
||||
patchPathPodLabels: patchPathRootLabels,
|
||||
patchPathPodAnnotations: "/metadata/annotations",
|
||||
}
|
||||
}
|
||||
|
||||
return &Patch{
|
||||
patchOps: []*patchOp{},
|
||||
patchPathContainer: "/spec/template/spec/containers/-",
|
||||
|
@ -38,20 +51,6 @@ func NewPatchDeployment() *Patch {
|
|||
}
|
||||
}
|
||||
|
||||
// NewPatchPod returns a new instance of Patch for Pod workloads
|
||||
func NewPatchPod() *Patch {
|
||||
return &Patch{
|
||||
patchOps: []*patchOp{},
|
||||
patchPathContainer: "/spec/containers/-",
|
||||
patchPathInitContainerRoot: "/spec/initContainers",
|
||||
patchPathInitContainer: "/spec/initContainers/-",
|
||||
patchPathVolumeRoot: "/spec/volumes",
|
||||
patchPathVolume: "/spec/volumes/-",
|
||||
patchPathPodLabels: patchPathRootLabels,
|
||||
patchPathPodAnnotations: "/metadata/annotations",
|
||||
}
|
||||
}
|
||||
|
||||
// Marshal returns the patch as JSON
|
||||
func (p *Patch) Marshal() ([]byte, error) {
|
||||
return json.Marshal(p.patchOps)
|
||||
|
@ -97,6 +96,15 @@ func (p *Patch) addVolume(volume *corev1.Volume) {
|
|||
})
|
||||
}
|
||||
|
||||
func (p *Patch) addPodLabelsRoot() {
|
||||
p.patchOps = append(p.patchOps, &patchOp{
|
||||
Op: "add",
|
||||
Path: p.patchPathPodLabels,
|
||||
Value: map[string]string{},
|
||||
})
|
||||
}
|
||||
|
||||
// AddPodLabel appends the label with the provided key and value
|
||||
func (p *Patch) addPodLabel(key, value string) {
|
||||
p.patchOps = append(p.patchOps, &patchOp{
|
||||
Op: "add",
|
||||
|
@ -105,14 +113,6 @@ func (p *Patch) addPodLabel(key, value string) {
|
|||
})
|
||||
}
|
||||
|
||||
func (p *Patch) addRootLabel(key, value string) {
|
||||
p.patchOps = append(p.patchOps, &patchOp{
|
||||
Op: "add",
|
||||
Path: patchPathRootLabels + "/" + escapeKey(key),
|
||||
Value: value,
|
||||
})
|
||||
}
|
||||
|
||||
func (p *Patch) addPodAnnotationsRoot() {
|
||||
p.patchOps = append(p.patchOps, &patchOp{
|
||||
Op: "add",
|
||||
|
|
|
@ -13,11 +13,10 @@ import (
|
|||
func TestPatch(t *testing.T) {
|
||||
fixture := fake.NewFactory(filepath.Join("..", "..", "controller", "proxy-injector", "fake", "data"))
|
||||
|
||||
// TODO end entity volume
|
||||
// secrets, err := fixture.Volume("inject-linkerd-secrets-volume-spec.yaml")
|
||||
// if err != nil {
|
||||
// t.Fatal("Unexpected error: ", err)
|
||||
// }
|
||||
secrets, err := fixture.Volume("inject-linkerd-secrets-volume-spec.yaml")
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected error: ", err)
|
||||
}
|
||||
|
||||
sidecar, err := fixture.Container("inject-sidecar-container-spec.yaml")
|
||||
if err != nil {
|
||||
|
@ -34,22 +33,22 @@ func TestPatch(t *testing.T) {
|
|||
createdBy = "linkerd/cli v18.8.4"
|
||||
)
|
||||
|
||||
actual := NewPatchDeployment()
|
||||
actual := NewPatch(k8sPkg.Deployment)
|
||||
actual.addContainer(sidecar)
|
||||
actual.addInitContainerRoot()
|
||||
actual.addInitContainer(init)
|
||||
//actual.addVolumeRoot()
|
||||
//actual.addVolume(secrets)
|
||||
actual.addVolumeRoot()
|
||||
actual.addVolume(secrets)
|
||||
actual.addPodLabel(k8sPkg.ControllerNSLabel, controllerNamespace)
|
||||
actual.addPodAnnotation(k8sPkg.CreatedByAnnotation, createdBy)
|
||||
|
||||
expected := NewPatchDeployment()
|
||||
expected := NewPatch(k8sPkg.Deployment)
|
||||
expected.patchOps = []*patchOp{
|
||||
{Op: "add", Path: expected.patchPathContainer, Value: sidecar},
|
||||
{Op: "add", Path: expected.patchPathInitContainerRoot, Value: []*v1.Container{}},
|
||||
{Op: "add", Path: expected.patchPathInitContainer, Value: init},
|
||||
//{Op: "add", Path: expected.patchPathVolumeRoot, Value: []*v1.Volume{}},
|
||||
//{Op: "add", Path: expected.patchPathVolume, Value: secrets},
|
||||
{Op: "add", Path: expected.patchPathVolumeRoot, Value: []*v1.Volume{}},
|
||||
{Op: "add", Path: expected.patchPathVolume, Value: secrets},
|
||||
{Op: "add", Path: expected.patchPathPodLabels + "/" + escapeKey(k8sPkg.ControllerNSLabel), Value: controllerNamespace},
|
||||
{Op: "add", Path: expected.patchPathPodAnnotations + "/" + escapeKey(k8sPkg.CreatedByAnnotation), Value: createdBy},
|
||||
}
|
||||
|
|
|
@ -27,7 +27,11 @@ func newReport(conf *ResourceConfig) Report {
|
|||
var name string
|
||||
if m := conf.pod.Meta; m != nil {
|
||||
name = m.Name
|
||||
if name == "" {
|
||||
name = m.GenerateName
|
||||
}
|
||||
}
|
||||
|
||||
return Report{
|
||||
Kind: strings.ToLower(conf.workload.metaType.Kind),
|
||||
Name: name,
|
||||
|
@ -35,13 +39,13 @@ func newReport(conf *ResourceConfig) Report {
|
|||
}
|
||||
|
||||
// ResName returns a string "Kind/Name" for the workload referred in the report r
|
||||
func (r Report) ResName() string {
|
||||
func (r *Report) ResName() string {
|
||||
return fmt.Sprintf("%s/%s", r.Kind, r.Name)
|
||||
}
|
||||
|
||||
// Injectable returns false if the report flags indicate that the workload is on a host network
|
||||
// or there is already a sidecar or the resource is not supported or inject is explicitly disabled
|
||||
func (r Report) Injectable() bool {
|
||||
func (r *Report) Injectable() bool {
|
||||
return !r.HostNetwork && !r.Sidecar && !r.UnsupportedResource && !r.InjectDisabled
|
||||
}
|
||||
|
||||
|
|
|
@ -190,6 +190,10 @@ const (
|
|||
// MountPathBase is the base directory of the mount path.
|
||||
MountPathBase = "/var/run/linkerd"
|
||||
|
||||
// MountPathServiceAccount is the default path where Kuberenetes stores
|
||||
// the service account token
|
||||
MountPathServiceAccount = "/var/run/secrets/kubernetes.io/serviceaccount"
|
||||
|
||||
// MountPathGlobalConfig is the path at which the global config file is mounted.
|
||||
MountPathGlobalConfig = MountPathBase + "/config/global"
|
||||
|
||||
|
|
Loading…
Reference in New Issue