From a0d066ab06a8af5e537ddc11d0385d282079f954 Mon Sep 17 00:00:00 2001 From: Yousong Zhou Date: Wed, 14 Apr 2021 20:15:44 +0800 Subject: [PATCH 1/8] build: ansibleserver-base: v1.0.3: add sshpass Required for using ansible with ansible_password --- build/docker/Dockerfile.ansibleserver-base | 1 + 1 file changed, 1 insertion(+) diff --git a/build/docker/Dockerfile.ansibleserver-base b/build/docker/Dockerfile.ansibleserver-base index f8e47a1223..5c0ad22260 100644 --- a/build/docker/Dockerfile.ansibleserver-base +++ b/build/docker/Dockerfile.ansibleserver-base @@ -7,6 +7,7 @@ MAINTAINER "Yousong Zhou " RUN set -x \ && apk update \ && apk add openssh-client \ + && apk add sshpass \ && apk add ansible \ && apk add tzdata git ca-certificates \ && rm -rf /var/cache/apk/* From de402bcf2d3e6ba9e38425593d6f6c737c096bf4 Mon Sep 17 00:00:00 2001 From: Yousong Zhou Date: Wed, 14 Apr 2021 20:16:36 +0800 Subject: [PATCH 2/8] build: ansibleserver: use ansibleserver-base:v1.0.3 --- build/docker/Dockerfile.ansibleserver | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/docker/Dockerfile.ansibleserver b/build/docker/Dockerfile.ansibleserver index 3c9b00af6f..97b86b7b12 100644 --- a/build/docker/Dockerfile.ansibleserver +++ b/build/docker/Dockerfile.ansibleserver @@ -1,4 +1,4 @@ -FROM registry.cn-beijing.aliyuncs.com/yunionio/ansibleserver-base:v1.0.2 +FROM registry.cn-beijing.aliyuncs.com/yunionio/ansibleserver-base:v1.0.3 # install playbook and telegraf install pkg COPY --from=registry.cn-beijing.aliyuncs.com/yunionio/file-repo:v0.1.0 /opt/yunion/playbook /opt/yunion/playbook From cc7ffd707a8b246761928d37587afff364617816 Mon Sep 17 00:00:00 2001 From: Yousong Zhou Date: Tue, 13 Apr 2021 19:27:30 +0800 Subject: [PATCH 3/8] regions: lbagents: move pb update/create as mcclient module method --- .../models/loadbalanceragents_deploy.go | 46 ++------------ pkg/mcclient/modules/mod_ansibleplaybooks.go | 62 ++++++++++++++++++- 2 files changed, 65 insertions(+), 43 deletions(-) diff --git a/pkg/compute/models/loadbalanceragents_deploy.go b/pkg/compute/models/loadbalanceragents_deploy.go index ea8baa262e..27a9860247 100644 --- a/pkg/compute/models/loadbalanceragents_deploy.go +++ b/pkg/compute/models/loadbalanceragents_deploy.go @@ -27,7 +27,6 @@ import ( "yunion.io/x/pkg/util/regutils" "yunion.io/x/pkg/utils" - ansible_apis "yunion.io/x/onecloud/pkg/apis/ansible" compute_apis "yunion.io/x/onecloud/pkg/apis/compute" identity_apis "yunion.io/x/onecloud/pkg/apis/identity" "yunion.io/x/onecloud/pkg/cloudcommon/db" @@ -372,47 +371,10 @@ func (lbagent *SLoadbalancerAgent) updateOrCreatePbModel(ctx context.Context, pb *ansible.Playbook, ) (*mcclient_models.AnsiblePlaybook, error) { cliSess := auth.GetSession(ctx, userCred, "", "") - - if pbId == "" { - pbJson, err := mcclient_modules.AnsiblePlaybooks.Get(cliSess, pbName, nil) - if err == nil { - pbModel := &mcclient_models.AnsiblePlaybook{} - if err := pbJson.Unmarshal(pbModel); err == nil { - pbId = pbModel.Id - } - } - } - - var pbJson jsonutils.JSONObject - if pbId != "" { - var err error - ansiblePbInput := &ansible_apis.AnsiblePlaybookUpdateInput{ - Name: pbName, - Playbook: *pb, - } - params := ansiblePbInput.JSON(ansiblePbInput) - pbJson, err = mcclient_modules.AnsiblePlaybooks.Update(cliSess, pbId, params) - if err != nil { - return nil, errors.WithMessage(err, "update ansibleplaybook") - } - } else { - var err error - ansiblePbInput := &ansible_apis.AnsiblePlaybookCreateInput{ - Name: pbName, - Playbook: *pb, - } - params := ansiblePbInput.JSON(ansiblePbInput) - pbJson, err = mcclient_modules.AnsiblePlaybooks.Create(cliSess, params) - if err != nil { - return nil, errors.WithMessage(err, "create ansibleplaybook") - } - } - - pbModel := &mcclient_models.AnsiblePlaybook{} - if err := pbJson.Unmarshal(pbModel); err != nil { - return nil, errors.WithMessage(err, "unmarshal ansibleplaybook") - } - return pbModel, nil + pbModel, err := mcclient_modules.AnsiblePlaybooks.UpdateOrCreatePbModel( + ctx, cliSess, pbId, pbName, pb, + ) + return pbModel, err } func (lbagent *SLoadbalancerAgent) PerformUndeploy(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { diff --git a/pkg/mcclient/modules/mod_ansibleplaybooks.go b/pkg/mcclient/modules/mod_ansibleplaybooks.go index 187cfcb25c..4221ab6c70 100644 --- a/pkg/mcclient/modules/mod_ansibleplaybooks.go +++ b/pkg/mcclient/modules/mod_ansibleplaybooks.go @@ -14,7 +14,18 @@ package modules -import "yunion.io/x/onecloud/pkg/mcclient/modulebase" +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + ansible_apis "yunion.io/x/onecloud/pkg/apis/ansible" + "yunion.io/x/onecloud/pkg/mcclient" + mcclient_models "yunion.io/x/onecloud/pkg/mcclient/models" + "yunion.io/x/onecloud/pkg/mcclient/modulebase" + "yunion.io/x/onecloud/pkg/util/ansible" +) type AnsiblePlaybookManager struct { modulebase.ResourceManager @@ -41,3 +52,52 @@ func init() { } registerV2(&AnsiblePlaybooks) } + +func (man *AnsiblePlaybookManager) UpdateOrCreatePbModel( + ctx context.Context, + cliSess *mcclient.ClientSession, + pbId string, + pbName string, + pb *ansible.Playbook, +) (*mcclient_models.AnsiblePlaybook, error) { + if pbId == "" { + pbJson, err := man.Get(cliSess, pbName, nil) + if err == nil { + pbModel := &mcclient_models.AnsiblePlaybook{} + if err := pbJson.Unmarshal(pbModel); err == nil { + pbId = pbModel.Id + } + } + } + + var pbJson jsonutils.JSONObject + if pbId != "" { + var err error + ansiblePbInput := &ansible_apis.AnsiblePlaybookUpdateInput{ + Name: pbName, + Playbook: *pb, + } + params := ansiblePbInput.JSON(ansiblePbInput) + pbJson, err = man.Update(cliSess, pbId, params) + if err != nil { + return nil, errors.Wrap(err, "update ansibleplaybook") + } + } else { + var err error + ansiblePbInput := &ansible_apis.AnsiblePlaybookCreateInput{ + Name: pbName, + Playbook: *pb, + } + params := ansiblePbInput.JSON(ansiblePbInput) + pbJson, err = man.Create(cliSess, params) + if err != nil { + return nil, errors.Wrap(err, "create ansibleplaybook") + } + } + + pbModel := &mcclient_models.AnsiblePlaybook{} + if err := pbJson.Unmarshal(pbModel); err != nil { + return nil, errors.Wrap(err, "unmarshal ansibleplaybook") + } + return pbModel, nil +} From 98489ca038a6b86cea91010be6e1020f3201e1eb Mon Sep 17 00:00:00 2001 From: Yousong Zhou Date: Tue, 13 Apr 2021 15:48:27 +0800 Subject: [PATCH 4/8] region: guests: sshable: refactor for reusability --- pkg/compute/models/guest_sshable.go | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/pkg/compute/models/guest_sshable.go b/pkg/compute/models/guest_sshable.go index 662a94a3a2..e97abc6a9b 100644 --- a/pkg/compute/models/guest_sshable.go +++ b/pkg/compute/models/guest_sshable.go @@ -88,9 +88,20 @@ func (guest *SGuest) GetDetailsSshable( tryData.PrivateKey = privateKey tryData.PublicKey = publicKey + if err := guest.sshableTryEach(ctx, userCred, tryData); err != nil { + return nil, err + } + return tryData.outputJSON(), nil +} + +func (guest *SGuest) sshableTryEach( + ctx context.Context, + userCred mcclient.TokenCredential, + tryData *GuestSshableTryData, +) error { gns, err := guest.GetNetworks("") if err != nil { - return nil, httperrors.NewInternalServerError("fetch network interface information: %v", err) + return httperrors.NewInternalServerError("fetch network interface information: %v", err) } type gnInfo struct { guestNetwork *SGuestnetwork @@ -111,7 +122,7 @@ func (guest *SGuest) GetDetailsSshable( if vpc.Id == compute_api.DEFAULT_VPC_ID { // - vpc_id == "default" if ok := guest.sshableTryDefaultVPC(ctx, tryData, gn); ok { - return tryData.outputJSON(), nil + return nil } } else { gnInfos = append(gnInfos, gnInfo{ @@ -125,7 +136,7 @@ func (guest *SGuest) GetDetailsSshable( // - check eip if eip, err := guest.GetEipOrPublicIp(); err == nil && eip != nil { if ok := guest.sshableTryEip(ctx, tryData, eip); ok { - return tryData.outputJSON(), nil + return nil } } @@ -157,7 +168,7 @@ func (guest *SGuest) GetDetailsSshable( continue } if ok := guest.sshableTryForward(ctx, tryData, &fwd); ok { - return tryData.outputJSON(), nil + return nil } } } @@ -174,7 +185,7 @@ func (guest *SGuest) GetDetailsSshable( var fwd cloudproxy_api.ForwardDetails if err := res.Unmarshal(&fwd); err == nil { if ok := guest.sshableTryForward(ctx, tryData, &fwd); ok { - return tryData.outputJSON(), nil + return nil } } } else { @@ -215,12 +226,12 @@ func (guest *SGuest) GetDetailsSshable( for j := range dnats { dnat := &dnats[j] if ok := guest.sshableTryDnat(ctx, tryData, dnat); ok { - return tryData.outputJSON(), nil + return nil } } } - return tryData.outputJSON(), nil + return nil } func (guest *SGuest) sshableTryDnat( From cba83b48a8781975d6b8c86bfe98d354b525bd1e Mon Sep 17 00:00:00 2001 From: Yousong Zhou Date: Wed, 14 Apr 2021 21:21:36 +0800 Subject: [PATCH 5/8] region: guests: sshable: add field sshable_last_state --- pkg/compute/models/guest_sshable.go | 18 ++++++++++++++++++ pkg/compute/models/guests.go | 2 ++ 2 files changed, 20 insertions(+) diff --git a/pkg/compute/models/guest_sshable.go b/pkg/compute/models/guest_sshable.go index e97abc6a9b..40f3de55e9 100644 --- a/pkg/compute/models/guest_sshable.go +++ b/pkg/compute/models/guest_sshable.go @@ -20,6 +20,7 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/log" + "yunion.io/x/pkg/tristate" "yunion.io/x/sqlchemy" cloudproxy_api "yunion.io/x/onecloud/pkg/apis/cloudproxy" @@ -91,6 +92,23 @@ func (guest *SGuest) GetDetailsSshable( if err := guest.sshableTryEach(ctx, userCred, tryData); err != nil { return nil, err } + + { + sshable := false + for i := range tryData.MethodTried { + if tryData.MethodTried[i].Sshable { + sshable = true + break + } + } + if _, err := db.Update(guest, func() error { + guest.SshableLastState = tristate.NewFromBool(sshable) + return nil + }); err != nil { + log.Errorf("update guest %s(%s) sshable_last_state to %v: %v", guest.Name, guest.Id, sshable, err) + } + } + return tryData.outputJSON(), nil } diff --git a/pkg/compute/models/guests.go b/pkg/compute/models/guests.go index cebbc5988b..bbe8a1ff5a 100644 --- a/pkg/compute/models/guests.go +++ b/pkg/compute/models/guests.go @@ -151,6 +151,8 @@ type SGuest struct { // 套餐名称 InstanceType string `width:"64" charset:"utf8" nullable:"true" list:"user" create:"optional"` + + SshableLastState tristate.TriState `nullable:"false" default:"false" list:"user"` } func (manager *SGuestManager) AllowListItems(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { From b090a63c3b33413df7ac21fd19b08df1a3c80623 Mon Sep 17 00:00:00 2001 From: Yousong Zhou Date: Tue, 13 Apr 2021 19:27:22 +0800 Subject: [PATCH 6/8] region: guests: sshable: add POST /servers//make-sshable --- pkg/apis/compute/guest_sshable.go | 10 +++ pkg/compute/models/guest_sshable.go | 130 ++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+) diff --git a/pkg/apis/compute/guest_sshable.go b/pkg/apis/compute/guest_sshable.go index bf36ba0152..1755d6f2df 100644 --- a/pkg/apis/compute/guest_sshable.go +++ b/pkg/apis/compute/guest_sshable.go @@ -36,3 +36,13 @@ type GuestSshableOutput struct { MethodTried []GuestSshableMethodData } + +type GuestMakeSshableInput struct { + User string + PrivateKey string + Password string +} + +type GuestMakeSshableOutput struct { + AnsiblePlaybookId string +} diff --git a/pkg/compute/models/guest_sshable.go b/pkg/compute/models/guest_sshable.go index 40f3de55e9..2fb3770446 100644 --- a/pkg/compute/models/guest_sshable.go +++ b/pkg/compute/models/guest_sshable.go @@ -16,6 +16,7 @@ package models import ( "context" + "fmt" "time" "yunion.io/x/jsonutils" @@ -30,12 +31,16 @@ import ( "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/auth" + mcclient_modules "yunion.io/x/onecloud/pkg/mcclient/modules" cloudproxy_module "yunion.io/x/onecloud/pkg/mcclient/modules/cloudproxy" + "yunion.io/x/onecloud/pkg/util/ansible" "yunion.io/x/onecloud/pkg/util/httputils" ssh_util "yunion.io/x/onecloud/pkg/util/ssh" ) type GuestSshableTryData struct { + DryRun bool + User string Host string Port int @@ -320,6 +325,11 @@ func (guest *SGuest) sshableTry( tryData *GuestSshableTryData, methodData compute_api.GuestSshableMethodData, ) bool { + if tryData.DryRun { + tryData.AddMethodTried(methodData) + return true + } + ctx, _ = context.WithTimeout(ctx, 7*time.Second) conf := ssh_util.ClientConfig{ Username: tryData.User, @@ -338,3 +348,123 @@ func (guest *SGuest) sshableTry( tryData.AddMethodTried(methodData) return ok } + +func (guest *SGuest) AllowPerformMakeSshable( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, +) bool { + return db.IsProjectAllowGetSpec(userCred, guest, "make-sshable") +} + +func (guest *SGuest) PerformMakeSshable( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + input compute_api.GuestMakeSshableInput, +) (output compute_api.GuestMakeSshableOutput, err error) { + if guest.Status != compute_api.VM_RUNNING { + return output, httperrors.NewBadRequestError("make-sshable can only be performed when in running state") + } + + if input.User == "" { + return output, httperrors.NewBadRequestError("missing username") + } + if input.PrivateKey == "" && input.Password == "" { + return output, httperrors.NewBadRequestError("private_key and password cannot both be empty") + } + + _, projectPublicKey, err := sshkeys.GetSshProjectKeypair(ctx, guest.ProjectId) + if err != nil { + return output, httperrors.NewInternalServerError("fetch project public key: %v", err) + } + _, adminPublicKey, err := sshkeys.GetSshAdminKeypair(ctx) + if err != nil { + return output, httperrors.NewInternalServerError("fetch admin public key: %v", err) + } + + tryData := &GuestSshableTryData{ + DryRun: true, + } + if err := guest.sshableTryEach(ctx, userCred, tryData); err != nil { + return output, httperrors.NewNotAcceptableError("searching for usable ssh address: %v", err) + } else if len(tryData.MethodTried) == 0 { + return output, httperrors.NewNotAcceptableError("no usable ssh address") + } + + host := ansible.Host{ + Name: guest.Name, + } + host.SetVar("ansible_user", input.User) + host.SetVar("ansible_host", tryData.MethodTried[0].Host) + host.SetVar("ansible_port", fmt.Sprintf("%d", tryData.MethodTried[0].Port)) + host.SetVar("ansible_become", "yes") + pb := &ansible.Playbook{ + Inventory: ansible.Inventory{ + Hosts: []ansible.Host{host}, + }, + Modules: []ansible.Module{ + { + Name: "group", + Args: []string{ + "name=cloudroot", + "state=present", + }, + }, + { + Name: "user", + Args: []string{ + "name=cloudroot", + "state=present", + "group=cloudroot", + }, + }, + { + Name: "authorized_key", + Args: []string{ + "user=cloudroot", + "state=present", + fmt.Sprintf("key=%q", adminPublicKey), + }, + }, + { + Name: "authorized_key", + Args: []string{ + "user=cloudroot", + "state=present", + fmt.Sprintf("key=%q", projectPublicKey), + }, + }, + { + Name: "lineinfile", + Args: []string{ + "dest=/etc/sudoers", + "state=present", + fmt.Sprintf("regexp=%q", "^cloudroot "), + fmt.Sprintf("line=%q", "cloudroot ALL=(ALL) NOPASSWD: ALL"), + fmt.Sprintf("validate=%q", "visudo -cf %s"), + }, + }, + }, + } + if input.PrivateKey != "" { + pb.PrivateKey = []byte(input.PrivateKey) + } else if input.Password != "" { + host.SetVar("ansible_password", input.Password) + } + + cliSess := auth.GetSession(ctx, userCred, "", "") + pbId := "" + pbName := "make-sshable-" + guest.Name + pbModel, err := mcclient_modules.AnsiblePlaybooks.UpdateOrCreatePbModel( + ctx, cliSess, pbId, pbName, pb, + ) + if err != nil { + return output, httperrors.NewGeneralError(err) + } + + output = compute_api.GuestMakeSshableOutput{ + AnsiblePlaybookId: pbModel.Id, + } + return output, nil +} From 264c58073e83819d44bdba942816975e704981cf Mon Sep 17 00:00:00 2001 From: Yousong Zhou Date: Thu, 15 Apr 2021 12:06:39 +0800 Subject: [PATCH 7/8] region: guests: sshable: add GET /servers//make-sshable-cmd --- pkg/apis/compute/guest_sshable.go | 4 ++ pkg/compute/models/guest_sshable.go | 58 +++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/pkg/apis/compute/guest_sshable.go b/pkg/apis/compute/guest_sshable.go index 1755d6f2df..dc07184e17 100644 --- a/pkg/apis/compute/guest_sshable.go +++ b/pkg/apis/compute/guest_sshable.go @@ -46,3 +46,7 @@ type GuestMakeSshableInput struct { type GuestMakeSshableOutput struct { AnsiblePlaybookId string } + +type GuestMakeSshableCmdOutput struct { + ShellCmd string +} diff --git a/pkg/compute/models/guest_sshable.go b/pkg/compute/models/guest_sshable.go index 2fb3770446..ab9e4a244b 100644 --- a/pkg/compute/models/guest_sshable.go +++ b/pkg/compute/models/guest_sshable.go @@ -17,6 +17,7 @@ package models import ( "context" "fmt" + "strings" "time" "yunion.io/x/jsonutils" @@ -468,3 +469,60 @@ func (guest *SGuest) PerformMakeSshable( } return output, nil } + +func (guest *SGuest) AllowGetDetailsMakeSshableCmd( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, +) bool { + return db.IsProjectAllowGetSpec(userCred, guest, "make-sshable-cmd") +} + +func (guest *SGuest) GetDetailsMakeSshableCmd( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, +) (output compute_api.GuestMakeSshableCmdOutput, err error) { + _, projectPublicKey, err := sshkeys.GetSshProjectKeypair(ctx, guest.ProjectId) + if err != nil { + return output, httperrors.NewInternalServerError("fetch project public key: %v", err) + } + _, adminPublicKey, err := sshkeys.GetSshAdminKeypair(ctx) + if err != nil { + return output, httperrors.NewInternalServerError("fetch admin public key: %v", err) + } + + varVals := [][2]string{ + [2]string{"user", "cloudroot"}, + [2]string{"adminpub", strings.TrimSpace(adminPublicKey)}, + [2]string{"projpub", strings.TrimSpace(projectPublicKey)}, + } + shellCmd := "" + for i := range varVals { + varVal := varVals[i] + shellCmd += fmt.Sprintf("%s=%q\n", varVal[0], varVal[1]) + } + + shellCmd += ` +group="$user" +sshdir="/home/$user/.ssh" +` + shellCmd += ` +id -g "$group" &>/dev/null || groupadd "$group" +id -u "$user" &>/dev/null || useradd --create-home --gid "$group" "$user" +mkdir -p "$sshdir" +echo "$adminpub" >>"$sshdir/authorized_keys" +echo "$projpub" >>"$sshdir/authorized_keys" +chown -R "$user:$group" "$sshdir" +chmod -R 700 "$sshdir" +chmod -R 600 "$sshdir/authorized_keys" + +if ! grep -q "^$user " /etc/sudoers; then + echo "$user ALL=(ALL) NOPASSWD: ALL" | EDITOR='tee -a' visudo +fi +` + output = compute_api.GuestMakeSshableCmdOutput{ + ShellCmd: shellCmd, + } + return output, nil +} From 1e330e8f3c96adf99877d411d8d8a82be64efc5b Mon Sep 17 00:00:00 2001 From: Yousong Zhou Date: Tue, 13 Apr 2021 19:52:45 +0800 Subject: [PATCH 8/8] climc: add commands server-make-sshable, server-make-sshable-cmd --- cmd/climc/shell/compute/servers.go | 2 ++ pkg/mcclient/options/servers.go | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/cmd/climc/shell/compute/servers.go b/cmd/climc/shell/compute/servers.go index b874327be7..ac0e1f8b90 100644 --- a/cmd/climc/shell/compute/servers.go +++ b/cmd/climc/shell/compute/servers.go @@ -85,6 +85,7 @@ func init() { cmd.Perform("save-template", new(options.ServerSaveImageOptions)) cmd.Perform("remote-update", new(options.ServerRemoteUpdateOptions)) cmd.Perform("create-eip", &options.ServerCreateEipOptions{}) + cmd.Perform("make-sshable", &options.ServerMakeSshableOptions{}) cmd.Get("vnc", new(options.ServerIdOptions)) cmd.Get("desc", new(options.ServerIdOptions)) @@ -92,6 +93,7 @@ func init() { cmd.Get("iso", new(options.ServerIdOptions)) cmd.Get("create-params", new(options.ServerIdOptions)) cmd.Get("sshable", new(options.ServerIdOptions)) + cmd.Get("make-sshable-cmd", new(options.ServerIdOptions)) cmd.Get("change-owner-candidate-domains", new(options.ServerChangeOwnerCandidateDomainsOptions)) type ServerTaskShowOptions struct { diff --git a/pkg/mcclient/options/servers.go b/pkg/mcclient/options/servers.go index 22419a5b61..c4dfb68424 100644 --- a/pkg/mcclient/options/servers.go +++ b/pkg/mcclient/options/servers.go @@ -1089,3 +1089,21 @@ type ServerCreateEipOptions struct { func (opts *ServerCreateEipOptions) Params() (jsonutils.JSONObject, error) { return jsonutils.Marshal(opts), nil } + +type ServerMakeSshableOptions struct { + BaseIdOptions + + User string `help:"ssh username for ssh connection" default:"root"` + PrivateKey string `help:"ssh privatekey for ssh connection"` + Password string `help:"ssh password for ssh connection"` +} + +func (opts *ServerMakeSshableOptions) Params() (jsonutils.JSONObject, error) { + if opts.User == "" { + return nil, fmt.Errorf("ssh username must be set") + } + if opts.PrivateKey == "" && opts.Password == "" { + return nil, fmt.Errorf("either --private-key or --password must be set") + } + return jsonutils.Marshal(opts), nil +}