From b95af3dc79e418a6d33b16835b43b49d958383ba Mon Sep 17 00:00:00 2001 From: Zexi Li Date: Tue, 21 Aug 2018 17:39:55 +0800 Subject: [PATCH] climc: helm api --- cmd/climc/shell/k8s/charts.go | 63 ++++++++ cmd/climc/shell/k8s/helm_list.go | 51 +++++++ cmd/climc/shell/k8s/k8s.go | 3 + cmd/climc/shell/k8s/releases.go | 177 +++++++++++++++++++++++ cmd/climc/shell/k8s/tiller.go | 56 +++++++ pkg/mcclient/modules/k8s/helm_chart.go | 16 ++ pkg/mcclient/modules/k8s/helm_release.go | 23 +++ pkg/mcclient/modules/k8s/helm_tiller.go | 23 +++ 8 files changed, 412 insertions(+) create mode 100644 cmd/climc/shell/k8s/charts.go create mode 100644 cmd/climc/shell/k8s/helm_list.go create mode 100644 cmd/climc/shell/k8s/releases.go create mode 100644 cmd/climc/shell/k8s/tiller.go create mode 100644 pkg/mcclient/modules/k8s/helm_chart.go create mode 100644 pkg/mcclient/modules/k8s/helm_release.go create mode 100644 pkg/mcclient/modules/k8s/helm_tiller.go diff --git a/cmd/climc/shell/k8s/charts.go b/cmd/climc/shell/k8s/charts.go new file mode 100644 index 0000000000..8cb609642a --- /dev/null +++ b/cmd/climc/shell/k8s/charts.go @@ -0,0 +1,63 @@ +package k8s + +import ( + "fmt" + + json "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modules/k8s" +) + +func initChart() { + cmdN := func(suffix string) string { + return resourceCmdN("chart", suffix) + } + + type listOpt struct { + baseListOptions + Name string `help:"Chart name"` + Repo string `help:"Repository name"` + RepoUrl string `help:"Repository url"` + AllVersion bool `json:"Get Chart all history versions"` + Keyword string `json:"Chart keyword"` + } + R(&listOpt{}, cmdN("list"), "List k8s helm global charts", func(s *mcclient.ClientSession, args *listOpt) error { + params := fetchPagingParams(args.baseListOptions) + if len(args.Name) != 0 { + params.Add(json.NewString(args.Name), "name") + } + if len(args.Repo) != 0 { + params.Add(json.NewString(args.Repo), "repo") + } + if len(args.RepoUrl) != 0 { + params.Add(json.NewString(args.RepoUrl), "repo_url") + } + if args.AllVersion { + params.Add(json.JSONTrue, "all_version") + } + if len(args.Keyword) != 0 { + params.Add(json.NewString(args.Keyword), "keyword") + } + charts, err := k8s.Charts.List(s, params) + if err != nil { + return err + } + PrintHelmListResult(charts, ChartPrinter{}) + return nil + }) +} + +type ChartPrinter struct{} + +func (p ChartPrinter) Columns() []interface{} { + return []interface{}{"REPO/NAME", "VERSION", "DESCRIPTION"} +} + +func (p ChartPrinter) RowKeys(obj *json.JSONDict) []interface{} { + repo, _ := obj.GetString("repo") + name, _ := obj.GetString("chart", "metadata", "name") + version, _ := obj.GetString("chart", "metadata", "version") + desc, _ := obj.GetString("chart", "metadata", "description") + return []interface{}{fmt.Sprintf("%s/%s", repo, name), version, desc} +} diff --git a/cmd/climc/shell/k8s/helm_list.go b/cmd/climc/shell/k8s/helm_list.go new file mode 100644 index 0000000000..df1055a29e --- /dev/null +++ b/cmd/climc/shell/k8s/helm_list.go @@ -0,0 +1,51 @@ +package k8s + +import ( + "fmt" + + "github.com/gosuri/uitable" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient/modules" +) + +type ListPrinter interface { + Columns() []interface{} + RowKeys(obj *jsonutils.JSONDict) []interface{} +} + +func ListerTable(res *modules.ListResult, printer ListPrinter) *uitable.Table { + min := func(x, y int) int { + if x < y { + return x + } + return y + } + table := uitable.New() + table.MaxColWidth = 80 + table.AddRow(printer.Columns()...) + var idx int + for ; idx < min(res.Limit, res.Total); idx++ { + table.AddRow(printer.RowKeys(res.Data[idx].(*jsonutils.JSONDict))...) + } + return table +} + +func PrintHelmListResult(res *modules.ListResult, printer ListPrinter) { + fmt.Println(ListerTable(res, printer)) + + table := uitable.New() + total := res.Total + offset := res.Offset + limit := res.Limit + page := (offset / limit) + 1 + pages := total / limit + if pages*limit < total { + pages += 1 + } + table.AddRow("") + table.AddRow("Total", "Pages", "Limit", "Offset", "Page") + table.AddRow(total, pages, limit, offset, page) + fmt.Println(table) +} diff --git a/cmd/climc/shell/k8s/k8s.go b/cmd/climc/shell/k8s/k8s.go index af4892e71a..d7b46f482a 100644 --- a/cmd/climc/shell/k8s/k8s.go +++ b/cmd/climc/shell/k8s/k8s.go @@ -17,7 +17,10 @@ func init() { initNode() // helm resources + initTiller() initRepo() + initChart() + initRelease() // kubernetes original resources initRaw() diff --git a/cmd/climc/shell/k8s/releases.go b/cmd/climc/shell/k8s/releases.go new file mode 100644 index 0000000000..179fb199b4 --- /dev/null +++ b/cmd/climc/shell/k8s/releases.go @@ -0,0 +1,177 @@ +package k8s + +import ( + "fmt" + "io/ioutil" + + json "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modules/k8s" +) + +func initRelease() { + cmdN := func(suffix string) string { + return resourceCmdN("release", suffix) + } + type listOpt struct { + namespaceListOptions + baseListOptions + Filter string `help:"Filter, split by space"` + Admin bool `help:"Admin to show all namespace releases"` + Deployed bool `help:"Show deployed status releases"` + Deleted bool `help:"Show deleted status releases"` + Deleting bool `help:"Show deleting status releases"` + Failed bool `help:"Show failed status releases"` + Superseded bool `help:"Show superseded status releases"` + Pending bool `help:"Show pending status releases"` + } + R(&listOpt{}, cmdN("list"), "List k8s cluster helm releases", func(s *mcclient.ClientSession, args *listOpt) error { + params := fetchNamespaceParams(args.namespaceListOptions) + params.Update(fetchPagingParams(args.baseListOptions)) + if args.Filter != "" { + params.Add(json.NewString(args.Filter), "filter") + } + if args.Namespace != "" { + params.Add(json.NewString(args.Namespace), "namespace") + } + params.Add(json.JSONTrue, "all") + if args.Admin { + params.Add(json.JSONTrue, "admin") + } + if args.Deployed { + params.Add(json.JSONTrue, "deployed") + } + if args.Deleted { + params.Add(json.JSONTrue, "deleted") + } + if args.Deleting { + params.Add(json.JSONTrue, "deleting") + } + if args.Failed { + params.Add(json.JSONTrue, "failed") + } + if args.Superseded { + params.Add(json.JSONTrue, "superseded") + } + if args.Pending { + params.Add(json.JSONTrue, "pending") + } + ret, err := k8s.Releases.ListInContexts(s, params, args.ClusterContext()) + if err != nil { + return err + } + printList(ret, k8s.Releases.GetColumns(s)) + return nil + }) + + type showOpt struct { + clusterBaseOptions + NAME string `help:"Release instance name"` + } + R(&showOpt{}, cmdN("show"), "Get helm release details", func(s *mcclient.ClientSession, args *showOpt) error { + ret, err := k8s.Releases.GetInContexts(s, args.NAME, nil, args.ClusterContext()) + if err != nil { + return err + } + resources, err := ret.GetString("info", "status", "resources") + if err != nil { + return err + } + fmt.Println(resources) + return nil + }) + + type releaseCUOpts struct { + Values string `help:"Specify values in a YAML file (can specify multiple)" short-token:"f"` + Version string `help:"Specify the exact chart version to install. If not specified, latest version installed"` + //Set []string `help:"set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)"` + DryRun bool `help:"Simulate an install"` + Details bool `help:"Show release deploy details, include kubernetes created resources"` + Timeout int64 `help:"Time in seconds to wait for any individual kubernetes operation (like Jobs for hooks)" default:"600"` + } + releaseCUDict := func(args releaseCUOpts) (*json.JSONDict, error) { + params := json.NewDict() + if args.Version != "" { + params.Add(json.NewString(args.Version), "version") + } + if args.DryRun { + params.Add(json.JSONTrue, "dry_run") + } + params.Add(json.NewInt(args.Timeout), "timeout") + if args.Values != "" { + //vals, err := helm.MergeValuesF(args.Values, args.Set, []string{}) + vals, err := ioutil.ReadFile(args.Values) + if err != nil { + return nil, err + } + params.Add(json.NewString(string(vals)), "values") + } + return params, nil + } + + type releaseCreateOpts struct { + namespaceOptions + releaseCUOpts + Name string `help:"Release name, If unspecified, it will autogenerate one for you"` + CHARTNAME string `help:"Helm chart name, e.g stable/etcd"` + } + R(&releaseCreateOpts{}, cmdN("create"), "Create release with specified helm chart", func(s *mcclient.ClientSession, args *releaseCreateOpts) error { + params, err := releaseCUDict(args.releaseCUOpts) + if err != nil { + return err + } + params.Add(json.NewString(args.CHARTNAME), "chart_name") + if args.Namespace != "" { + params.Add(json.NewString(args.Namespace), "namespace") + } + if args.Name != "" { + params.Add(json.NewString(args.Name), "release_name") + } + ret, err := k8s.Releases.CreateInContexts(s, params, args.ClusterContext()) + if err != nil { + return err + } + printObject(ret) + return nil + }) + + type releaseUpgradeOpts struct { + clusterBaseOptions + releaseCUOpts + NAME string `help:"Release instance name"` + CHARTNAME string `help:"Helm chart name, e.g stable/etcd"` + ReuseValues bool `help:"When upgrading, reuse the last release's values, and merge in any new values. If '--reset-values' is specified, this is ignored"` + ResetValues bool `help:"When upgrading, reset the values to the ones built into the chart"` + } + R(&releaseUpgradeOpts{}, cmdN("upgrade"), "Upgrade release", func(s *mcclient.ClientSession, args *releaseUpgradeOpts) error { + params, err := releaseCUDict(args.releaseCUOpts) + if err != nil { + return err + } + params.Add(json.NewString(args.CHARTNAME), "chart_name") + params.Add(json.NewString(args.NAME), "release_name") + if args.ReuseValues { + params.Add(json.JSONTrue, "reuse_values") + } + if args.ResetValues { + params.Add(json.JSONTrue, "reset_values") + } + + res, err := k8s.Releases.PutInContexts(s, args.NAME, params, args.ClusterContext()) + if err != nil { + return err + } + printObject(res) + return nil + }) + + type deleteOpt struct { + clusterBaseOptions + NAME string `help:"Release instance name"` + } + R(&deleteOpt{}, cmdN("delete"), "Delete release", func(s *mcclient.ClientSession, args *deleteOpt) error { + _, err := k8s.Releases.DeleteInContexts(s, args.NAME, nil, args.ClusterContext()) + return err + }) +} diff --git a/cmd/climc/shell/k8s/tiller.go b/cmd/climc/shell/k8s/tiller.go new file mode 100644 index 0000000000..247e5fba13 --- /dev/null +++ b/cmd/climc/shell/k8s/tiller.go @@ -0,0 +1,56 @@ +package k8s + +import ( + json "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modules/k8s" +) + +func initTiller() { + cmdN := func(suffix string) string { + return resourceCmdN("tiller", suffix) + } + type createOpt struct { + clusterBaseOptions + KubeContext string `json:"kube_context"` + Namespace string `json:"namespace" default:"kube-system"` + // Upgrade if Tiller is already installed + Upgrade bool `json:"upgrade"` + // Name of service account + ServiceAccount string `json:"service_account" default:"tiller"` + // Use the canary Tiller image + Canary bool `json:"canary_image"` + + // Override Tiller image + Image string `json:"tiller_image" default:"yunion/tiller:v2.9.0"` + // Limit the maximum number of revisions saved per release. Use 0 for no limit. + MaxHistory int `json:"history_max"` + } + R(&createOpt{}, cmdN("create"), "Install helm tiller server to Kubernetes cluster", func(s *mcclient.ClientSession, args *createOpt) error { + params := json.NewDict() + if len(args.KubeContext) > 0 { + params.Add(json.NewString(args.KubeContext), "kube_context") + } + params.Add(json.NewString(args.Namespace), "namespace") + params.Add(json.NewString(args.ServiceAccount), "service_account") + if args.Canary { + params.Add(json.JSONTrue, "canary_image") + } + if args.Upgrade { + params.Add(json.JSONTrue, "upgrade") + } + if len(args.Image) > 0 { + params.Add(json.NewString(args.Image), "tiller_image") + } + if args.MaxHistory > 0 { + params.Add(json.NewInt(int64(args.MaxHistory)), "history_max") + } + ret, err := k8s.Tiller.CreateInContexts(s, params, args.ClusterContext()) + if err != nil { + return err + } + printObject(ret) + return nil + }) +} diff --git a/pkg/mcclient/modules/k8s/helm_chart.go b/pkg/mcclient/modules/k8s/helm_chart.go new file mode 100644 index 0000000000..522628793f --- /dev/null +++ b/pkg/mcclient/modules/k8s/helm_chart.go @@ -0,0 +1,16 @@ +package k8s + +import ( + "yunion.io/x/onecloud/pkg/mcclient/modules" +) + +var ( + Charts *modules.ResourceManager +) + +func init() { + Charts = NewManager("chart", "charts", + NewResourceCols(), + NewColumns()) + modules.Register(Charts) +} diff --git a/pkg/mcclient/modules/k8s/helm_release.go b/pkg/mcclient/modules/k8s/helm_release.go new file mode 100644 index 0000000000..ef254c0a45 --- /dev/null +++ b/pkg/mcclient/modules/k8s/helm_release.go @@ -0,0 +1,23 @@ +package k8s + +import ( + "yunion.io/x/onecloud/pkg/mcclient/modules" +) + +var ( + Releases *ReleaseManager +) + +type ReleaseManager struct { + modules.ResourceManager +} + +func init() { + Releases = &ReleaseManager{ + ResourceManager: *NewManager( + "release", "releases", + NewNamespaceCols(""), + NewColumns(), + )} + modules.Register(Releases) +} diff --git a/pkg/mcclient/modules/k8s/helm_tiller.go b/pkg/mcclient/modules/k8s/helm_tiller.go new file mode 100644 index 0000000000..ddb442561f --- /dev/null +++ b/pkg/mcclient/modules/k8s/helm_tiller.go @@ -0,0 +1,23 @@ +package k8s + +import ( + "yunion.io/x/onecloud/pkg/mcclient/modules" +) + +var ( + Tiller *TillerManager +) + +type TillerManager struct { + modules.ResourceManager +} + +func init() { + Tiller = &TillerManager{ + ResourceManager: *NewManager( + "tiller", "tiller", + NewColumns(), + NewColumns(), + )} + modules.Register(Tiller) +}