From d3f70d4ff24f8368344779894cb34b980ca19e1c Mon Sep 17 00:00:00 2001 From: chenmin Date: Wed, 10 Jun 2020 19:10:51 +0800 Subject: [PATCH] init --- .vscode/sftp.json | 15 +++ conf/app.ini | 7 ++ main.go | 26 +++++ models/cluster.go | 23 ++++ models/models.go | 39 +++++++ setting/setting.go | 37 ++++++ util/file.go | 13 +++ util/router.go | 30 +++++ views/charts/chats.go | 255 ++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 445 insertions(+) create mode 100644 .vscode/sftp.json create mode 100644 conf/app.ini create mode 100644 main.go create mode 100644 models/cluster.go create mode 100644 models/models.go create mode 100644 setting/setting.go create mode 100644 util/file.go create mode 100644 util/router.go create mode 100644 views/charts/chats.go diff --git a/.vscode/sftp.json b/.vscode/sftp.json new file mode 100644 index 0000000..1476718 --- /dev/null +++ b/.vscode/sftp.json @@ -0,0 +1,15 @@ +{ + "name": "My Server", + "host": "172.16.90.230", + "protocol": "sftp", + "port": 22, + "username": "root", + "password": "@cm10086", + "remotePath": "/data/go/src/helm-api", + "syncMode": "update", + "ignore": [ + "**/.vscode/**", + "**/.git/**" + ], + "uploadOnSave": true +} diff --git a/conf/app.ini b/conf/app.ini new file mode 100644 index 0000000..5644e0a --- /dev/null +++ b/conf/app.ini @@ -0,0 +1,7 @@ +[database] +Type = mysql +User = root +Password = test123 +Host = 127.0.0.1:3306 +Name = nvwa +TablePrefix = kube_ \ No newline at end of file diff --git a/main.go b/main.go new file mode 100644 index 0000000..573868f --- /dev/null +++ b/main.go @@ -0,0 +1,26 @@ +package main + +import "github.com/gin-gonic/gin" + +import "helm-api/views/charts" +import "helm-api/setting" +import "helm-api/models" + +func init() { + setting.Setup() + models.Setup() +} + +func main() { + r := gin.Default() + r.POST("/charts/:cluster/:namespace/:name", charts.Update) + r.GET("/charts/:cluster/:namespace/", charts.List) + r.DELETE("/charts/:cluster/:namespace/:name", charts.Delete) + r.GET("/charts/:cluster/:namespace/:name", charts.Retrieve) + r.GET("/ping", func(c *gin.Context) { + c.JSON(200, gin.H{ + "message": "pong", + }) + }) + r.Run(":10000") +} \ No newline at end of file diff --git a/models/cluster.go b/models/cluster.go new file mode 100644 index 0000000..4d819a9 --- /dev/null +++ b/models/cluster.go @@ -0,0 +1,23 @@ +package models + +import ( + // "fmt" + + "github.com/jinzhu/gorm" +) + +type Cluster struct { + ID int `json:"id"` + Name string `json:"name"` + Config string `json:"config"` +} + +// GetArticle Get a single article based on ID +func GetCluster(id int) (*Cluster, error) { + var cluster Cluster + err := db.Where("id = ?", id).First(&cluster).Error + if err != nil && err != gorm.ErrRecordNotFound { + return nil, err + } + return &cluster, nil +} diff --git a/models/models.go b/models/models.go new file mode 100644 index 0000000..fd4b052 --- /dev/null +++ b/models/models.go @@ -0,0 +1,39 @@ +package models + +import ( + "fmt" + + "github.com/jinzhu/gorm" + _ "github.com/jinzhu/gorm/dialects/mysql" + + "helm-api/setting" +) + +var db *gorm.DB + +func Setup () { + var err error + db, err = gorm.Open(setting.DatabaseSetting.Type, fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=True&loc=Local", + setting.DatabaseSetting.User, + setting.DatabaseSetting.Password, + setting.DatabaseSetting.Host, + setting.DatabaseSetting.Name)) + + if err != nil { + fmt.Println(err) + return + }else { + fmt.Println("connection succedssed") + } + gorm.DefaultTableNameHandler = func(db *gorm.DB, defaultTableName string) string { + return setting.DatabaseSetting.TablePrefix + defaultTableName + } + + db.SingularTable(true) + db.DB().SetMaxIdleConns(10) + db.DB().SetMaxOpenConns(100) +} + +func CloseDB() { + defer db.Close() +} diff --git a/setting/setting.go b/setting/setting.go new file mode 100644 index 0000000..4e5621a --- /dev/null +++ b/setting/setting.go @@ -0,0 +1,37 @@ +package setting + +import ( + "log" + + "github.com/go-ini/ini" +) + +type Database struct { + Type string + User string + Password string + Host string + Name string + TablePrefix string +} + +var DatabaseSetting = &Database{} + +var cfg *ini.File + +func Setup() { + var err error + cfg, err = ini.Load("conf/app.ini") + if err != nil { + log.Fatalf("setting.Setup, fail to parse 'conf/app.ini': %v", err) + } + mapTo("database", DatabaseSetting) +} + +// mapTo map section +func mapTo(section string, v interface{}) { + err := cfg.Section(section).MapTo(v) + if err != nil { + log.Fatalf("Cfg.MapTo %s err: %v", section, err) + } +} diff --git a/util/file.go b/util/file.go new file mode 100644 index 0000000..d6b68c0 --- /dev/null +++ b/util/file.go @@ -0,0 +1,13 @@ +package util + +import ( + "fmt" + "io/ioutil" +) + +func WriteFile(name, content string) { + data := []byte(content) + if ioutil.WriteFile(name,data,0644) == nil { + fmt.Println("写入文件成功: ", name) + } +} \ No newline at end of file diff --git a/util/router.go b/util/router.go new file mode 100644 index 0000000..a4cd0b7 --- /dev/null +++ b/util/router.go @@ -0,0 +1,30 @@ +package util + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/golang/glog" +) + +type respBody struct { + Code int `json:"code"` // 0 or 1, 0 is ok, 1 is error + Data interface{} `json:"data,omitempty"` + Error string `json:"error,omitempty"` +} + +func RespErr(c *gin.Context, err error) { + glog.Warningln(err) + + c.JSON(http.StatusOK, &respBody{ + Code: 1, + Error: err.Error(), + }) +} + +func RespOK(c *gin.Context, data interface{}) { + c.JSON(http.StatusOK, &respBody{ + Code: 0, + Data: data, + }) +} diff --git a/views/charts/chats.go b/views/charts/chats.go new file mode 100644 index 0000000..2e15196 --- /dev/null +++ b/views/charts/chats.go @@ -0,0 +1,255 @@ +package charts + +import ( + "os" + "io" + "strconv" + "fmt" + + + "github.com/golang/glog" + "github.com/pkg/errors" + "github.com/unknwon/com" + "github.com/gin-gonic/gin" + "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/release" + "helm.sh/helm/v3/pkg/cli" + "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/chart/loader" + "helm.sh/helm/v3/pkg/downloader" + "helm.sh/helm/v3/pkg/getter" + "helm.sh/helm/v3/pkg/strvals" + "sigs.k8s.io/yaml" + + "helm-api/util" + "helm-api/models" +) + +type releaseElement struct { + Name string `json:"name"` + Namespace string `json:"namespace"` + Revision string `json:"revision"` + Updated string `json:"updated"` + Status string `json:"status"` + Chart string `json:"chart"` + ChartVersion string `json:"chart_version"` + AppVersion string `json:"app_version"` + + Notes string `json:"notes,omitempty"` + + // TODO: Test Suite? +} + +type releaseOptions struct { + Values string `json:"values"` + SetValues []string `json:"set"` + SetStringValues []string `json:"set_string"` +} + +var settings = cli.New() + +func actionConfigInit(cluster int, namespace string) (action.Configuration, error) { + actionConfig := new(action.Configuration) + kubeCluster := new(models.Cluster) + kubeCluster, err := models.GetCluster(cluster) + if err != nil { + glog.Errorf("%+v", err) + return *actionConfig, err + } + kubeConfig := "/tmp/config" + kubeContext := kubeCluster.Config + util.WriteFile(kubeConfig, kubeContext) + settings.KubeConfig = kubeConfig + err = actionConfig.Init(settings.RESTClientGetter(), namespace, os.Getenv("HELM_DRIVER"), glog.Infof) + if err != nil { + glog.Errorf("%+v", err) + return *actionConfig, err + } + + return *actionConfig, nil +} + +func List(c *gin.Context) { + namespace := c.Param("namespace") + cluster := com.StrTo(c.Param("cluster")).MustInt() + actionConfig, err := actionConfigInit(cluster, namespace) + if err != nil { + util.RespErr(c, err) + return + } + client := action.NewList(&actionConfig) + client.Deployed = true + results, err := client.Run() + if err != nil { + util.RespErr(c, err) + return + } + + // Initialize the array so no results returns an empty array instead of null + elements := make([]releaseElement, 0, len(results)) + for _, r := range results { + elements = append(elements, constructReleaseElement(r, false)) + } + util.RespOK(c, elements) +} + +func constructReleaseElement(r *release.Release, showStatus bool) releaseElement { + element := releaseElement{ + Name: r.Name, + Namespace: r.Namespace, + Revision: strconv.Itoa(r.Version), + Status: r.Info.Status.String(), + Chart: r.Chart.Metadata.Name, + ChartVersion: r.Chart.Metadata.Version, + AppVersion: r.Chart.Metadata.AppVersion, + } + if showStatus { + element.Notes = r.Info.Notes + } + t := "-" + if tspb := r.Info.LastDeployed; !tspb.IsZero() { + t = tspb.String() + } + element.Updated = t + + return element +} + +func Retrieve(c *gin.Context) { + namespace := c.Param("namespace") + name := c.Param("name") + cluster := com.StrTo(c.Param("cluster")).MustInt() + actionConfig, err := actionConfigInit(cluster, namespace) + if err != nil { + util.RespErr(c, err) + return + } + client := action.NewGet(&actionConfig) + results, err := client.Run(name) + if err != nil { + util.RespErr(c, err) + return + } + util.RespOK(c, results) +} + +func mergeValues(options releaseOptions) (map[string]interface{}, error) { + vals := map[string]interface{}{} + err := yaml.Unmarshal([]byte(options.Values), &vals) + if err != nil { + return vals, fmt.Errorf("failed parsing values") + } + + for _, value := range options.SetValues { + if err := strvals.ParseInto(value, vals); err != nil { + return vals, fmt.Errorf("failed parsing set data") + } + } + + for _, value := range options.SetStringValues { + if err := strvals.ParseIntoString(value, vals); err != nil { + return vals, fmt.Errorf("failed parsing set_string data") + } + } + + return vals, nil +} + + +func Update(c *gin.Context) { + namespace := c.Param("namespace") + name := c.Param("name") + chart := c.Query("chart") + cluster := com.StrTo(c.Param("cluster")).MustInt() + var options releaseOptions + err := c.BindJSON(&options) + if err != nil && err != io.EOF { + util.RespErr(c, err) + return + } + actionConfig, err := actionConfigInit(cluster, namespace) + if err != nil { + util.RespErr(c, err) + return + } + vals, err := mergeValues(options) + if err != nil { + util.RespErr(c, err) + return + } + client := action.NewInstall(&actionConfig) + rel, err := runInstall(name, chart, client, vals) + if err != nil { + util.RespErr(c, err) + return + } + util.RespOK(c, rel) +} + +func isChartInstallable(ch *chart.Chart) (bool, error) { + switch ch.Metadata.Type { + case "", "application": + return true, nil + } + return false, errors.Errorf("%s charts are not installable", ch.Metadata.Type) +} + +func runInstall(name string, chart string, client *action.Install, vals map[string]interface{}) (*release.Release, error) { + client.ReleaseName = name + + cp, err := client.ChartPathOptions.LocateChart(chart, settings) + if err != nil { + return nil, err + } + + p := getter.All(settings) + // Check chart dependencies to make sure all are present in /charts + chartRequested, err := loader.Load(cp) + if err != nil { + return nil, err + } + + validInstallableChart, err := isChartInstallable(chartRequested) + if !validInstallableChart { + return nil, err + } + + // if chartRequested.Metadata.Deprecated { + // fmt.Fprintln("WARNING: This chart is deprecated") + // } + + if req := chartRequested.Metadata.Dependencies; req != nil { + // If CheckDependencies returns an error, we have unfulfilled dependencies. + // As of Helm 2.4.0, this is treated as a stopping condition: + // https://github.com/helm/helm/issues/2209 + if err := action.CheckDependencies(chartRequested, req); err != nil { + if client.DependencyUpdate { + man := &downloader.Manager{ + ChartPath: cp, + Keyring: client.ChartPathOptions.Keyring, + SkipUpdate: false, + Getters: p, + RepositoryConfig: settings.RepositoryConfig, + RepositoryCache: settings.RepositoryCache, + Debug: settings.Debug, + } + if err := man.Update(); err != nil { + return nil, err + } + // Reload the chart with the updated Chart.lock file. + if chartRequested, err = loader.Load(cp); err != nil { + return nil, errors.Wrap(err, "failed reloading chart after repo update") + } + } else { + return nil, err + } + } + } + client.Namespace = settings.Namespace() + return client.Run(chartRequested, vals) +} + + +func Delete(c *gin.Context) { + +} \ No newline at end of file