package handler import ( "context" "encoding/base64" "encoding/json" "fmt" apimodel "github.com/goodrain/rainbond/api/model" "github.com/goodrain/rainbond/api/util" "github.com/goodrain/rainbond/pkg/component/k8s" "github.com/goodrain/rainbond/pkg/component/storage" "github.com/goodrain/rainbond/pkg/generated/clientset/versioned" "github.com/goodrain/rainbond/pkg/helm" rutil "github.com/goodrain/rainbond/util" hrepo "github.com/helm/helm/pkg/repo" "github.com/pkg/errors" "github.com/sirupsen/logrus" "helm.sh/helm/v3/pkg/chart/loader" "io/ioutil" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "net/http" "os" "path" "path/filepath" "sigs.k8s.io/yaml" "strings" ) // AppTemplate - type AppTemplate struct { Name string Versions hrepo.ChartVersions } // HelmAction - type HelmAction struct { ctx context.Context kubeClient *kubernetes.Clientset rainbondClient versioned.Interface repo *helm.Repo config *rest.Config mapper meta.RESTMapper } // CreateHelmManager 创建 helm 客户端 func CreateHelmManager() *HelmAction { repo := helm.NewRepo(repoFile, repoCache) return &HelmAction{ kubeClient: k8s.Default().Clientset, rainbondClient: k8s.Default().RainbondClient, ctx: context.Background(), repo: repo, config: k8s.Default().RestConfig, mapper: k8s.Default().Mapper, } } var ( dataDir = "/grdata/helm" repoFile = path.Join(dataDir, "repo/repositories.yaml") repoCache = path.Join(dataDir, "cache") ) // GetChartInformation 获取 helm 应用 chart 包的详细版本信息 func (h *HelmAction) GetChartInformation(chart apimodel.ChartInformation) (*[]apimodel.HelmChartInformation, *util.APIHandleError) { req, err := http.NewRequest("GET", chart.RepoURL+"/index.yaml", nil) if err != nil { return nil, &util.APIHandleError{Code: 400, Err: errors.Wrap(err, "GetChartInformation NewRequest")} } client := &http.Client{} req.SetBasicAuth(chart.Username, chart.Password) resp, err := client.Do(req) if err != nil { return nil, &util.APIHandleError{Code: 400, Err: errors.Wrap(err, "GetChartInformation client.Do")} } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, &util.APIHandleError{Code: 400, Err: errors.Wrap(err, "GetChartInformation ioutil.ReadAll")} } jbody, err := yaml.YAMLToJSON(body) if err != nil { return nil, &util.APIHandleError{Code: 400, Err: errors.Wrap(err, "GetChartInformation yaml.YAMLToJSON")} } var indexFile hrepo.IndexFile if err := json.Unmarshal(jbody, &indexFile); err != nil { logrus.Errorf("json.Unmarshal: %v", err) return nil, &util.APIHandleError{Code: 400, Err: errors.Wrap(err, "GetChartInformation json.Unmarshal")} } if len(indexFile.Entries) == 0 { return nil, &util.APIHandleError{Code: 400, Err: fmt.Errorf("entries not found")} } var chartInformations []apimodel.HelmChartInformation if chart, ok := indexFile.Entries[chart.ChartName]; ok { for _, version := range chart { v := version chartInformations = append(chartInformations, apimodel.HelmChartInformation{ Version: v.Version, Keywords: v.Keywords, Pic: v.Icon, Abstract: v.Description, }) } } return &chartInformations, nil } // CheckHelmApp check helm app func (h *HelmAction) CheckHelmApp(checkHelmApp apimodel.CheckHelmApp) (string, error) { helmAppYaml, err := GetHelmAppYaml(checkHelmApp.Name, checkHelmApp.Chart, checkHelmApp.Version, checkHelmApp.Namespace, "", checkHelmApp.Overrides) if err != nil { return "", errors.Wrap(err, "helm app check failed") } return helmAppYaml, nil } // UpdateHelmRepo update repo func (h *HelmAction) UpdateHelmRepo(names string) error { err := UpdateRepo(names) if err != nil { return errors.Wrap(err, "helm repo update failed") } return nil } // AddHelmRepo add helm repo func (h *HelmAction) AddHelmRepo(helmRepo apimodel.CheckHelmApp) error { err := h.repo.Add(helmRepo.RepoName, helmRepo.RepoURL, helmRepo.Username, helmRepo.Password) if err != nil { logrus.Errorf("add helm repo err: %v", err) return err } return nil } // GetHelmAppYaml get helm app yaml func GetHelmAppYaml(name, chart, version, namespace, chartPath string, overrides []string) (string, error) { helmCmd, err := helm.NewHelm(namespace, repoFile, repoCache) if err != nil { logrus.Errorf("Failed to create help client:%v", err) return "", err } release, err := helmCmd.Install(chartPath, name, chart, version, overrides) if err != nil { logrus.Errorf("helm --dry-run install failure: %v", err) return "", err } return release.Manifest, nil } // UpdateRepo Update Helm warehouse func UpdateRepo(names string) error { helmCmd, err := helm.NewHelm("", repoFile, repoCache) if err != nil { logrus.Errorf("Failed to create helm client:%v", err) return err } err = helmCmd.UpdateRepo(names) if err != nil { logrus.Errorf("helm update failure: %v", err) return err } return nil } // GetYamlByChart get yaml by chart func (h *HelmAction) GetYamlByChart(chartPath, namespace, name, version string, overrides []string) (string, error) { helmAppYaml, err := GetHelmAppYaml(name, "", version, namespace, chartPath, overrides) if err != nil { return "", errors.Wrap(err, "helm app check failed") } return helmAppYaml, nil } func prepareUploadChartPath(eventID string) (string, error) { basePath := path.Join("/grdata/package_build/temp/events", eventID) files, err := filepath.Glob(path.Join(basePath, "*")) if err != nil { return "", err } if len(files) == 0 { err = storage.Default().StorageCli.DownloadDirToDir(basePath, basePath) if err != nil { return "", err } files, err = filepath.Glob(path.Join(basePath, "*")) if err != nil { return "", err } } if len(files) != 1 { return "", fmt.Errorf("number of files is incorrect, make sure there is only one compressed package") } if strings.HasSuffix(files[0], ".tgz") { err = rutil.UnTar(files[0], basePath, true) if err != nil { return "", err } err = os.RemoveAll(files[0]) if err != nil { return "", err } } files, err = filepath.Glob(path.Join(basePath, "*")) if err != nil { return "", err } if len(files) != 1 { return "", fmt.Errorf("number of files is incorrect, make sure there is only one dir") } chartPath := files[0] s, err := os.Stat(chartPath) if err != nil { return "", err } if !s.IsDir() { return "", fmt.Errorf("upload file not is tgz") } return chartPath, nil } // GetUploadChartPathAndVersion returns the prepared upload chart path and metadata version. func GetUploadChartPathAndVersion(eventID string) (string, string, error) { chartPath, err := prepareUploadChartPath(eventID) if err != nil { return "", "", err } chartLoaded, err := loader.Load(chartPath) if err != nil { logrus.Errorf("load upload helm chart failure: %v", err) return "", "", errors.Wrap(err, "load upload helm chart failure") } version := "" if chartLoaded != nil && chartLoaded.Metadata != nil { version = chartLoaded.Metadata.Version } return chartPath, version, nil } // GetUploadChartInformation - func (h *HelmAction) GetUploadChartInformation(eventID string) ([]apimodel.HelmChartInformation, error) { chartPath, version, err := GetUploadChartPathAndVersion(eventID) if err != nil { return nil, err } chart, err := loader.Load(chartPath) if err != nil { logrus.Errorf("load upload helm chart failure: %v", err) return nil, errors.Wrap(err, "load upload helm chart failure") } var chartInformation []apimodel.HelmChartInformation if chart != nil && chart.Metadata != nil { chartInformation = append(chartInformation, apimodel.HelmChartInformation{ Name: chart.Metadata.Name, Version: version, Keywords: chart.Metadata.Keywords, Pic: chart.Metadata.Icon, Abstract: chart.Metadata.Description, }) } return chartInformation, nil } // CheckUploadChart - func (h *HelmAction) CheckUploadChart(name, version, namespace, eventID string) error { chartPath, chartVersion, err := GetUploadChartPathAndVersion(eventID) if err != nil { return err } if version == "" { version = chartVersion } helmCmd, err := helm.NewHelm(namespace, repoFile, repoCache) if err != nil { return err } _, err = helmCmd.Install(chartPath, name, "", version, []string{}) if err != nil { return err } return nil } // GetUploadChartResource - func (h *HelmAction) GetUploadChartResource(name, version, namespace, eventID string, overrides []string) (interface{}, error) { chartPath, chartVersion, err := GetUploadChartPathAndVersion(eventID) if err != nil { return nil, err } if version == "" { version = chartVersion } helmCmd, err := helm.NewHelm(namespace, repoFile, repoCache) if err != nil { return nil, err } release, err := helmCmd.Install(chartPath, name, "", version, overrides) if err != nil { return nil, err } chartBuildResourceList := handleFileORYamlToObject("upload-chart", []byte(release.Manifest), h.config) appResource := HandleDetailResource(namespace, chartBuildResourceList, false, h.kubeClient, h.mapper) return appResource, nil } // GetUploadChartValue - func (h *HelmAction) GetUploadChartValue(eventID string) (*apimodel.UploadChartValueYaml, error) { chartPath, _, err := GetUploadChartPathAndVersion(eventID) if err != nil { return nil, err } valuePath := path.Join(chartPath, "values.yaml") valueYaml, err := os.ReadFile(valuePath) if err != nil { return nil, err } readmePath := path.Join(chartPath, "README.md") readmeYaml, err := os.ReadFile(readmePath) if err != nil { return &apimodel.UploadChartValueYaml{ Values: map[string]string{"value.yaml": base64.StdEncoding.EncodeToString(valueYaml)}, Readme: "", }, nil } return &apimodel.UploadChartValueYaml{ Values: map[string]string{"values.yaml": base64.StdEncoding.EncodeToString(valueYaml)}, Readme: base64.StdEncoding.EncodeToString(readmeYaml), }, nil }