From d80d9f13b7051fcc8954bfe02205ee4d6f8f4fc7 Mon Sep 17 00:00:00 2001 From: Qiu Jian Date: Sun, 9 May 2021 01:27:46 +0800 Subject: [PATCH] fix: init cloud shell by apigateway --- build/climc/root/root/.bashrc | 3 + build/docker/Dockerfile.climc | 4 +- build/docker/Dockerfile.climc-base | 4 +- build/docker/Makefile | 4 ++ cmd/climc/shell/compute/webconsole.go | 12 ++-- pkg/apigateway/handler/init.go | 1 + pkg/apis/webconsole/k8s.go | 28 ++++++++ pkg/mcclient/modules/webconsole/doc.go | 1 + .../{ => webconsole}/mod_webconsole.go | 67 ++++++++++++++++++- pkg/webconsole/command/kube_command.go | 20 +++++- pkg/webconsole/handlers.go | 33 +++++---- 11 files changed, 152 insertions(+), 25 deletions(-) create mode 100644 build/climc/root/root/.bashrc create mode 100644 pkg/apis/webconsole/k8s.go create mode 100644 pkg/mcclient/modules/webconsole/doc.go rename pkg/mcclient/modules/{ => webconsole}/mod_webconsole.go (53%) diff --git a/build/climc/root/root/.bashrc b/build/climc/root/root/.bashrc new file mode 100644 index 0000000000..a726941823 --- /dev/null +++ b/build/climc/root/root/.bashrc @@ -0,0 +1,3 @@ +echo "Welcome to Cloud Shell :-) You may execute climc and other command tools in this shell." +echo "Please exec 'climc' to get started" +echo "" diff --git a/build/docker/Dockerfile.climc b/build/docker/Dockerfile.climc index 198ed5ad9f..fa5b0da53b 100644 --- a/build/docker/Dockerfile.climc +++ b/build/docker/Dockerfile.climc @@ -1,5 +1,7 @@ -FROM registry.cn-beijing.aliyuncs.com/yunionio/climc-base:20200820 +FROM registry.cn-beijing.aliyuncs.com/yunionio/climc-base:20210508 ADD ./build/climc/root/opt /opt +ADD ./build/climc/root/root /root + ADD ./_output/alpine-build/bin/climc ./_output/alpine-build/bin/*cli /opt/yunion/bin/ diff --git a/build/docker/Dockerfile.climc-base b/build/docker/Dockerfile.climc-base index 2175c275dc..98ca03acc6 100644 --- a/build/docker/Dockerfile.climc-base +++ b/build/docker/Dockerfile.climc-base @@ -6,7 +6,9 @@ ENV TZ Asia/Shanghai RUN mkdir -p /opt/yunion/bin -RUN apk add --no-cache bash bash-completion tzdata ca-certificates vim && \ +RUN echo http://dl-cdn.alpinelinux.org/alpine/edge/testing >>/etc/apk/repositories + +RUN apk add --no-cache bash bash-completion tzdata ca-certificates vim ipmitool kubectl && \ rm -rf /var/cache/apk/* RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime diff --git a/build/docker/Makefile b/build/docker/Makefile index 22c1ec6485..cd49fec1f1 100644 --- a/build/docker/Makefile +++ b/build/docker/Makefile @@ -1,3 +1,7 @@ debian10-base: docker buildx build --platform linux/arm64,linux/amd64 --push \ -t registry.cn-beijing.aliyuncs.com/yunionio/debian10-base:1.0 -f ./Dockerfile.debian-base . + +climc-base: + docker buildx build --platform linux/arm64,linux/amd64 --push \ + -t registry.cn-beijing.aliyuncs.com/yunionio/climc-base:$(VERSION) -f ./Dockerfile.climc-base . diff --git a/cmd/climc/shell/compute/webconsole.go b/cmd/climc/shell/compute/webconsole.go index 0d82bf8b83..57ba4e10d4 100644 --- a/cmd/climc/shell/compute/webconsole.go +++ b/cmd/climc/shell/compute/webconsole.go @@ -24,7 +24,7 @@ import ( webconsole_api "yunion.io/x/onecloud/pkg/apis/webconsole" "yunion.io/x/onecloud/pkg/mcclient" - "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/mcclient/modules/webconsole" o "yunion.io/x/onecloud/pkg/mcclient/options" "yunion.io/x/onecloud/pkg/webconsole/command" ) @@ -70,7 +70,7 @@ func init() { if err != nil { return err } - ret, err := modules.WebConsole.DoK8sShellConnect(s, args.NAME, params) + ret, err := webconsole.WebConsole.DoK8sShellConnect(s, args.NAME, params) if err != nil { return err } @@ -83,7 +83,7 @@ func init() { if err != nil { return err } - ret, err := modules.WebConsole.DoK8sLogConnect(s, args.NAME, params) + ret, err := webconsole.WebConsole.DoK8sLogConnect(s, args.NAME, params) if err != nil { return err } @@ -92,7 +92,7 @@ func init() { }) R(&o.WebConsoleBaremetalOptions{}, "webconsole-baremetal", "Connect baremetal host webconsole", func(s *mcclient.ClientSession, args *o.WebConsoleBaremetalOptions) error { - ret, err := modules.WebConsole.DoBaremetalConnect(s, args.ID, nil) + ret, err := webconsole.WebConsole.DoBaremetalConnect(s, args.ID, nil) if err != nil { return err } @@ -105,7 +105,7 @@ func init() { if err != nil { return err } - ret, err := modules.WebConsole.DoSshConnect(s, args.IP, params) + ret, err := webconsole.WebConsole.DoSshConnect(s, args.IP, params) if err != nil { return err } @@ -114,7 +114,7 @@ func init() { }) R(&o.WebConsoleServerOptions{}, "webconsole-server", "Connect server remote graphic console", func(s *mcclient.ClientSession, args *o.WebConsoleServerOptions) error { - ret, err := modules.WebConsole.DoServerConnect(s, args.ID, nil) + ret, err := webconsole.WebConsole.DoServerConnect(s, args.ID, nil) if err != nil { return err } diff --git a/pkg/apigateway/handler/init.go b/pkg/apigateway/handler/init.go index d55a9cd410..c5c8a16a65 100644 --- a/pkg/apigateway/handler/init.go +++ b/pkg/apigateway/handler/init.go @@ -22,6 +22,7 @@ import ( "yunion.io/x/onecloud/pkg/mcclient/modules/k8s" _ "yunion.io/x/onecloud/pkg/mcclient/modules/monitor" _ "yunion.io/x/onecloud/pkg/mcclient/modules/notify" + _ "yunion.io/x/onecloud/pkg/mcclient/modules/webconsole" _ "yunion.io/x/onecloud/pkg/mcclient/modules/yunionconf" ) diff --git a/pkg/apis/webconsole/k8s.go b/pkg/apis/webconsole/k8s.go new file mode 100644 index 0000000000..87ae8301d1 --- /dev/null +++ b/pkg/apis/webconsole/k8s.go @@ -0,0 +1,28 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package webconsole + +type SK8sRequest struct { + Cluster string `json:"cluster"` + Namespace string `json:"namespace"` + Container string `json:"container"` +} + +type SK8sShellRequest struct { + SK8sRequest + + Command string `json:"command"` + Env map[string]string `json:"env"` +} diff --git a/pkg/mcclient/modules/webconsole/doc.go b/pkg/mcclient/modules/webconsole/doc.go new file mode 100644 index 0000000000..ac47e0e9b2 --- /dev/null +++ b/pkg/mcclient/modules/webconsole/doc.go @@ -0,0 +1 @@ +package webconsole // import "yunion.io/x/onecloud/pkg/mcclient/modules/webconsole" diff --git a/pkg/mcclient/modules/mod_webconsole.go b/pkg/mcclient/modules/webconsole/mod_webconsole.go similarity index 53% rename from pkg/mcclient/modules/mod_webconsole.go rename to pkg/mcclient/modules/webconsole/mod_webconsole.go index 963ea9c3e2..7af4495629 100644 --- a/pkg/mcclient/modules/mod_webconsole.go +++ b/pkg/mcclient/modules/webconsole/mod_webconsole.go @@ -12,15 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -package modules +package webconsole import ( "fmt" "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + webconsole_api "yunion.io/x/onecloud/pkg/apis/webconsole" + "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/auth" "yunion.io/x/onecloud/pkg/mcclient/modulebase" + "yunion.io/x/onecloud/pkg/mcclient/modules/k8s" ) var ( @@ -30,7 +35,7 @@ var ( func init() { WebConsole = WebConsoleManager{NewWebConsoleManager()} - register(&WebConsole) + modulebase.Register("v1", &WebConsole) } type WebConsoleManager struct { @@ -64,6 +69,64 @@ func (m WebConsoleManager) DoK8sShellConnect(s *mcclient.ClientSession, id strin return m.DoK8sConnect(s, id, "shell", params) } +func (m WebConsoleManager) DoCloudShell(s *mcclient.ClientSession, _ jsonutils.JSONObject) (jsonutils.JSONObject, error) { + adminSession := auth.GetAdminSession(s.GetContext(), s.GetRegion(), "") + + query := jsonutils.NewDict() + query.Add(jsonutils.JSONTrue, "system") + query.Add(jsonutils.NewString("system"), "scope") + query.Add(jsonutils.NewString("system-default"), "name") + clusters, err := k8s.KubeClusters.List(adminSession, query) + if err != nil { + return nil, errors.Wrap(err, "KubeClusters") + } + if len(clusters.Data) == 0 { + return nil, httperrors.NewNotFoundError("cluster system-default not found") + } + clusterId, _ := clusters.Data[0].GetString("id") + if len(clusterId) == 0 { + return nil, httperrors.NewNotFoundError("cluster system-default no id") + } + + query = jsonutils.NewDict() + query.Add(jsonutils.NewString(clusterId), "cluster") + query.Add(jsonutils.NewString("onecloud"), "namespace") + query.Add(jsonutils.NewString("climc"), "search") + pods, err := k8s.Pods.List(adminSession, query) + if err != nil { + return nil, errors.Wrap(err, "Pods") + } + if len(pods.Data) == 0 { + return nil, httperrors.NewNotFoundError("pod climc not found") + } + podName, _ := pods.Data[0].GetString("name") + if len(podName) == 0 { + return nil, httperrors.NewNotFoundError("pod climc no name") + } + + req := webconsole_api.SK8sShellRequest{} + req.Cluster = clusterId + req.Namespace = "onecloud" + req.Container = "climc" + req.Command = "/bin/bash" + req.Env = map[string]string{ + "OS_AUTH_TOKEN": s.GetToken().GetTokenString(), + "OS_PROJECT_NAME": s.GetProjectName(), + "OS_PROJECT_DOMAIN": s.GetProjectDomain(), + "OS_AUTH_URL": "https://default-keystone:30500/v3", + "OS_ENDPOINT_TYPE": "internal", + "YUNION_USE_CACHED_TOKEN": "false", + "YUNION_INSECURE": "true", + "OS_USERNAME": "", + "OS_PASSWORD": "", + "OS_DOMAIN_NAME": "", + "OS_ACCESS_KEY": "", + "OS_SECRET_KEY": "", + "OS_TRY_TERM_WIDTH": "false", + } + return m.DoK8sConnect(s, podName, "shell", jsonutils.Marshal(req)) +} + func (m WebConsoleManager) DoK8sLogConnect(s *mcclient.ClientSession, id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { return m.DoK8sConnect(s, id, "log", params) } diff --git a/pkg/webconsole/command/kube_command.go b/pkg/webconsole/command/kube_command.go index 6647f25f42..4f731d83aa 100644 --- a/pkg/webconsole/command/kube_command.go +++ b/pkg/webconsole/command/kube_command.go @@ -23,6 +23,7 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/log" + webconsole_api "yunion.io/x/onecloud/pkg/apis/webconsole" o "yunion.io/x/onecloud/pkg/webconsole/options" ) @@ -114,12 +115,29 @@ func (c *KubectlExec) Command(cmd string, args ...string) *KubectlExec { } func NewPodBashCommand(env *K8sEnv) ICommand { + shellRequest := webconsole_api.SK8sShellRequest{} + err := env.Data.Unmarshal(&shellRequest) + if err != nil { + log.Errorf("env.Data.Unmarshal SK8sShellRequest: %s", err) + } + if shellRequest.Command == "" { + shellRequest.Command = "sh" + } + args := make([]string, 0) + if len(shellRequest.Env) > 0 { + for k, v := range shellRequest.Env { + args = append(args, fmt.Sprintf("%s=%s", k, v)) + } + args = append(args, shellRequest.Command) + shellRequest.Command = "env" + } + return NewKubectlCommand(env.Kubeconfig, env.Namespace).Exec(). Stdin(). TTY(). Pod(env.Pod). Container(env.Container). - Command("sh") + Command(shellRequest.Command, args...) } type KubectlLog struct { diff --git a/pkg/webconsole/handlers.go b/pkg/webconsole/handlers.go index 3ac29398bf..a8395da739 100644 --- a/pkg/webconsole/handlers.go +++ b/pkg/webconsole/handlers.go @@ -23,6 +23,7 @@ import ( "net/url" "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" webconsole_api "yunion.io/x/onecloud/pkg/apis/webconsole" "yunion.io/x/onecloud/pkg/appsrv" @@ -54,21 +55,25 @@ func InitHandlers(app *appsrv.Application) { func fetchK8sEnv(ctx context.Context, w http.ResponseWriter, r *http.Request) (*command.K8sEnv, error) { params, _, body := appsrv.FetchEnv(ctx, w, r) - cluster, _ := body.GetString("cluster") - if cluster == "" { - cluster = "default" + + k8sReq := webconsole_api.SK8sRequest{} + err := body.Unmarshal(&k8sReq) + if err != nil { + return nil, errors.Wrap(err, "body.Unmarshal SK8sRequest") } - namespace, _ := body.GetString("namespace") - if namespace == "" { - namespace = "default" + + if k8sReq.Cluster == "" { + k8sReq.Cluster = "default" + } + if k8sReq.Namespace == "" { + k8sReq.Namespace = "default" } podName := params[""] - container, _ := body.GetString("container") adminSession := auth.GetAdminSession(ctx, o.Options.Region, "") query := jsonutils.NewDict() - query.Add(jsonutils.NewString(namespace), "namespace") - query.Add(jsonutils.NewString(cluster), "cluster") + query.Add(jsonutils.NewString(k8sReq.Namespace), "namespace") + query.Add(jsonutils.NewString(k8sReq.Cluster), "cluster") obj, err := k8s.Pods.Get(adminSession, podName, query) if err != nil { return nil, err @@ -78,13 +83,13 @@ func fetchK8sEnv(ctx context.Context, w http.ResponseWriter, r *http.Request) (* } data := jsonutils.NewDict() - ret, err := k8s.KubeClusters.GetSpecific(adminSession, cluster, "kubeconfig", data) + ret, err := k8s.KubeClusters.GetSpecific(adminSession, k8sReq.Cluster, "kubeconfig", data) if err != nil { return nil, err } conf, err := ret.GetString("kubeconfig") if err != nil { - return nil, httperrors.NewNotFoundError("Not found cluster %q kubeconfig", cluster) + return nil, httperrors.NewNotFoundError("Not found cluster %q kubeconfig", k8sReq.Cluster) } f, err := ioutil.TempFile("", "kubeconfig-") if err != nil { @@ -94,10 +99,10 @@ func fetchK8sEnv(ctx context.Context, w http.ResponseWriter, r *http.Request) (* f.WriteString(conf) return &command.K8sEnv{ - Cluster: cluster, - Namespace: namespace, + Cluster: k8sReq.Cluster, + Namespace: k8sReq.Namespace, Pod: podName, - Container: container, + Container: k8sReq.Container, Kubeconfig: f.Name(), Data: body, }, nil