Skip to content

Commit

Permalink
feature: support adb shell (#20245)
Browse files Browse the repository at this point in the history
* feature: support adb shell

* fix(build): add kubectl into Dockerfile.kubectl

---------

Co-authored-by: Qiu Jian <[email protected]>
Co-authored-by: Zexi Li <[email protected]>
  • Loading branch information
3 people authored May 14, 2024
1 parent 9806dee commit f3b9f25
Show file tree
Hide file tree
Showing 12 changed files with 167 additions and 5 deletions.
9 changes: 7 additions & 2 deletions build/docker/Dockerfile.kubectl
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
FROM alpine:3.11
FROM alpine:3.19

ARG TARGETPLATFORM
ARG BUILDPLATFORM
ARG TARGETARCH

RUN echo http://dl-cdn.alpinelinux.org/alpine/edge/testing >>/etc/apk/repositories
RUN apk update && apk add kubectl && rm -rf /var/cache/apk/*
RUN apk update && apk add curl android-tools && rm -rf /var/cache/apk/*
RUN curl https://iso.yunion.cn/binaries/kubernetes-release/release/v1.22.9/bin/linux/${TARGETARCH}/kubectl -o /usr/bin/kubectl && chmod a+x /usr/bin/kubectl
3 changes: 2 additions & 1 deletion build/docker/Dockerfile.webconsole
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
FROM registry.cn-beijing.aliyuncs.com/yunionio/webconsole-base:20230731.2
#FROM registry.cn-beijing.aliyuncs.com/yunionio/webconsole-base:20230731.2
FROM registry.cn-beijing.aliyuncs.com/yunionio/webconsole-base:20240514.0

ADD ./_output/alpine-build/bin/webconsole /opt/yunion/bin/webconsole
2 changes: 1 addition & 1 deletion build/docker/Dockerfile.webconsole-base
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM registry.cn-beijing.aliyuncs.com/yunionio/kubectl:1.18.6
FROM registry.cn-beijing.aliyuncs.com/yunionio/kubectl:1.22.9

MAINTAINER "Zexi Li <[email protected]>"

Expand Down
7 changes: 6 additions & 1 deletion build/docker/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@ climc-base:
docker buildx build --platform linux/arm64,linux/amd64 --push \
-t registry.cn-beijing.aliyuncs.com/yunionio/climc-base:$(CLIMC_BASE_VERSION) -f ./Dockerfile.climc-base .

WEBCONSOLE_BASE_VERSION = 20230731.2
KUBECTL_VERSION = 1.22.9
kubectl:
docker buildx build --platform linux/arm64,linux/amd64 --push \
-t registry.cn-beijing.aliyuncs.com/yunionio/kubectl:$(KUBECTL_VERSION) -f ./Dockerfile.kubectl .

WEBCONSOLE_BASE_VERSION = 20240514.0
webconsole-base:
$(DOCKER_BUILDX)/webconsole-base:$(WEBCONSOLE_BASE_VERSION) -f ./Dockerfile.webconsole-base .

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
备份导出文件{{ .pack_file_name }}创建成功
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Backup export file {{ .pack_file_name }} was created.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
备份导出文件{{ .pack_file_name }}创建成功
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Backup export file {{ .pack_file_name }} was created.
4 changes: 4 additions & 0 deletions pkg/mcclient/modules/webconsole/mod_webconsole.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ func (m WebConsoleManager) DoK8sShellConnect(s *mcclient.ClientSession, id strin
return m.DoK8sConnect(s, id, "shell", params)
}

func (m WebConsoleManager) DoAdbShell(s *mcclient.ClientSession, id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) {
return m.DoConnect(s, "adb", id, "shell", params)
}

func (m WebConsoleManager) DoContainerExec(s *mcclient.ClientSession, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
containerId, err := data.GetString("container_id")
if err != nil {
Expand Down
73 changes: 73 additions & 0 deletions pkg/webconsole/command/adb_command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// 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 command

import (
"fmt"
"os/exec"

"yunion.io/x/pkg/errors"

"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
o "yunion.io/x/onecloud/pkg/webconsole/options"
)

type SAdbShellInfo struct {
IpAddr string `json:"ip_addr"`
Port int `json:"port"`
}

type SAdbShellCommand struct {
*BaseCommand
Info *SAdbShellInfo
s *mcclient.ClientSession
}

func NewAdbShellCommand(info *SAdbShellInfo, s *mcclient.ClientSession) (*SAdbShellCommand, error) {
if info.IpAddr == "" {
return nil, errors.Wrap(httperrors.ErrInputParameter, "Empty host ip address")
}
if info.Port == 0 {
return nil, errors.Wrap(httperrors.ErrInputParameter, "Empty host port")
}
name := o.Options.AdbPath
cmd := NewBaseCommand(s, name, "-s", fmt.Sprintf("%s:%d", info.IpAddr, info.Port), "shell")
tool := &SAdbShellCommand{
BaseCommand: cmd,
Info: info,
}
initCmd := exec.Command(name, "connect", fmt.Sprintf("%s:%d", info.IpAddr, info.Port))
if err := initCmd.Run(); err != nil {
return nil, errors.Wrap(err, "connect adb")
}
return tool, nil
}

func (c *SAdbShellCommand) GetCommand() *exec.Cmd {
return c.BaseCommand.GetCommand()
}

func (c SAdbShellCommand) GetProtocol() string {
return PROTOCOL_TTY
}

func (c *SAdbShellCommand) Cleanup() error {
initCmd := exec.Command(o.Options.AdbPath, "disconnect", fmt.Sprintf("%s:%d", c.Info.IpAddr, c.Info.Port))
if err := initCmd.Run(); err != nil {
return errors.Wrap(err, "disconnect adb")
}
return nil
}
1 change: 1 addition & 0 deletions pkg/webconsole/options/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type WebConsoleOptions struct {
common_options.DBOptions

KubectlPath string `help:"kubectl binary path used to connect k8s cluster" default:"/usr/bin/kubectl"`
AdbPath string `help:"adb binary path" default:"/usr/bin/adb"`
IpmitoolPath string `help:"ipmitool binary path used to connect baremetal sol" default:"/usr/bin/ipmitool"`
EnableAutoLogin bool `help:"allow webconsole to log in directly with the cloudroot public key" default:"false"`
ApsaraConsoleAddr string `help:"Apsara console addr" default:"https://xxxx.com.cn/module/ecs/vnc/index.html"`
Expand Down
69 changes: 69 additions & 0 deletions pkg/webconsole/service/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"yunion.io/x/pkg/util/httputils"
"yunion.io/x/pkg/util/regutils"

compute_api "yunion.io/x/onecloud/pkg/apis/compute"
webconsole_api "yunion.io/x/onecloud/pkg/apis/webconsole"
"yunion.io/x/onecloud/pkg/appsrv"
"yunion.io/x/onecloud/pkg/appsrv/dispatcher"
Expand Down Expand Up @@ -59,6 +60,7 @@ func initHandlers(app *appsrv.Application) {
app.AddHandler("POST", ApiPathPrefix+"baremetal/<id>", auth.Authenticate(handleBaremetalShell))
app.AddHandler("POST", ApiPathPrefix+"ssh/<ip>", auth.Authenticate(handleSshShell))
app.AddHandler("POST", ApiPathPrefix+"server/<id>", auth.Authenticate(handleServerRemoteConsole))
app.AddHandler("POST", ApiPathPrefix+"adb/<id>/shell", auth.Authenticate(handleAdbShell))
app.AddHandler("POST", ApiPathPrefix+"server-rdp/<id>", auth.Authenticate(handleServerRemoteRDPConsole))
app.AddHandler("GET", ApiPathPrefix+"sftp/<session-id>/list", server.HandleSftpList)
app.AddHandler("GET", ApiPathPrefix+"sftp/<session-id>/download", server.HandleSftpDownload)
Expand Down Expand Up @@ -398,3 +400,70 @@ func sendJSON(w http.ResponseWriter, body jsonutils.JSONObject) {
}
appsrv.SendJSON(w, ret)
}

func handleAdbShell(ctx context.Context, w http.ResponseWriter, r *http.Request) {
env, err := fetchCloudEnv(ctx, w, r)
if err != nil {
httperrors.GeneralServerError(ctx, w, err)
return
}
serverId := env.Params["<id>"]
serverDetails := compute_api.ServerDetails{}
resp, err := modules.Servers.GetById(env.ClientSessin, serverId, nil)
if err != nil {
httperrors.GeneralServerError(ctx, w, err)
return
}
err = resp.Unmarshal(&serverDetails)
if err != nil {
httperrors.GeneralServerError(ctx, w, err)
return
}

adbPort := -1
// {\"container_port\":5555,\"host_port\":23888,\"protocol\":\"tcp\"}
type sPortMapping struct {
ContainerPort int `json:"container_port"`
HostPort int `json:"host_port"`
Protocol string `json:"protocol"`
}
if pmstr, ok := serverDetails.Metadata["port_mappings"]; ok {
pmJson, err := jsonutils.ParseString(pmstr)
if err != nil {
httperrors.GeneralServerError(ctx, w, err)
return
}
portMaps := make([]sPortMapping, 0)
err = pmJson.Unmarshal(&portMaps)
if err != nil {
httperrors.GeneralServerError(ctx, w, err)
return
}
for i := range portMaps {
portMap := portMaps[i]
if portMap.ContainerPort == 5555 && portMap.Protocol == "tcp" {
adbPort = portMap.HostPort
break
}
}
if adbPort < 0 {
httperrors.GeneralServerError(ctx, w, httperrors.NewNotSupportedError("adb port not found"))
return
}
} else {
httperrors.GeneralServerError(ctx, w, httperrors.NewNotSupportedError("porting_mapping not supported"))
return
}

info := command.SAdbShellInfo{
IpAddr: serverDetails.HostAccessIp,
Port: adbPort,
}

cmd, err := command.NewAdbShellCommand(&info, env.ClientSessin)
if err != nil {
httperrors.GeneralServerError(ctx, w, err)
return
}
handleCommandSession(ctx, cmd, w)
}

0 comments on commit f3b9f25

Please sign in to comment.