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 +}