mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-09-24 16:03:43 +08:00
Merge pull request #10713 from yousong/feature/yousong-make-sshable
Feature/yousong make sshable
This commit is contained in:
@@ -36,3 +36,17 @@ type GuestSshableOutput struct {
|
||||
|
||||
MethodTried []GuestSshableMethodData
|
||||
}
|
||||
|
||||
type GuestMakeSshableInput struct {
|
||||
User string
|
||||
PrivateKey string
|
||||
Password string
|
||||
}
|
||||
|
||||
type GuestMakeSshableOutput struct {
|
||||
AnsiblePlaybookId string
|
||||
}
|
||||
|
||||
type GuestMakeSshableCmdOutput struct {
|
||||
ShellCmd string
|
||||
}
|
||||
|
||||
@@ -16,10 +16,13 @@ package models
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
@@ -29,12 +32,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
|
||||
@@ -88,9 +95,37 @@ func (guest *SGuest) GetDetailsSshable(
|
||||
tryData.PrivateKey = privateKey
|
||||
tryData.PublicKey = publicKey
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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 +146,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 +160,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 +192,7 @@ func (guest *SGuest) GetDetailsSshable(
|
||||
continue
|
||||
}
|
||||
if ok := guest.sshableTryForward(ctx, tryData, &fwd); ok {
|
||||
return tryData.outputJSON(), nil
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -174,7 +209,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 +250,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(
|
||||
@@ -291,6 +326,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,
|
||||
@@ -309,3 +349,180 @@ 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user