blob: 36ba666466bdf5781325cbb50fa286b74f4f90ca [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 kube
import (
"fmt"
"github.com/apache/dubbo-kubernetes/sail/pkg/features"
"io"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"net/http"
"os"
"regexp"
"strings"
)
var cronJobNameRegexp = regexp.MustCompile(`(.+)-\d{8,10}$`)
func DefaultRestConfig(kubeconfig, configContext string, fns ...func(config *rest.Config)) (*rest.Config, error) {
config, err := BuildClientConfig(kubeconfig, configContext)
if err != nil {
return nil, err
}
for _, fn := range fns {
fn(config)
}
return config, nil
}
func BuildClientCmd(kubeconfig, context string, overrides ...func(configOverrides *clientcmd.ConfigOverrides)) clientcmd.ClientConfig {
if kubeconfig != "" {
info, err := os.Stat(kubeconfig)
if err != nil || info.Size() == 0 {
kubeconfig = ""
}
}
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
loadingRules.DefaultClientConfig = &clientcmd.DefaultClientConfig
loadingRules.ExplicitPath = kubeconfig
configOverrides := &clientcmd.ConfigOverrides{
ClusterDefaults: clientcmd.ClusterDefaults,
CurrentContext: context,
}
for _, fn := range overrides {
fn(configOverrides)
}
return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides)
}
func BuildClientConfig(kubeconfig, context string) (*rest.Config, error) {
c, err := BuildClientCmd(kubeconfig, context).ClientConfig()
if err != nil {
return nil, err
}
return SetRestDefaults(c), nil
}
func SetRestDefaults(config *rest.Config) *rest.Config {
if config.GroupVersion == nil || config.GroupVersion.Empty() {
config.GroupVersion = &corev1.SchemeGroupVersion
}
if len(config.APIPath) == 0 {
if len(config.GroupVersion.Group) == 0 {
config.APIPath = "/api"
} else {
config.APIPath = "/apis"
}
}
if len(config.ContentType) == 0 {
if features.KubernetesClientContentType == "json" {
config.ContentType = runtime.ContentTypeJSON
} else {
config.AcceptContentTypes = runtime.ContentTypeProtobuf + "," + runtime.ContentTypeJSON
config.ContentType = runtime.ContentTypeJSON
}
}
if config.NegotiatedSerializer == nil {
config.NegotiatedSerializer = serializer.NewCodecFactory(nil).WithoutConversion()
}
return config
}
func StripPodUnusedFields(obj any) (any, error) {
t, ok := obj.(metav1.ObjectMetaAccessor)
if !ok {
// shouldn't happen
return obj, nil
}
// ManagedFields is large and we never use it
t.GetObjectMeta().SetManagedFields(nil)
// only container ports can be used
if pod := obj.(*corev1.Pod); pod != nil {
containers := []corev1.Container{}
for _, c := range pod.Spec.Containers {
if len(c.Ports) > 0 {
containers = append(containers, corev1.Container{
Ports: c.Ports,
})
}
}
oldSpec := pod.Spec
newSpec := corev1.PodSpec{
Containers: containers,
ServiceAccountName: oldSpec.ServiceAccountName,
NodeName: oldSpec.NodeName,
HostNetwork: oldSpec.HostNetwork,
Hostname: oldSpec.Hostname,
Subdomain: oldSpec.Subdomain,
}
pod.Spec = newSpec
pod.Status.InitContainerStatuses = nil
pod.Status.ContainerStatuses = nil
}
return obj, nil
}
const MaxRequestBodyBytes = int64(6 * 1024 * 1024)
func HTTPConfigReader(req *http.Request) ([]byte, error) {
defer req.Body.Close()
lr := &io.LimitedReader{
R: req.Body,
N: MaxRequestBodyBytes + 1,
}
data, err := io.ReadAll(lr)
if err != nil {
return nil, err
}
if lr.N <= 0 {
return nil, errors.NewRequestEntityTooLargeError(fmt.Sprintf("limit is %d", MaxRequestBodyBytes))
}
return data, nil
}
func GetDeployMetaFromPod(pod *corev1.Pod) (types.NamespacedName, metav1.TypeMeta) {
if pod == nil {
return types.NamespacedName{}, metav1.TypeMeta{}
}
// try to capture more useful namespace/name info for deployments, etc.
// TODO(dougreid): expand to enable lookup of OWNERs recursively a la kubernetesenv
deployMeta := types.NamespacedName{Name: pod.Name, Namespace: pod.Namespace}
typeMetadata := metav1.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
}
if len(pod.GenerateName) > 0 {
// if the pod name was generated (or is scheduled for generation), we can begin an investigation into the controlling reference for the pod.
var controllerRef metav1.OwnerReference
controllerFound := false
for _, ref := range pod.GetOwnerReferences() {
if ref.Controller != nil && *ref.Controller {
controllerRef = ref
controllerFound = true
break
}
}
if controllerFound {
typeMetadata.APIVersion = controllerRef.APIVersion
typeMetadata.Kind = controllerRef.Kind
// heuristic for deployment detection
deployMeta.Name = controllerRef.Name
if typeMetadata.Kind == "ReplicaSet" && pod.Labels["pod-template-hash"] != "" && strings.HasSuffix(controllerRef.Name, pod.Labels["pod-template-hash"]) {
name := strings.TrimSuffix(controllerRef.Name, "-"+pod.Labels["pod-template-hash"])
deployMeta.Name = name
typeMetadata.Kind = "Deployment"
} else if typeMetadata.Kind == "ReplicaSet" && pod.Labels["rollouts-pod-template-hash"] != "" &&
strings.HasSuffix(controllerRef.Name, pod.Labels["rollouts-pod-template-hash"]) {
// Heuristic for ArgoCD Rollout
name := strings.TrimSuffix(controllerRef.Name, "-"+pod.Labels["rollouts-pod-template-hash"])
deployMeta.Name = name
typeMetadata.Kind = "Rollout"
typeMetadata.APIVersion = "v1alpha1"
} else if typeMetadata.Kind == "ReplicationController" && pod.Labels["deploymentconfig"] != "" {
// If the pod is controlled by the replication controller, which is created by the DeploymentConfig resource in
// Openshift platform, set the deploy name to the deployment config's name, and the kind to 'DeploymentConfig'.
//
// nolint: lll
// For DeploymentConfig details, refer to
// https://docs.openshift.com/container-platform/4.1/applications/deployments/what-deployments-are.html#deployments-and-deploymentconfigs_what-deployments-are
//
// For the reference to the pod label 'deploymentconfig', refer to
// https://github.com/openshift/library-go/blob/7a65fdb398e28782ee1650959a5e0419121e97ae/pkg/apps/appsutil/const.go#L25
deployMeta.Name = pod.Labels["deploymentconfig"]
typeMetadata.Kind = "DeploymentConfig"
} else if typeMetadata.Kind == "Job" {
// If job name suffixed with `-<digit-timestamp>`, where the length of digit timestamp is 8~10,
// trim the suffix and set kind to cron job.
if jn := cronJobNameRegexp.FindStringSubmatch(controllerRef.Name); len(jn) == 2 {
deployMeta.Name = jn[1]
typeMetadata.Kind = "CronJob"
// heuristically set cron job api version to v1 as it cannot be derived from pod metadata.
typeMetadata.APIVersion = "batch/v1"
}
}
}
}
if deployMeta.Name == "" {
// if we haven't been able to extract a deployment name, then just give it the pod name
deployMeta.Name = pod.Name
}
return deployMeta, typeMetadata
}