mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-08-31 01:35:56 +08:00
feat(region): save image for public cloud
This commit is contained in:
@@ -242,3 +242,11 @@ type DiskUpdateInput struct {
|
||||
// 磁盘类型
|
||||
DiskType string `json:"disk_type"`
|
||||
}
|
||||
|
||||
type DiskSaveInput struct {
|
||||
Name string
|
||||
Format string
|
||||
|
||||
// swagger: ignore
|
||||
ImageId string
|
||||
}
|
||||
|
||||
@@ -453,3 +453,28 @@ type ServerStopInput struct {
|
||||
// 目前仅阿里云,腾讯云此参数生效
|
||||
StopCharging bool `json:"stop_charging"`
|
||||
}
|
||||
|
||||
type ServerSaveImageInput struct {
|
||||
// 镜像名称
|
||||
Name string
|
||||
GenerateName string
|
||||
Notes string
|
||||
IsPublic bool
|
||||
// 镜像格式
|
||||
Format string
|
||||
|
||||
// 保存镜像后是否自动启动,若实例状态为运行中,则会先关闭实例
|
||||
// default: false
|
||||
AutoStart bool
|
||||
// swagger: ignore
|
||||
Restart bool
|
||||
|
||||
// swagger: ignore
|
||||
OsType string
|
||||
|
||||
// swagger: ignore
|
||||
OsArch string
|
||||
|
||||
// swagger: ignore
|
||||
ImageId string
|
||||
}
|
||||
|
||||
@@ -65,6 +65,11 @@ type SSubImage struct {
|
||||
SizeBytes int64
|
||||
}
|
||||
|
||||
type SaveImageOptions struct {
|
||||
Name string
|
||||
Notes string
|
||||
}
|
||||
|
||||
func CloudImage2Image(image ICloudImage) SImage {
|
||||
return SImage{
|
||||
CreatedAt: image.GetCreatedAt(),
|
||||
|
||||
@@ -330,6 +330,8 @@ type ICloudVM interface {
|
||||
GetInstanceSnapshot(idStr string) (ICloudInstanceSnapshot, error)
|
||||
GetInstanceSnapshots() ([]ICloudInstanceSnapshot, error)
|
||||
ResetToInstanceSnapshot(ctx context.Context, idStr string) error
|
||||
|
||||
SaveImage(opts *SaveImageOptions) (ICloudImage, error)
|
||||
}
|
||||
|
||||
type ICloudNic interface {
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"time"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
api "yunion.io/x/onecloud/pkg/apis/compute"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db/quotas"
|
||||
@@ -114,6 +115,10 @@ func (self *SBaseGuestDriver) RequestAttachDisk(ctx context.Context, guest *mode
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SBaseGuestDriver) RequestSaveImage(ctx context.Context, userCred mcclient.TokenCredential, guest *models.SGuest, task taskman.ITask) error {
|
||||
return errors.Wrapf(cloudprovider.ErrNotImplemented, "RequestSaveImage")
|
||||
}
|
||||
|
||||
func (self *SBaseGuestDriver) RequestGuestCreateAllDisks(ctx context.Context, guest *models.SGuest, task taskman.ITask) error {
|
||||
return fmt.Errorf("Not Implement")
|
||||
}
|
||||
|
||||
@@ -507,6 +507,13 @@ func (self *SESXiGuestDriver) AllowReconfigGuest() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (self *SESXiGuestDriver) RequestSaveImage(ctx context.Context, userCred mcclient.TokenCredential, guest *models.SGuest, task taskman.ITask) error {
|
||||
disks := guest.CategorizeDisks()
|
||||
opts := api.DiskSaveInput{}
|
||||
task.GetParams().Unmarshal(&opts)
|
||||
return disks.Root.StartDiskSaveTask(ctx, userCred, opts, task.GetTaskId())
|
||||
}
|
||||
|
||||
func (self *SESXiGuestDriver) DoGuestCreateDisksTask(ctx context.Context, guest *models.SGuest, task taskman.ITask) error {
|
||||
subtask, err := taskman.TaskManager.NewTask(ctx, "ESXiGuestCreateDiskTask", guest, task.GetUserCred(), task.GetParams(), task.GetTaskId(), "", nil)
|
||||
if err != nil {
|
||||
|
||||
@@ -437,6 +437,13 @@ func (self *SKVMGuestDriver) RequestAttachDisk(ctx context.Context, guest *model
|
||||
return guest.StartSyncTask(ctx, task.GetUserCred(), false, task.GetTaskId())
|
||||
}
|
||||
|
||||
func (self *SKVMGuestDriver) RequestSaveImage(ctx context.Context, userCred mcclient.TokenCredential, guest *models.SGuest, task taskman.ITask) error {
|
||||
disks := guest.CategorizeDisks()
|
||||
opts := api.DiskSaveInput{}
|
||||
task.GetParams().Unmarshal(&opts)
|
||||
return disks.Root.StartDiskSaveTask(ctx, userCred, opts, task.GetTaskId())
|
||||
}
|
||||
|
||||
func (self *SKVMGuestDriver) GetDetachDiskStatus() ([]string, error) {
|
||||
return []string{api.VM_READY, api.VM_RUNNING}, nil
|
||||
}
|
||||
|
||||
@@ -133,6 +133,50 @@ func (self *SManagedVirtualizedGuestDriver) GetJsonDescAtHost(ctx context.Contex
|
||||
return jsonutils.Marshal(&config), nil
|
||||
}
|
||||
|
||||
func (self *SManagedVirtualizedGuestDriver) RequestSaveImage(ctx context.Context, userCred mcclient.TokenCredential, guest *models.SGuest, task taskman.ITask) error {
|
||||
taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) {
|
||||
iVm, err := guest.GetIVM()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "guest.GetIVM")
|
||||
}
|
||||
opts := &cloudprovider.SaveImageOptions{}
|
||||
err = task.GetParams().Unmarshal(opts)
|
||||
image, err := iVm.SaveImage(opts)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "iVm.SaveImage")
|
||||
}
|
||||
err = cloudprovider.WaitStatus(image, cloudprovider.IMAGE_STATUS_ACTIVE, time.Second*10, time.Minute*10)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "wait image %s(%s) active current is: %s", image.GetName(), image.GetGlobalId(), image.GetStatus())
|
||||
}
|
||||
host := guest.GetHost()
|
||||
if host == nil {
|
||||
return nil, errors.Wrapf(cloudprovider.ErrNotFound, "find guest %s host", guest.Name)
|
||||
}
|
||||
region := host.GetRegion()
|
||||
iRegion, err := host.GetIRegion()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "host.GetIRegion")
|
||||
}
|
||||
caches, err := region.GetStoragecaches()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "region.GetStoragecaches")
|
||||
}
|
||||
for i := range caches {
|
||||
if caches[i].ManagerId == host.ManagerId {
|
||||
iStoragecache, err := iRegion.GetIStoragecacheById(caches[i].ExternalId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "iRegion.GetIStoragecacheById(%s)", caches[i].ExternalId)
|
||||
}
|
||||
result := caches[i].SyncCloudImages(ctx, userCred, iStoragecache, region)
|
||||
log.Infof("sync cloud image for storagecache %s result: %s", caches[i].Name, result.Result())
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SManagedVirtualizedGuestDriver) RequestGuestCreateAllDisks(ctx context.Context, guest *models.SGuest, task taskman.ITask) error {
|
||||
diskCat := guest.CategorizeDisks()
|
||||
var imageId string
|
||||
|
||||
+41
-24
@@ -989,23 +989,40 @@ func (self *SDisk) GetZone() *SZone {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SDisk) PrepareSaveImage(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict) (string, error) {
|
||||
if zone := self.GetZone(); zone == nil {
|
||||
func (self *SDisk) PrepareSaveImage(ctx context.Context, userCred mcclient.TokenCredential, input api.ServerSaveImageInput) (string, error) {
|
||||
zone := self.GetZone()
|
||||
if zone == nil {
|
||||
return "", httperrors.NewResourceNotFoundError("No zone for this disk")
|
||||
}
|
||||
data.Add(jsonutils.NewString(self.DiskFormat), "disk_format")
|
||||
if !data.Contains("generate_name") {
|
||||
name, _ := data.GetString("name")
|
||||
if len(input.GenerateName) == 0 {
|
||||
s := auth.GetAdminSession(ctx, options.Options.Region, "")
|
||||
imageList, err := modules.Images.List(s, jsonutils.Marshal(map[string]string{"name": name, "admin": "true"}))
|
||||
imageList, err := modules.Images.List(s, jsonutils.Marshal(map[string]string{"name": input.Name, "admin": "true"}))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if imageList.Total > 0 {
|
||||
return "", httperrors.NewConflictError("Duplicate image name %s", name)
|
||||
return "", httperrors.NewConflictError("Duplicate image name %s", input.Name)
|
||||
}
|
||||
}
|
||||
|
||||
opts := struct {
|
||||
Name string
|
||||
GenerateName string
|
||||
VirtualSize int
|
||||
DiskFormat string
|
||||
Properties map[string]string
|
||||
}{
|
||||
Name: input.Name,
|
||||
GenerateName: input.GenerateName,
|
||||
VirtualSize: self.DiskSize,
|
||||
DiskFormat: self.DiskFormat,
|
||||
Properties: map[string]string{
|
||||
"notes": input.Notes,
|
||||
"os_type": input.OsType,
|
||||
"os_arch": input.OsArch,
|
||||
},
|
||||
}
|
||||
|
||||
/*
|
||||
no need to check quota anymore
|
||||
session := auth.GetSession(userCred, options.Options.Region, "v2")
|
||||
@@ -1014,8 +1031,7 @@ func (self *SDisk) PrepareSaveImage(ctx context.Context, userCred mcclient.Token
|
||||
return "", err
|
||||
}*/
|
||||
us := auth.GetSession(ctx, userCred, options.Options.Region, "")
|
||||
data.Add(jsonutils.NewInt(int64(self.DiskSize)), "virtual_size")
|
||||
result, err := modules.Images.Create(us, data)
|
||||
result, err := modules.Images.Create(us, jsonutils.Marshal(opts))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -1030,7 +1046,7 @@ func (self *SDisk) AllowPerformSave(ctx context.Context, userCred mcclient.Token
|
||||
return self.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, self, "save")
|
||||
}
|
||||
|
||||
func (self *SDisk) PerformSave(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
func (self *SDisk) PerformSave(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.DiskSaveInput) (jsonutils.JSONObject, error) {
|
||||
if self.Status != api.DISK_READY {
|
||||
return nil, httperrors.NewResourceNotReadyError("Save disk when disk is READY")
|
||||
|
||||
@@ -1043,26 +1059,27 @@ func (self *SDisk) PerformSave(ctx context.Context, userCred mcclient.TokenCrede
|
||||
return nil, httperrors.NewResourceNotReadyError("Save disk when not being USED")
|
||||
}
|
||||
|
||||
if name, err := data.GetString("name"); err != nil || len(name) == 0 {
|
||||
if len(input.Name) == 0 {
|
||||
return nil, httperrors.NewInputParameterError("Image name is required")
|
||||
}
|
||||
kwargs := data.(*jsonutils.JSONDict)
|
||||
if imageId, err := self.PrepareSaveImage(ctx, userCred, kwargs); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
kwargs.Add(jsonutils.NewString(imageId), "image_id")
|
||||
return nil, self.StartDiskSaveTask(ctx, userCred, kwargs, "")
|
||||
opts := api.ServerSaveImageInput{
|
||||
Name: input.Name,
|
||||
}
|
||||
input.ImageId, err = self.PrepareSaveImage(ctx, userCred, opts)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "PrepareSaveImage")
|
||||
}
|
||||
return nil, self.StartDiskSaveTask(ctx, userCred, input, "")
|
||||
}
|
||||
|
||||
func (self *SDisk) StartDiskSaveTask(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, parentTaskId string) error {
|
||||
self.SetStatus(userCred, api.DISK_START_SAVE, "")
|
||||
if task, err := taskman.TaskManager.NewTask(ctx, "DiskSaveTask", self, userCred, data, parentTaskId, "", nil); err != nil {
|
||||
log.Errorf("Start DiskSaveTask failed:%v", err)
|
||||
return err
|
||||
} else {
|
||||
task.ScheduleRun(nil)
|
||||
func (self *SDisk) StartDiskSaveTask(ctx context.Context, userCred mcclient.TokenCredential, input api.DiskSaveInput, parentTaskId string) error {
|
||||
data := jsonutils.Marshal(input).(*jsonutils.JSONDict)
|
||||
task, err := taskman.TaskManager.NewTask(ctx, "DiskSaveTask", self, userCred, data, parentTaskId, "", nil)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "NewTask")
|
||||
}
|
||||
self.SetStatus(userCred, api.DISK_START_SAVE, "")
|
||||
task.ScheduleRun(nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ import (
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/notifyclient"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/policy"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/userdata"
|
||||
"yunion.io/x/onecloud/pkg/cloudprovider"
|
||||
"yunion.io/x/onecloud/pkg/compute/options"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
@@ -178,51 +179,50 @@ func (self *SGuest) AllowPerformSaveImage(ctx context.Context, userCred mcclient
|
||||
return self.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, self, "save-image")
|
||||
}
|
||||
|
||||
func (self *SGuest) PerformSaveImage(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
if !utils.IsInStringArray(self.Status, []string{api.VM_READY}) {
|
||||
func (self *SGuest) PerformSaveImage(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ServerSaveImageInput) (jsonutils.JSONObject, error) {
|
||||
if !utils.IsInStringArray(self.Status, []string{api.VM_READY, api.VM_RUNNING}) {
|
||||
return nil, httperrors.NewInputParameterError("Cannot save image in status %s", self.Status)
|
||||
} else if !data.Contains("name") && !data.Contains("generate_name") {
|
||||
}
|
||||
input.Restart = (self.Status == api.VM_RUNNING) || input.AutoStart
|
||||
if len(input.Name) == 0 && len(input.GenerateName) == 0 {
|
||||
return nil, httperrors.NewInputParameterError("Image name is required")
|
||||
} else if disks := self.CategorizeDisks(); disks.Root == nil {
|
||||
}
|
||||
disks := self.CategorizeDisks()
|
||||
if disks.Root == nil {
|
||||
return nil, httperrors.NewInputParameterError("No root image")
|
||||
} else {
|
||||
kwargs := data.(*jsonutils.JSONDict)
|
||||
restart := (self.Status == api.VM_RUNNING) || jsonutils.QueryBoolean(data, "auto_start", false)
|
||||
properties := jsonutils.NewDict()
|
||||
if notes, err := data.GetString("notes"); err != nil && len(notes) > 0 {
|
||||
properties.Add(jsonutils.NewString(notes), "notes")
|
||||
}
|
||||
input.OsType = self.OsType
|
||||
if len(input.OsType) == 0 {
|
||||
input.OsType = "Linux"
|
||||
}
|
||||
input.OsArch = self.OsArch
|
||||
if self.OsArch == api.OS_ARCH_ARM {
|
||||
if osArch := self.GetMetadata("os_arch", nil); len(osArch) == 0 {
|
||||
host := self.GetHost()
|
||||
input.OsArch = host.CpuArchitecture
|
||||
}
|
||||
osType := self.OsType
|
||||
if len(osType) == 0 {
|
||||
osType = "Linux"
|
||||
}
|
||||
properties.Add(jsonutils.NewString(osType), "os_type")
|
||||
if self.OsArch == api.OS_ARCH_ARM {
|
||||
var osArch string
|
||||
if osArch = self.GetMetadata("os_arch", nil); len(osArch) == 0 {
|
||||
host := self.GetHost()
|
||||
osArch = host.CpuArchitecture
|
||||
}
|
||||
properties.Add(jsonutils.NewString(osArch), "os_arch")
|
||||
kwargs.Set("os_arch", jsonutils.NewString(self.OsArch))
|
||||
}
|
||||
kwargs.Add(properties, "properties")
|
||||
kwargs.Add(jsonutils.NewBool(restart), "restart")
|
||||
}
|
||||
|
||||
factory, _ := cloudprovider.GetProviderFactory(self.GetDriver().GetProvider())
|
||||
if factory == nil || factory.IsOnPremise() { // OneCloud or VMware
|
||||
lockman.LockObject(ctx, disks.Root)
|
||||
defer lockman.ReleaseObject(ctx, disks.Root)
|
||||
|
||||
if imageId, err := disks.Root.PrepareSaveImage(ctx, userCred, kwargs); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
kwargs.Add(jsonutils.NewString(imageId), "image_id")
|
||||
var err error
|
||||
input.ImageId, err = disks.Root.PrepareSaveImage(ctx, userCred, input)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "PrepareSaveImage")
|
||||
}
|
||||
return nil, self.StartGuestSaveImage(ctx, userCred, kwargs, "")
|
||||
}
|
||||
if len(input.Name) == 0 {
|
||||
input.Name = input.GenerateName
|
||||
}
|
||||
|
||||
return nil, self.StartGuestSaveImage(ctx, userCred, input, "")
|
||||
}
|
||||
|
||||
func (self *SGuest) StartGuestSaveImage(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, parentTaskId string) error {
|
||||
return self.GetDriver().StartGuestSaveImage(ctx, userCred, self, data, parentTaskId)
|
||||
func (self *SGuest) StartGuestSaveImage(ctx context.Context, userCred mcclient.TokenCredential, input api.ServerSaveImageInput, parentTaskId string) error {
|
||||
return self.GetDriver().StartGuestSaveImage(ctx, userCred, self, jsonutils.Marshal(input).(*jsonutils.JSONDict), parentTaskId)
|
||||
}
|
||||
|
||||
func (self *SGuest) AllowPerformSaveGuestImage(ctx context.Context, userCred mcclient.TokenCredential,
|
||||
|
||||
@@ -116,6 +116,7 @@ type IGuestDriver interface {
|
||||
StartGuestSaveImage(ctx context.Context, userCred mcclient.TokenCredential, guest *SGuest, params *jsonutils.JSONDict, parentTaskId string) error
|
||||
|
||||
StartGuestSaveGuestImage(ctx context.Context, userCred mcclient.TokenCredential, guest *SGuest, params *jsonutils.JSONDict, parentTaskId string) error
|
||||
RequestSaveImage(ctx context.Context, userCred mcclient.TokenCredential, guest *SGuest, task taskman.ITask) error
|
||||
|
||||
RequestStopGuestForDelete(ctx context.Context, guest *SGuest, host *SHost, task taskman.ITask) error
|
||||
|
||||
|
||||
@@ -37,29 +37,33 @@ func init() {
|
||||
func (self *GuestSaveImageTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) {
|
||||
guest := obj.(*models.SGuest)
|
||||
log.Infof("Saving server image: %s", guest.Name)
|
||||
if restart, _ := self.GetParams().Bool("restart"); restart {
|
||||
restart := jsonutils.QueryBoolean(self.Params, "restart", false)
|
||||
if restart {
|
||||
self.SetStage("OnStopServerComplete", nil)
|
||||
guest.StartGuestStopTask(ctx, self.GetUserCred(), false, false, self.GetTaskId())
|
||||
} else {
|
||||
self.OnStopServerComplete(ctx, guest, nil)
|
||||
return
|
||||
}
|
||||
self.OnStopServerComplete(ctx, guest, nil)
|
||||
}
|
||||
|
||||
func (self *GuestSaveImageTask) OnStopServerComplete(ctx context.Context, guest *models.SGuest, body jsonutils.JSONObject) {
|
||||
self.SetStage("OnSaveRootImageComplete", nil)
|
||||
disks := guest.CategorizeDisks()
|
||||
if err := disks.Root.StartDiskSaveTask(ctx, self.GetUserCred(), self.GetParams(), self.GetTaskId()); err != nil {
|
||||
self.SetStageFailed(ctx, jsonutils.NewString(err.Error()))
|
||||
err := guest.GetDriver().RequestSaveImage(ctx, self.GetUserCred(), guest, self)
|
||||
if err != nil {
|
||||
self.OnSaveRootImageCompleteFailed(ctx, guest, jsonutils.NewString(err.Error()))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (self *GuestSaveImageTask) OnSaveRootImageComplete(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) {
|
||||
if restart, _ := self.GetParams().Bool("restart"); restart {
|
||||
restart := jsonutils.QueryBoolean(self.Params, "restart", false)
|
||||
if restart {
|
||||
self.SetStage("OnStartServerComplete", nil)
|
||||
guest.StartGueststartTask(ctx, self.GetUserCred(), nil, self.GetTaskId())
|
||||
} else {
|
||||
self.SetStageComplete(ctx, nil)
|
||||
return
|
||||
}
|
||||
self.SetStage("OnSyncstatusComplete", nil)
|
||||
guest.StartSyncstatus(ctx, self.GetUserCred(), self.GetTaskId())
|
||||
}
|
||||
|
||||
func (self *GuestSaveImageTask) OnSaveRootImageCompleteFailed(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) {
|
||||
@@ -73,5 +77,13 @@ func (self *GuestSaveImageTask) OnStartServerComplete(ctx context.Context, guest
|
||||
}
|
||||
|
||||
func (self *GuestSaveImageTask) OnStartServerCompleteFailed(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) {
|
||||
self.SetStageFailed(ctx, nil)
|
||||
}
|
||||
|
||||
func (self *GuestSaveImageTask) OnSyncstatusComplete(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) {
|
||||
self.SetStageComplete(ctx, nil)
|
||||
}
|
||||
|
||||
func (self *GuestSaveImageTask) OnSyncstatusCompleteFailed(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) {
|
||||
self.SetStageFailed(ctx, nil)
|
||||
}
|
||||
|
||||
@@ -48,13 +48,15 @@ func (self *GuestSaveGuestImageTask) OnInit(ctx context.Context, obj db.IStandal
|
||||
for index, dataDisk := range disks.Data {
|
||||
params := jsonutils.DeepCopy(self.Params).(*jsonutils.JSONDict)
|
||||
params.Add(imageIds[index], "image_id")
|
||||
if err := dataDisk.StartDiskSaveTask(ctx, self.UserCred, params, self.GetTaskId()); err != nil {
|
||||
opts := api.DiskSaveInput{ImageId: imageIds[index].String()}
|
||||
if err := dataDisk.StartDiskSaveTask(ctx, self.UserCred, opts, self.GetTaskId()); err != nil {
|
||||
self.taskFailed(ctx, guest, jsonutils.NewString(err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
self.Params.Add(imageIds[len(imageIds)-1], "image_id")
|
||||
if err := disks.Root.StartDiskSaveTask(ctx, self.UserCred, self.Params, self.GetTaskId()); err != nil {
|
||||
opts := api.DiskSaveInput{ImageId: imageIds[len(imageIds)-1].String()}
|
||||
if err := disks.Root.StartDiskSaveTask(ctx, self.UserCred, opts, self.GetTaskId()); err != nil {
|
||||
self.taskFailed(ctx, guest, jsonutils.NewString(err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1090,3 +1090,35 @@ func (self *SInstance) SetAutoRenew(autoRenew bool) error {
|
||||
func (self *SInstance) SetMetadata(tags map[string]string, replace bool) error {
|
||||
return self.host.zone.region.SetResourceTags("ecs", "instance", []string{self.InstanceId}, tags, replace)
|
||||
}
|
||||
|
||||
func (self *SRegion) SaveImage(instanceId string, opts *cloudprovider.SaveImageOptions) (*SImage, error) {
|
||||
params := map[string]string{
|
||||
"InstanceId": instanceId,
|
||||
"ImageName": opts.Name,
|
||||
"Description": opts.Notes,
|
||||
"ClientToken": utils.GenRequestId(20),
|
||||
}
|
||||
resp, err := self.ecsRequest("CreateImage", params)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "CreateImage")
|
||||
}
|
||||
ret := struct{ ImageId string }{}
|
||||
err = resp.Unmarshal(&ret)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "resp.Unmarshal")
|
||||
}
|
||||
image, err := self.GetImage(ret.ImageId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "GetImage(%s)", ret.ImageId)
|
||||
}
|
||||
image.storageCache = self.getStoragecache()
|
||||
return image, nil
|
||||
}
|
||||
|
||||
func (self *SInstance) SaveImage(opts *cloudprovider.SaveImageOptions) (cloudprovider.ICloudImage, error) {
|
||||
image, err := self.host.zone.region.SaveImage(self.InstanceId, opts)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "SaveImage(%s)", opts.Name)
|
||||
}
|
||||
return image, nil
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ package shell
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudprovider"
|
||||
"yunion.io/x/onecloud/pkg/multicloud/aliyun"
|
||||
"yunion.io/x/onecloud/pkg/util/shellutils"
|
||||
)
|
||||
@@ -212,4 +213,22 @@ func init() {
|
||||
return cli.SetInstanceAutoRenew(args.ID, args.AutoRenew)
|
||||
})
|
||||
|
||||
type InstanceSaveImageOptions struct {
|
||||
ID string `help:"Instance ID"`
|
||||
IMAGE_NAME string `help:"Image name"`
|
||||
Notes string `hlep:"Image desc"`
|
||||
}
|
||||
shellutils.R(&InstanceSaveImageOptions{}, "instance-save-image", "Save instance to image", func(cli *aliyun.SRegion, args *InstanceSaveImageOptions) error {
|
||||
opts := cloudprovider.SaveImageOptions{
|
||||
Name: args.IMAGE_NAME,
|
||||
Notes: args.Notes,
|
||||
}
|
||||
image, err := cli.SaveImage(args.ID, &opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(image)
|
||||
return nil
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@@ -1230,3 +1230,45 @@ func (self *SInstance) GetArn() string {
|
||||
}
|
||||
return fmt.Sprintf("arn:%s:ec2:%s:%s:instance/%s", partition, self.host.zone.region.GetId(), self.GetAccountId(), self.InstanceId)
|
||||
}
|
||||
|
||||
func (self *SRegion) SaveImage(instanceId string, opts *cloudprovider.SaveImageOptions) (*SImage, error) {
|
||||
params := map[string]string{
|
||||
"Description": opts.Notes,
|
||||
"InstanceId": instanceId,
|
||||
"Name": opts.Name,
|
||||
}
|
||||
ret := struct {
|
||||
ImageId string `xml:"imageId"`
|
||||
}{}
|
||||
err := self.ec2Request("CreateImage", params, &ret)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "CreateImage")
|
||||
}
|
||||
err = cloudprovider.Wait(time.Second*10, time.Minute*5, func() (bool, error) {
|
||||
_, err := self.GetImage(ret.ImageId)
|
||||
if err != nil {
|
||||
if errors.Cause(err) == cloudprovider.ErrNotFound {
|
||||
return false, nil
|
||||
}
|
||||
return false, errors.Wrapf(err, "GetImage(%s)", ret.ImageId)
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "wait for image created")
|
||||
}
|
||||
image, err := self.GetImage(ret.ImageId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "GetImage(%s)", ret.ImageId)
|
||||
}
|
||||
image.storageCache = self.getStoragecache()
|
||||
return image, nil
|
||||
}
|
||||
|
||||
func (self *SInstance) SaveImage(opts *cloudprovider.SaveImageOptions) (cloudprovider.ICloudImage, error) {
|
||||
image, err := self.host.zone.region.SaveImage(self.InstanceId, opts)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "SaveImage")
|
||||
}
|
||||
return image, nil
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudprovider"
|
||||
"yunion.io/x/onecloud/pkg/multicloud/aws"
|
||||
"yunion.io/x/onecloud/pkg/util/shellutils"
|
||||
)
|
||||
@@ -177,4 +178,23 @@ func init() {
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
type InstanceSaveImageOptions struct {
|
||||
ID string `help:"Instance ID"`
|
||||
IMAGE_NAME string `help:"Image name"`
|
||||
Notes string `hlep:"Image desc"`
|
||||
}
|
||||
shellutils.R(&InstanceSaveImageOptions{}, "instance-save-image", "Save instance to image", func(cli *aws.SRegion, args *InstanceSaveImageOptions) error {
|
||||
opts := cloudprovider.SaveImageOptions{
|
||||
Name: args.IMAGE_NAME,
|
||||
Notes: args.Notes,
|
||||
}
|
||||
image, err := cli.SaveImage(args.ID, &opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(image)
|
||||
return nil
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@@ -906,3 +906,39 @@ func (self *SInstance) GetProjectId() string {
|
||||
func (self *SInstance) GetError() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SRegion) SaveImage(osType, diskId string, opts *cloudprovider.SaveImageOptions) (*SImage, error) {
|
||||
params := map[string]interface{}{
|
||||
"Location": self.Name,
|
||||
"Name": opts.Name,
|
||||
"Properties": map[string]interface{}{
|
||||
"storageProfile": map[string]interface{}{
|
||||
"osDisk": map[string]interface{}{
|
||||
"osType": osType,
|
||||
"managedDisk": map[string]string{
|
||||
"id": diskId,
|
||||
},
|
||||
"osState": "Generalized",
|
||||
},
|
||||
},
|
||||
},
|
||||
"Type": "Microsoft.Compute/images",
|
||||
}
|
||||
image := &SImage{storageCache: self.getStoragecache()}
|
||||
err := self.create("", jsonutils.Marshal(params), image)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "create image")
|
||||
}
|
||||
return image, nil
|
||||
}
|
||||
|
||||
func (self *SInstance) SaveImage(opts *cloudprovider.SaveImageOptions) (cloudprovider.ICloudImage, error) {
|
||||
if self.Properties.StorageProfile.OsDisk.ManagedDisk == nil {
|
||||
return nil, fmt.Errorf("invalid os disk for save image")
|
||||
}
|
||||
image, err := self.host.zone.region.SaveImage(self.GetOSType(), self.Properties.StorageProfile.OsDisk.ManagedDisk.ID, opts)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "SaveImage")
|
||||
}
|
||||
return image, nil
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudprovider"
|
||||
"yunion.io/x/onecloud/pkg/multicloud/azure"
|
||||
"yunion.io/x/onecloud/pkg/util/shellutils"
|
||||
)
|
||||
@@ -195,4 +196,24 @@ func init() {
|
||||
shellutils.R(&InstanceSecurityGroupOptions{}, "instance-set-secgrp", "Attach a disk to intance", func(cli *azure.SRegion, args *InstanceSecurityGroupOptions) error {
|
||||
return cli.SetSecurityGroup(args.ID, args.SecurityGroup)
|
||||
})
|
||||
|
||||
type InstanceSaveImageOptions struct {
|
||||
DISK_ID string `help:"Instance Os Disk ID"`
|
||||
IMAGE_NAME string `help:"Image name"`
|
||||
Notes string `hlep:"Image desc"`
|
||||
OsType string `help:"Os Type" choices:"Linux|Windows" default:"Linux"`
|
||||
}
|
||||
shellutils.R(&InstanceSaveImageOptions{}, "instance-save-image", "Save instance to image", func(cli *azure.SRegion, args *InstanceSaveImageOptions) error {
|
||||
opts := cloudprovider.SaveImageOptions{
|
||||
Name: args.IMAGE_NAME,
|
||||
Notes: args.Notes,
|
||||
}
|
||||
image, err := cli.SaveImage(args.OsType, args.DISK_ID, &opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(image)
|
||||
return nil
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@@ -878,3 +878,31 @@ func (region *SRegion) RebuildRoot(instanceId string, imageId string, sysDiskSiz
|
||||
}
|
||||
return disk.GetGlobalId(), nil
|
||||
}
|
||||
|
||||
func (self *SRegion) SaveImage(diskId string, opts *cloudprovider.SaveImageOptions) (*SImage, error) {
|
||||
params := map[string]interface{}{
|
||||
"name": opts.Name,
|
||||
"description": opts.Notes,
|
||||
"sourceDisk": diskId,
|
||||
}
|
||||
image := &SImage{}
|
||||
err := self.Insert("global/images", jsonutils.Marshal(params), image)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Insert")
|
||||
}
|
||||
image.storagecache = self.getStoragecache()
|
||||
return image, nil
|
||||
}
|
||||
|
||||
func (self *SInstance) SaveImage(opts *cloudprovider.SaveImageOptions) (cloudprovider.ICloudImage, error) {
|
||||
for i := range self.Disks {
|
||||
if self.Disks[0].Index == 0 {
|
||||
image, err := self.host.zone.region.SaveImage(self.Disks[i].Source, opts)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "SaveImage")
|
||||
}
|
||||
return image, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.Wrapf(cloudprovider.ErrNotFound, "no valid system disk found")
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudprovider"
|
||||
"yunion.io/x/onecloud/pkg/multicloud/google"
|
||||
"yunion.io/x/onecloud/pkg/util/shellutils"
|
||||
)
|
||||
@@ -184,4 +185,22 @@ func init() {
|
||||
return nil
|
||||
})
|
||||
|
||||
type InstanceSaveImageOptions struct {
|
||||
DISK_ID string `help:"Instance System disk ID"`
|
||||
IMAGE_NAME string `help:"Image name"`
|
||||
Notes string `hlep:"Image desc"`
|
||||
}
|
||||
shellutils.R(&InstanceSaveImageOptions{}, "instance-save-image", "Save instance to image", func(cli *google.SRegion, args *InstanceSaveImageOptions) error {
|
||||
opts := cloudprovider.SaveImageOptions{
|
||||
Name: args.IMAGE_NAME,
|
||||
Notes: args.Notes,
|
||||
}
|
||||
image, err := cli.SaveImage(args.DISK_ID, &opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(image)
|
||||
return nil
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@@ -213,7 +213,7 @@ func (region *SRegion) GetIStoragecaches() ([]cloudprovider.ICloudStoragecache,
|
||||
return []cloudprovider.ICloudStoragecache{cache}, nil
|
||||
}
|
||||
|
||||
func (region *SRegion) getStoragecache() cloudprovider.ICloudStoragecache {
|
||||
func (region *SRegion) getStoragecache() *SStoragecache {
|
||||
return &SStoragecache{region: region}
|
||||
}
|
||||
|
||||
|
||||
@@ -1417,3 +1417,51 @@ func updateWindowsUserData(userData string, osVersion string, username, password
|
||||
|
||||
return base64.StdEncoding.EncodeToString([]byte(shells)), nil
|
||||
}
|
||||
|
||||
func (self *SRegion) SaveImage(instanceId string, opts *cloudprovider.SaveImageOptions) (*SImage, error) {
|
||||
params := map[string]string{
|
||||
"name": opts.Name,
|
||||
"instance_id": instanceId,
|
||||
}
|
||||
if len(opts.Notes) > 0 {
|
||||
params["description"] = func() string {
|
||||
opts.Notes = strings.ReplaceAll(opts.Notes, "<", "")
|
||||
opts.Notes = strings.ReplaceAll(opts.Notes, ">", "")
|
||||
opts.Notes = strings.ReplaceAll(opts.Notes, "\n", "")
|
||||
if len(opts.Notes) > 1024 {
|
||||
opts.Notes = opts.Notes[:1024]
|
||||
}
|
||||
return opts.Notes
|
||||
}()
|
||||
}
|
||||
resp, err := self.ecsClient.Images.CreateInContextWithSpec(nil, "action", jsonutils.Marshal(params), "")
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Images.Create")
|
||||
}
|
||||
jobId, err := resp.GetString("job_id")
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "resp.GetString(job_id)")
|
||||
}
|
||||
err = self.waitTaskStatus(self.ecsClient.Images.ServiceType(), jobId, TASK_SUCCESS, 15*time.Second, 10*time.Minute)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "waitTaskStatus")
|
||||
}
|
||||
imageId, err := self.GetTaskEntityID(self.ecsClient.Images.ServiceType(), jobId, "image_id")
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "GetTaskEntityID")
|
||||
}
|
||||
image, err := self.GetImage(imageId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "GetImage(%s)", imageId)
|
||||
}
|
||||
image.storageCache = self.getStoragecache()
|
||||
return image, nil
|
||||
}
|
||||
|
||||
func (self *SInstance) SaveImage(opts *cloudprovider.SaveImageOptions) (cloudprovider.ICloudImage, error) {
|
||||
image, err := self.host.zone.region.SaveImage(self.ID, opts)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "SaveImage")
|
||||
}
|
||||
return image, nil
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudprovider"
|
||||
"yunion.io/x/onecloud/pkg/multicloud/huawei"
|
||||
"yunion.io/x/onecloud/pkg/util/shellutils"
|
||||
)
|
||||
@@ -170,4 +171,23 @@ func init() {
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
type InstanceSaveImageOptions struct {
|
||||
ID string `help:"Instance ID"`
|
||||
IMAGE_NAME string `help:"Image name"`
|
||||
Notes string `hlep:"Image desc"`
|
||||
}
|
||||
shellutils.R(&InstanceSaveImageOptions{}, "instance-save-image", "Save instance to image", func(cli *huawei.SRegion, args *InstanceSaveImageOptions) error {
|
||||
opts := cloudprovider.SaveImageOptions{
|
||||
Name: args.IMAGE_NAME,
|
||||
Notes: args.Notes,
|
||||
}
|
||||
image, err := cli.SaveImage(args.ID, &opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(image)
|
||||
return nil
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ package multicloud
|
||||
import (
|
||||
"context"
|
||||
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudprovider"
|
||||
)
|
||||
|
||||
@@ -64,3 +66,7 @@ func (instance *SInstanceBase) CreateInstanceSnapshot(ctx context.Context, name
|
||||
func (instance *SInstanceBase) ResetToInstanceSnapshot(ctx context.Context, idStr string) error {
|
||||
return cloudprovider.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (self *SInstanceBase) SaveImage(opts *cloudprovider.SaveImageOptions) (cloudprovider.ICloudImage, error) {
|
||||
return nil, errors.Wrapf(cloudprovider.ErrNotImplemented, "SaveImage")
|
||||
}
|
||||
|
||||
@@ -74,27 +74,29 @@ func (self *SRegion) GetImages(status string, owner string, imageIds []string, n
|
||||
params["Limit"] = fmt.Sprintf("%d", limit)
|
||||
params["Offset"] = fmt.Sprintf("%d", offset)
|
||||
|
||||
filter := 0
|
||||
if len(status) > 0 {
|
||||
params[fmt.Sprintf("Filters.%d.Name", filter)] = "image-state"
|
||||
params[fmt.Sprintf("Filters.%d.Values.0", filter)] = status
|
||||
filter++
|
||||
}
|
||||
if imageIds != nil && len(imageIds) > 0 {
|
||||
for index, imageId := range imageIds {
|
||||
params[fmt.Sprintf("ImageIds.%d", index)] = imageId
|
||||
}
|
||||
}
|
||||
if len(owner) > 0 {
|
||||
params[fmt.Sprintf("Filters.%d.Name", filter)] = "image-type"
|
||||
params[fmt.Sprintf("Filters.%d.Values.0", filter)] = owner
|
||||
filter++
|
||||
for index, imageId := range imageIds {
|
||||
params[fmt.Sprintf("ImageIds.%d", index)] = imageId
|
||||
}
|
||||
|
||||
if len(name) > 0 {
|
||||
params[fmt.Sprintf("Filters.%d.Name", filter)] = "image-name"
|
||||
params[fmt.Sprintf("Filters.%d.Values.0", filter)] = name
|
||||
filter++
|
||||
if len(imageIds) == 0 { // imageIds 不能和Filter同时查询
|
||||
filter := 0
|
||||
if len(status) > 0 {
|
||||
params[fmt.Sprintf("Filters.%d.Name", filter)] = "image-state"
|
||||
params[fmt.Sprintf("Filters.%d.Values.0", filter)] = status
|
||||
filter++
|
||||
}
|
||||
|
||||
if len(owner) > 0 {
|
||||
params[fmt.Sprintf("Filters.%d.Name", filter)] = "image-type"
|
||||
params[fmt.Sprintf("Filters.%d.Values.0", filter)] = owner
|
||||
filter++
|
||||
}
|
||||
|
||||
if len(name) > 0 {
|
||||
params[fmt.Sprintf("Filters.%d.Name", filter)] = "image-name"
|
||||
params[fmt.Sprintf("Filters.%d.Values.0", filter)] = name
|
||||
filter++
|
||||
}
|
||||
}
|
||||
|
||||
images := make([]SImage, 0)
|
||||
|
||||
@@ -1009,3 +1009,46 @@ func (self *SInstance) SetAutoRenew(autoRenew bool) error {
|
||||
func (self *SInstance) SetMetadata(tags map[string]string, replace bool) error {
|
||||
return self.host.zone.region.SetResourceTags("cvm", "instance", []string{self.InstanceId}, tags, replace)
|
||||
}
|
||||
|
||||
func (self *SRegion) SaveImage(instanceId string, opts *cloudprovider.SaveImageOptions) (*SImage, error) {
|
||||
params := map[string]string{
|
||||
"ImageName": opts.Name,
|
||||
"InstanceId": instanceId,
|
||||
}
|
||||
if len(opts.Notes) > 0 {
|
||||
params["ImageDescription"] = opts.Notes
|
||||
}
|
||||
resp, err := self.cvmRequest("CreateImage", params, true)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "CreateImage")
|
||||
}
|
||||
ret := struct{ ImageId string }{}
|
||||
err = resp.Unmarshal(&ret)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "resp.Unmarshal")
|
||||
}
|
||||
imageIds := []string{}
|
||||
if len(ret.ImageId) > 0 {
|
||||
imageIds = append(imageIds, ret.ImageId)
|
||||
}
|
||||
|
||||
images, _, err := self.GetImages("", "PRIVATE_IMAGE", imageIds, opts.Name, 0, 1)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "GetImage(%s,%s)", opts.Name, ret.ImageId)
|
||||
}
|
||||
for i := range images {
|
||||
if images[i].ImageId == ret.ImageId || images[i].ImageName == opts.Name {
|
||||
images[i].storageCache = self.getStoragecache()
|
||||
return &images[i], nil
|
||||
}
|
||||
}
|
||||
return nil, errors.Wrapf(cloudprovider.ErrNotFound, "after save image %s", opts.Name)
|
||||
}
|
||||
|
||||
func (self *SInstance) SaveImage(opts *cloudprovider.SaveImageOptions) (cloudprovider.ICloudImage, error) {
|
||||
image, err := self.host.zone.region.SaveImage(self.InstanceId, opts)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "SaveImage")
|
||||
}
|
||||
return image, nil
|
||||
}
|
||||
|
||||
@@ -223,4 +223,22 @@ func init() {
|
||||
return cli.SetInstanceAutoRenew(args.ID, args.AutoRenew)
|
||||
})
|
||||
|
||||
type InstanceSaveImageOptions struct {
|
||||
ID string `help:"Instance ID"`
|
||||
IMAGE_NAME string `help:"Image name"`
|
||||
Notes string `hlep:"Image desc"`
|
||||
}
|
||||
shellutils.R(&InstanceSaveImageOptions{}, "instance-save-image", "Save instance to image", func(cli *qcloud.SRegion, args *InstanceSaveImageOptions) error {
|
||||
opts := cloudprovider.SaveImageOptions{
|
||||
Name: args.IMAGE_NAME,
|
||||
Notes: args.Notes,
|
||||
}
|
||||
image, err := cli.SaveImage(args.ID, &opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(image)
|
||||
return nil
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user