region: guests: sshable: add POST /servers/<server-id>/make-sshable

This commit is contained in:
Yousong Zhou
2021-04-15 16:41:39 +08:00
parent cba83b48a8
commit b090a63c3b
2 changed files with 140 additions and 0 deletions
+10
View File
@@ -36,3 +36,13 @@ type GuestSshableOutput struct {
MethodTried []GuestSshableMethodData
}
type GuestMakeSshableInput struct {
User string
PrivateKey string
Password string
}
type GuestMakeSshableOutput struct {
AnsiblePlaybookId string
}
+130
View File
@@ -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
}