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"
)
type AppTemplate struct {
Name string
Versions hrepo.ChartVersions
}
type HelmAction struct {
ctx context.Context
kubeClient *kubernetes.Clientset
rainbondClient versioned.Interface
repo *helm.Repo
config *rest.Config
mapper meta.RESTMapper
}
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")
)
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
}
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
}
func (h *HelmAction) UpdateHelmRepo(names string) error {
err := UpdateRepo(names)
if err != nil {
return errors.Wrap(err, "helm repo update failed")
}
return nil
}
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
}
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
}
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
}
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
}
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
}
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
}
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
}
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
}
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
}