package handler
import (
"bytes"
"context"
"encoding/json"
"fmt"
apimodel "github.com/goodrain/rainbond/api/model"
"github.com/goodrain/rainbond/api/util"
"github.com/goodrain/rainbond/db"
dbmodel "github.com/goodrain/rainbond/db/model"
"github.com/goodrain/rainbond/pkg/component/storage"
"github.com/sirupsen/logrus"
"io/ioutil"
appv1 "k8s.io/api/apps/v1"
autoscalingv1 "k8s.io/api/autoscaling/v1"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer/yaml"
yamlt "k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"os"
"path"
"path/filepath"
"strings"
)
func HandleK8SResourceObjectsName(k8sResourceObjects []apimodel.K8sResourceObject) map[string]apimodel.LabelResource {
fileResource := make(map[string]apimodel.LabelResource)
var DeployNames, JobNames, CJNames, STSNames, RoleNames, HPANames, RBNames, SANames, SecretNames, ServiceNames, CMNames, NetworkPolicyNames, IngressNames, PVCNames []string
defaultResource := make(map[string][]string)
for _, k8sResourceObject := range k8sResourceObjects {
if k8sResourceObject.Error != "" {
fileResource[k8sResourceObject.FileName] = apimodel.LabelResource{
Status: k8sResourceObject.Error,
}
continue
}
for _, buildResource := range k8sResourceObject.BuildResources {
switch buildResource.Resource.GetKind() {
case apimodel.Deployment:
DeployNames = append(DeployNames, buildResource.Resource.GetName())
case apimodel.Job:
JobNames = append(JobNames, buildResource.Resource.GetName())
case apimodel.CronJob:
CJNames = append(CJNames, buildResource.Resource.GetName())
case apimodel.StateFulSet:
STSNames = append(STSNames, buildResource.Resource.GetName())
case apimodel.Role:
RoleNames = append(RoleNames, buildResource.Resource.GetName())
case apimodel.HorizontalPodAutoscaler:
HPANames = append(HPANames, buildResource.Resource.GetName())
case apimodel.RoleBinding:
RBNames = append(RBNames, buildResource.Resource.GetName())
case apimodel.ServiceAccount:
SANames = append(SANames, buildResource.Resource.GetName())
case apimodel.Secret:
SecretNames = append(SecretNames, buildResource.Resource.GetName())
case apimodel.Service:
ServiceNames = append(ServiceNames, buildResource.Resource.GetName())
case apimodel.ConfigMap:
CMNames = append(CMNames, buildResource.Resource.GetName())
case apimodel.NetworkPolicy:
NetworkPolicyNames = append(NetworkPolicyNames, buildResource.Resource.GetName())
case apimodel.Ingress:
IngressNames = append(IngressNames, buildResource.Resource.GetName())
case apimodel.PVC:
PVCNames = append(PVCNames, buildResource.Resource.GetName())
default:
defaultNames, ok := defaultResource[buildResource.Resource.GetKind()]
if ok {
defaultResource[buildResource.Resource.GetKind()] = append(defaultNames, buildResource.Resource.GetName())
} else {
defaultResource[buildResource.Resource.GetKind()] = []string{buildResource.Resource.GetName()}
}
}
}
}
fileResource["app_resource"] = apimodel.LabelResource{
UnSupport: defaultResource,
Workloads: apimodel.WorkLoadsResource{
Deployments: DeployNames,
Jobs: JobNames,
CronJobs: CJNames,
StateFulSets: STSNames,
},
Others: apimodel.OtherResource{
Services: ServiceNames,
PVC: PVCNames,
Ingresses: IngressNames,
NetworkPolicies: NetworkPolicyNames,
ConfigMaps: CMNames,
Secrets: SecretNames,
ServiceAccounts: ServiceNames,
RoleBindings: RoleNames,
HorizontalPodAutoscalers: HPANames,
Roles: RoleNames,
},
Status: "",
}
return fileResource
}
func (c *clusterAction) AppYamlResourceName(yamlResource apimodel.YamlResource) (map[string]apimodel.LabelResource, *util.APIHandleError) {
logrus.Infof("AppYamlResourceName begin")
k8sResourceObjects := c.YamlToResource(yamlResource, apimodel.YamlSourceFile, "")
fileResource := HandleK8SResourceObjectsName(k8sResourceObjects)
logrus.Infof("AppYamlResourceName end")
return fileResource, nil
}
func (c *clusterAction) AppYamlResourceDetailed(yamlResource apimodel.YamlResource, yamlImport bool) (apimodel.ApplicationResource, *util.APIHandleError) {
logrus.Infof("AppYamlResourceDetailed begin")
source := apimodel.YamlSourceFile
if yamlResource.Yaml != "" {
source = apimodel.YamlSourceHelm
}
k8sResourceObjects := c.YamlToResource(yamlResource, source, yamlResource.Yaml)
appResource := HandleDetailResource(yamlResource.Namespace, k8sResourceObjects, yamlImport, c.clientset, c.mapper)
return appResource, nil
}
func (c *clusterAction) AppYamlResourceImport(namespace, tenantID, appID string, components apimodel.ApplicationResource) (apimodel.AppComponent, *util.APIHandleError) {
logrus.Infof("AppYamlResourceImport begin")
app, err := db.GetManager().ApplicationDao().GetAppByID(appID)
if err != nil {
return apimodel.AppComponent{}, &util.APIHandleError{Code: 400, Err: fmt.Errorf("GetAppByID error %v", err)}
}
var ar apimodel.AppComponent
k8sResource, err := c.CreateK8sResource(components.KubernetesResources, app.AppID)
if err != nil {
logrus.Errorf("create K8sResources err:%v", err)
return ar, &util.APIHandleError{Code: 400, Err: fmt.Errorf("create K8sResources err:%v", err)}
}
var componentAttributes []apimodel.ComponentAttributes
existComponents, err := db.GetManager().TenantServiceDao().ListByAppID(app.AppID)
if err != nil {
return apimodel.AppComponent{}, &util.APIHandleError{Code: 400, Err: fmt.Errorf("get app service by appID failure: %v", err)}
}
for _, componentResource := range components.ConvertResource {
component, err := c.CreateComponent(app, tenantID, componentResource, namespace, true, existComponents)
if err != nil {
logrus.Errorf("%v", err)
return ar, &util.APIHandleError{Code: 400, Err: fmt.Errorf("create app error:%v", err)}
}
c.createENV(componentResource.ENVManagement, component)
c.createConfig(componentResource.ConfigManagement, component)
c.createPort(componentResource.PortManagement, component)
componentResource.TelescopicManagement.RuleID = c.createTelescopic(componentResource.TelescopicManagement, component)
componentResource.HealthyCheckManagement.ProbeID = c.createHealthyCheck(componentResource.HealthyCheckManagement, component)
c.createK8sAttributes(componentResource.ComponentK8sAttributesManagement, tenantID, component)
componentAttributes = append(componentAttributes, apimodel.ComponentAttributes{
TS: component,
Image: componentResource.BasicManagement.Image,
Cmd: componentResource.BasicManagement.Cmd,
ENV: componentResource.ENVManagement,
Config: componentResource.ConfigManagement,
Port: componentResource.PortManagement,
Telescopic: componentResource.TelescopicManagement,
HealthyCheck: componentResource.HealthyCheckManagement,
ComponentK8sAttributes: componentResource.ComponentK8sAttributesManagement,
})
}
ar = apimodel.AppComponent{
App: app,
K8sResources: k8sResource,
Component: componentAttributes,
}
if err != nil {
return apimodel.AppComponent{}, &util.APIHandleError{Code: 400, Err: fmt.Errorf("app yaml resource import error:%v", err)}
}
logrus.Infof("AppYamlResourceImport end")
return ar, nil
}
func (c *clusterAction) YamlToResource(yamlResource apimodel.YamlResource, yamlSource, yamlContent string) []apimodel.K8sResourceObject {
yamlDirectoryPath := path.Join("/grdata/package_build/temp/events", yamlResource.EventID, "*")
yamlFilesPath := []string{apimodel.YamlSourceHelm}
if yamlSource == apimodel.YamlSourceFile {
baseDIR := path.Join("/grdata/package_build/temp/events", yamlResource.EventID)
err := storage.Default().StorageCli.DownloadDirToDir(baseDIR, baseDIR)
if err != nil {
logrus.Errorf("DownloadDirToDir failure: %v", err)
}
yamlFilesPath, _ = filepath.Glob(yamlDirectoryPath)
}
var fileBuildResourceList []apimodel.K8sResourceObject
for _, yamlFilePath := range yamlFilesPath {
var fileName string
yamlFileBytes := []byte(strings.TrimPrefix(yamlContent, "\n"))
if yamlSource == apimodel.YamlSourceFile {
fileName = path.Base(yamlFilePath)
var err error
yamlFileBytes, err = ioutil.ReadFile(yamlFilePath)
yamlFileBytes = []byte(strings.TrimPrefix(string(yamlFileBytes), "\n"))
if err != nil {
logrus.Errorf("yaml to resource first step failure: %v", err)
fileBuildResourceList = append(fileBuildResourceList, apimodel.K8sResourceObject{
FileName: fileName,
BuildResources: nil,
Error: err.Error(),
})
continue
}
}
fileBuildResourceList = handleFileORYamlToObject(fileName, yamlFileBytes, c.config)
}
return fileBuildResourceList
}
func ResourceCreate(buildResource apimodel.BuildResource, namespace string, mapper meta.RESTMapper, clientset *kubernetes.Clientset) (*unstructured.Unstructured, error) {
logrus.Infof("begin ResourceCreate function")
mapping, err := mapper.RESTMapping(buildResource.GVK.GroupKind(), buildResource.GVK.Version)
if err != nil {
if !meta.IsNoMatchError(err) {
return nil, err
}
mapper, err = RefreshMapper(clientset)
if err != nil {
return nil, err
}
mapping, err = mapper.RESTMapping(buildResource.GVK.GroupKind(), buildResource.GVK.Version)
if err != nil {
return nil, err
}
}
if mapping.Scope.Name() == meta.RESTScopeNameNamespace {
buildResource.Resource.SetNamespace(namespace)
buildResource.Dri = buildResource.DC.Resource(mapping.Resource).Namespace(buildResource.Resource.GetNamespace())
} else {
buildResource.Dri = buildResource.DC.Resource(mapping.Resource)
}
obj, err := buildResource.Dri.Create(context.Background(), buildResource.Resource, metav1.CreateOptions{})
if err != nil {
return nil, err
}
return obj, nil
}
func handleFileORYamlToObject(fileName string, yamlFileBytes []byte, config *rest.Config) []apimodel.K8sResourceObject {
var fileBuildResourceList []apimodel.K8sResourceObject
dc, err := dynamic.NewForConfig(config)
if err != nil {
logrus.Errorf("yaml to resource second step failure: %v", err)
fileBuildResourceList = append(fileBuildResourceList, apimodel.K8sResourceObject{
FileName: fileName,
BuildResources: nil,
Error: err.Error(),
})
return fileBuildResourceList
}
decoder := yamlt.NewYAMLOrJSONDecoder(bytes.NewReader(yamlFileBytes), 1000)
var buildResourceList []apimodel.BuildResource
for {
var rawObj runtime.RawExtension
if err = decoder.Decode(&rawObj); err != nil {
if err.Error() == "EOF" {
break
}
logrus.Errorf("yaml to resource third step failure: %v", err)
fileBuildResourceList = append(fileBuildResourceList, apimodel.K8sResourceObject{
FileName: fileName,
BuildResources: nil,
Error: err.Error(),
})
continue
}
obj, gvk, err := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme).Decode(rawObj.Raw, nil, nil)
if err != nil {
logrus.Errorf("yaml to resource fourth step failure: %v", err)
fileBuildResourceList = append(fileBuildResourceList, apimodel.K8sResourceObject{
FileName: fileName,
BuildResources: nil,
Error: err.Error(),
})
continue
}
unstructuredMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
if err != nil {
logrus.Errorf("yaml to resource fifth step failure: %v", err)
fileBuildResourceList = append(fileBuildResourceList, apimodel.K8sResourceObject{
FileName: fileName,
BuildResources: nil,
Error: err.Error(),
})
continue
}
unstructuredObj := &unstructured.Unstructured{Object: unstructuredMap}
if os.Getenv("USE_SAAS") == "true" && gvk.Kind != "ConfigMap" && gvk.Kind != "Secret" && gvk.Kind != "Deployment" && gvk.Kind != "StatefulSet" {
errMsg := fmt.Sprintf("无效的资源类型: %s. 只允许 ConfigMap、Secret、Deployment 和 StatefulSet.", gvk.Kind)
logrus.Errorf(errMsg)
fileBuildResourceList = append(fileBuildResourceList, apimodel.K8sResourceObject{
FileName: fileName,
BuildResources: nil,
Error: errMsg,
})
continue
}
buildResourceList = append(buildResourceList, apimodel.BuildResource{
Resource: unstructuredObj,
State: apimodel.CreateError,
ErrorOverview: "",
Dri: nil,
DC: dc,
GVK: gvk,
})
}
fileBuildResourceList = append(fileBuildResourceList, apimodel.K8sResourceObject{
FileName: fileName,
BuildResources: buildResourceList,
Error: "",
})
return fileBuildResourceList
}
func HandleDetailResource(namespace string, k8sResourceObjects []apimodel.K8sResourceObject, yamlImport bool, clientset *kubernetes.Clientset, mapper meta.RESTMapper) apimodel.ApplicationResource {
var K8SResource []dbmodel.K8sResource
var ConvertResource []apimodel.ConvertResource
for _, k8sResourceObject := range k8sResourceObjects {
if k8sResourceObject.Error != "" {
continue
}
var cms []corev1.ConfigMap
var hpas []autoscalingv1.HorizontalPodAutoscaler
for _, buildResource := range k8sResourceObject.BuildResources {
if buildResource.Resource.GetKind() == apimodel.ConfigMap {
var cm corev1.ConfigMap
cmJSON, _ := json.Marshal(buildResource.Resource)
json.Unmarshal(cmJSON, &cm)
cms = append(cms, cm)
continue
}
if buildResource.Resource.GetKind() == apimodel.HorizontalPodAutoscaler {
var hpa autoscalingv1.HorizontalPodAutoscaler
cmJSON, _ := json.Marshal(buildResource.Resource)
json.Unmarshal(cmJSON, &hpa)
hpas = append(hpas, hpa)
}
}
for _, buildResource := range k8sResourceObject.BuildResources {
errorOverview := "创建成功"
state := apimodel.CreateSuccess
switch buildResource.Resource.GetKind() {
case apimodel.Deployment:
deployJSON, _ := json.Marshal(buildResource.Resource)
var deployObject appv1.Deployment
json.Unmarshal(deployJSON, &deployObject)
memory, cpu := deployObject.Spec.Template.Spec.Containers[0].Resources.Requests.Memory().Value(), deployObject.Spec.Template.Spec.Containers[0].Resources.Requests.Cpu().MilliValue()
if memory == 0 {
memory = deployObject.Spec.Template.Spec.Containers[0].Resources.Limits.Memory().Value()
}
if cpu == 0 {
cpu = deployObject.Spec.Template.Spec.Containers[0].Resources.Limits.Cpu().MilliValue()
}
basic := apimodel.BasicManagement{
ResourceType: apimodel.Deployment,
Replicas: deployObject.Spec.Replicas,
Memory: memory / 1024 / 1024,
CPU: cpu,
Image: deployObject.Spec.Template.Spec.Containers[0].Image,
Cmd: strings.Join(append(deployObject.Spec.Template.Spec.Containers[0].Command, deployObject.Spec.Template.Spec.Containers[0].Args...), " "),
}
parameter := apimodel.YamlResourceParameter{
ComponentsCR: &ConvertResource,
Basic: basic,
Template: deployObject.Spec.Template,
Namespace: namespace,
Name: buildResource.Resource.GetName(),
RsLabel: deployObject.Labels,
HPAs: hpas,
CMs: cms,
}
PodTemplateSpecResource(parameter, nil, clientset)
case apimodel.Job:
jobJSON, _ := json.Marshal(buildResource.Resource)
var jobObject batchv1.Job
json.Unmarshal(jobJSON, &jobObject)
memory, cpu := jobObject.Spec.Template.Spec.Containers[0].Resources.Requests.Memory().Value(), jobObject.Spec.Template.Spec.Containers[0].Resources.Requests.Cpu().MilliValue()
if memory == 0 {
memory = jobObject.Spec.Template.Spec.Containers[0].Resources.Limits.Memory().Value()
}
if cpu == 0 {
cpu = jobObject.Spec.Template.Spec.Containers[0].Resources.Limits.Cpu().MilliValue()
}
basic := apimodel.BasicManagement{
ResourceType: apimodel.Job,
Replicas: jobObject.Spec.Completions,
Memory: memory / 1024 / 1024,
CPU: cpu,
Image: jobObject.Spec.Template.Spec.Containers[0].Image,
Cmd: strings.Join(append(jobObject.Spec.Template.Spec.Containers[0].Command, jobObject.Spec.Template.Spec.Containers[0].Args...), " "),
}
parameter := apimodel.YamlResourceParameter{
ComponentsCR: &ConvertResource,
Basic: basic,
Template: jobObject.Spec.Template,
Namespace: namespace,
Name: buildResource.Resource.GetName(),
RsLabel: jobObject.Labels,
HPAs: hpas,
CMs: cms,
}
PodTemplateSpecResource(parameter, nil, clientset)
case apimodel.CronJob:
cjJSON, _ := json.Marshal(buildResource.Resource)
var cjObject batchv1.CronJob
json.Unmarshal(cjJSON, &cjObject)
memory, cpu := cjObject.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Resources.Requests.Memory().Value(), cjObject.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Resources.Requests.Cpu().MilliValue()
if memory == 0 {
memory = cjObject.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Resources.Limits.Memory().Value()
}
if cpu == 0 {
cpu = cjObject.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Resources.Limits.Cpu().MilliValue()
}
basic := apimodel.BasicManagement{
ResourceType: apimodel.CronJob,
Replicas: cjObject.Spec.JobTemplate.Spec.Completions,
Memory: memory / 1024 / 1024,
CPU: cpu,
Image: cjObject.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Image,
Cmd: strings.Join(append(cjObject.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Command, cjObject.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Args...), " "),
}
parameter := apimodel.YamlResourceParameter{
ComponentsCR: &ConvertResource,
Basic: basic,
Template: cjObject.Spec.JobTemplate.Spec.Template,
Namespace: namespace,
Name: buildResource.Resource.GetName(),
RsLabel: cjObject.Labels,
HPAs: hpas,
CMs: cms,
}
PodTemplateSpecResource(parameter, nil, clientset)
case apimodel.StateFulSet:
stsJSON, _ := json.Marshal(buildResource.Resource)
var stsObject appv1.StatefulSet
json.Unmarshal(stsJSON, &stsObject)
memory, cpu := stsObject.Spec.Template.Spec.Containers[0].Resources.Requests.Memory().Value(), stsObject.Spec.Template.Spec.Containers[0].Resources.Requests.Cpu().MilliValue()
if memory == 0 {
memory = stsObject.Spec.Template.Spec.Containers[0].Resources.Limits.Memory().Value()
}
if cpu == 0 {
cpu = stsObject.Spec.Template.Spec.Containers[0].Resources.Limits.Cpu().MilliValue()
}
basic := apimodel.BasicManagement{
ResourceType: apimodel.StateFulSet,
Replicas: stsObject.Spec.Replicas,
Memory: memory / 1024 / 1024,
CPU: cpu,
Image: stsObject.Spec.Template.Spec.Containers[0].Image,
Cmd: strings.Join(append(stsObject.Spec.Template.Spec.Containers[0].Command, stsObject.Spec.Template.Spec.Containers[0].Args...), " "),
}
parameter := apimodel.YamlResourceParameter{
ComponentsCR: &ConvertResource,
Basic: basic,
Template: stsObject.Spec.Template,
Namespace: namespace,
Name: buildResource.Resource.GetName(),
RsLabel: stsObject.Labels,
HPAs: hpas,
CMs: cms,
}
PodTemplateSpecResource(parameter, stsObject.Spec.VolumeClaimTemplates, clientset)
default:
if yamlImport {
resource, err := ResourceCreate(buildResource, namespace, mapper, clientset)
if err != nil {
errorOverview = err.Error()
state = apimodel.CreateError
} else {
buildResource.Resource = resource
}
}
kubernetesResourcesYAML, err := ObjectToJSONORYaml("yaml", buildResource.Resource)
if err != nil {
logrus.Errorf("namespace:%v %v:%v error: %v", namespace, buildResource.Resource.GetKind(), buildResource.Resource.GetName(), err)
continue
}
K8SResource = append(K8SResource, dbmodel.K8sResource{
Name: buildResource.Resource.GetName(),
Kind: buildResource.Resource.GetKind(),
Content: kubernetesResourcesYAML,
State: state,
ErrorOverview: errorOverview,
})
}
}
}
logrus.Infof("AppYamlResourceDetailed end")
return apimodel.ApplicationResource{
KubernetesResources: K8SResource,
ConvertResource: ConvertResource,
}
}