blob: 28368b4420620d349a9bc6eded50b0b2748ba86a [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF 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 patch
import (
"fmt"
"strconv"
dubbo_cp "github.com/apache/dubbo-kubernetes/pkg/config/app/dubbo-cp"
"github.com/apache/dubbo-kubernetes/pkg/core/client/webhook"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
)
type DubboSdk struct {
options *dubbo_cp.Config
webhookClient webhook.Client
kubeClient kubernetes.Interface
}
func NewDubboSdk(options *dubbo_cp.Config, webhookClient webhook.Client, kubeClient kubernetes.Interface) *DubboSdk {
return &DubboSdk{
options: options,
webhookClient: webhookClient,
kubeClient: kubeClient,
}
}
const (
ExpireSeconds = 1800
Labeled = "true"
EnvDubboRegistryAddress = "DUBBO_REGISTRY_ADDRESS"
)
const (
RegistryInjectZookeeperLabel = "registry-zookeeper-inject"
RegistryInjectNacosLabel = "registry-nacos-inject"
RegistryInjectK8sLabel = "registry-k8s-inject"
DefaultK8sRegistryAddress = "kubernetes://DEFAULT_MASTER_HOST"
)
// the priority of registry
// default is zk > nacos
var (
registryInjectLabelPriorities = []string{
RegistryInjectZookeeperLabel,
RegistryInjectNacosLabel,
RegistryInjectK8sLabel,
}
registrySchemas = map[string]string{
RegistryInjectZookeeperLabel: "zookeeper",
RegistryInjectNacosLabel: "nacos",
}
)
func (s *DubboSdk) injectAnnotations(target *v1.Pod, annotations map[string]string) {
if target.Annotations == nil {
target.Annotations = make(map[string]string)
}
for k, v := range annotations {
if _, ok := target.Annotations[k]; !ok {
target.Annotations[k] = v
}
}
}
func (s *DubboSdk) NewPodWithDubboRegistryInject(origin *v1.Pod) (*v1.Pod, error) {
target := origin.DeepCopy()
// find specific registry inject label (such as zookeeper-registry-inject)
// in pod labels and namespace labels
var registryInjects []string
// 1. find in pod labels
for _, registryInject := range registryInjectLabelPriorities {
if target.Labels[registryInject] == Labeled { // find in pod labels
registryInjects = []string{registryInject}
break
}
}
// 2. find in namespace labels
if len(registryInjects) == 0 {
for _, registryInject := range registryInjectLabelPriorities {
if s.webhookClient.GetNamespaceLabels(target.Namespace)[registryInject] == Labeled {
// find in namespace labels
registryInjects = []string{registryInject}
break
}
}
}
// default is zk > nacos > k8s
if len(registryInjects) == 0 {
registryInjects = registryInjectLabelPriorities
}
// find registry service in k8s
var registryAddress string
for _, registryInject := range registryInjects {
if registryInject == RegistryInjectK8sLabel { // k8s registry
registryAddress = DefaultK8sRegistryAddress
continue
}
// other registry
serviceList := s.webhookClient.ListServices(target.Namespace, metav1.ListOptions{
LabelSelector: fmt.Sprintf("%s=%s", registryInject, Labeled),
})
if serviceList == nil || len(serviceList.Items) < 1 {
continue
}
schema := registrySchemas[registryInject]
registryAddress = fmt.Sprintf("%s://%s.%s.svc", schema, serviceList.Items[0].Name, serviceList.Items[0].Namespace)
break
}
var found bool
if len(registryAddress) > 0 {
// inject into env
var targetContainers []v1.Container
for _, c := range target.Spec.Containers {
if !found { // found DUBBO_REGISTRY_ADDRESS ENV, stop inject
found = s.injectEnv(&c, EnvDubboRegistryAddress, registryAddress)
}
targetContainers = append(targetContainers, c)
}
target.Spec.Containers = targetContainers
}
return target, nil
}
func (s *DubboSdk) injectEnv(container *v1.Container, name, value string) (found bool) {
for j, env := range container.Env {
if env.Name == name {
found = true
// env is not empty, inject into env
if len(env.Value) > 0 {
break
}
container.Env[j].Value = value
break
}
}
if found { // found registry env in pod, stop inject
return
}
container.Env = append(container.Env, v1.EnvVar{
Name: name,
Value: value,
})
return
}
func (s *DubboSdk) NewPodWithDubboCa(origin *v1.Pod) (*v1.Pod, error) {
target := origin.DeepCopy()
expireSeconds := int64(ExpireSeconds)
shouldInject := false
if target.Labels["dubbo-ca.inject"] == Labeled {
shouldInject = true
}
if !shouldInject && s.webhookClient.GetNamespaceLabels(target.Namespace)["dubbo-ca.inject"] == Labeled {
shouldInject = true
}
if shouldInject {
shouldInject = s.checkVolume(target, shouldInject)
for _, c := range target.Spec.Containers {
shouldInject = s.checkContainers(c, shouldInject)
}
}
if shouldInject {
s.injectVolumes(target, expireSeconds)
var targetContainers []v1.Container
for _, c := range target.Spec.Containers {
s.injectContainers(&c)
targetContainers = append(targetContainers, c)
}
target.Spec.Containers = targetContainers
}
return target, nil
}
func (s *DubboSdk) injectContainers(c *v1.Container) {
c.Env = append(c.Env, v1.EnvVar{
Name: "DUBBO_CA_ADDRESS",
Value: s.options.KubeConfig.ServiceName + "." + s.options.KubeConfig.Namespace + ".svc:" + strconv.Itoa(s.options.GrpcServer.SecureServerPort),
})
c.Env = append(c.Env, v1.EnvVar{
Name: "DUBBO_CA_CERT_PATH",
Value: "/var/run/secrets/dubbo-ca-cert/ca.crt",
})
c.Env = append(c.Env, v1.EnvVar{
Name: "DUBBO_OIDC_TOKEN",
Value: "/var/run/secrets/dubbo-ca-token/token",
})
c.Env = append(c.Env, v1.EnvVar{
Name: "DUBBO_OIDC_TOKEN_TYPE",
Value: "dubbo-ca-token",
})
c.VolumeMounts = append(c.VolumeMounts, v1.VolumeMount{
Name: "dubbo-ca-token",
MountPath: "/var/run/secrets/dubbo-ca-token",
ReadOnly: true,
})
c.VolumeMounts = append(c.VolumeMounts, v1.VolumeMount{
Name: "dubbo-ca-cert",
MountPath: "/var/run/secrets/dubbo-ca-cert",
ReadOnly: true,
})
}
func (s *DubboSdk) injectVolumes(target *v1.Pod, expireSeconds int64) {
target.Spec.Volumes = append(target.Spec.Volumes, v1.Volume{
Name: "dubbo-ca-token",
VolumeSource: v1.VolumeSource{
Projected: &v1.ProjectedVolumeSource{
Sources: []v1.VolumeProjection{
{
ServiceAccountToken: &v1.ServiceAccountTokenProjection{
Audience: "dubbo-ca",
ExpirationSeconds: &expireSeconds,
Path: "token",
},
},
},
},
},
})
target.Spec.Volumes = append(target.Spec.Volumes, v1.Volume{
Name: "dubbo-ca-cert",
VolumeSource: v1.VolumeSource{
Projected: &v1.ProjectedVolumeSource{
Sources: []v1.VolumeProjection{
{
ConfigMap: &v1.ConfigMapProjection{
LocalObjectReference: v1.LocalObjectReference{
Name: "dubbo-ca-cert",
},
Items: []v1.KeyToPath{
{
Key: "ca.crt",
Path: "ca.crt",
},
},
},
},
},
},
},
})
}
func (s *DubboSdk) checkContainers(c v1.Container, shouldInject bool) bool {
for _, e := range c.Env {
if e.Name == "DUBBO_CA_ADDRESS" {
shouldInject = false
break
}
if e.Name == "DUBBO_CA_CERT_PATH" {
shouldInject = false
break
}
if e.Name == "DUBBO_OIDC_TOKEN" {
shouldInject = false
break
}
if e.Name == "DUBBO_OIDC_TOKEN_TYPE" {
shouldInject = false
break
}
}
for _, m := range c.VolumeMounts {
if m.Name == "dubbo-ca-token" {
shouldInject = false
break
}
if m.Name == "dubbo-ca-cert" {
shouldInject = false
break
}
}
return shouldInject
}
func (s *DubboSdk) checkVolume(target *v1.Pod, shouldInject bool) bool {
for _, v := range target.Spec.Volumes {
if v.Name == "dubbo-ca-token" {
shouldInject = false
break
}
}
for _, v := range target.Spec.Volumes {
if v.Name == "dubbo-ca-cert" {
shouldInject = false
break
}
}
return shouldInject
}