github.com/sacloud/libsacloud/v2@v2.32.3/helper/builder/disk/builder.go (about)

     1  // Copyright 2016-2022 The Libsacloud Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package disk
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"fmt"
    21  	"reflect"
    22  
    23  	"github.com/sacloud/libsacloud/v2/helper/builder"
    24  	"github.com/sacloud/libsacloud/v2/pkg/size"
    25  
    26  	archiveUtil "github.com/sacloud/libsacloud/v2/helper/query"
    27  	"github.com/sacloud/libsacloud/v2/sacloud"
    28  	"github.com/sacloud/libsacloud/v2/sacloud/ostype"
    29  	"github.com/sacloud/libsacloud/v2/sacloud/types"
    30  )
    31  
    32  // Builder ディスクの構築インターフェース
    33  type Builder interface {
    34  	Validate(ctx context.Context, zone string) error
    35  	Build(ctx context.Context, zone string, serverID types.ID) (*BuildResult, error)
    36  	Update(ctx context.Context, zone string) (*UpdateResult, error)
    37  	DiskID() types.ID
    38  	UpdateLevel(ctx context.Context, zone string, disk *sacloud.Disk) builder.UpdateLevel
    39  	NoWaitFlag() bool
    40  }
    41  
    42  // BuildResult ディスク構築結果
    43  type BuildResult struct {
    44  	DiskID          types.ID
    45  	GeneratedSSHKey *sacloud.SSHKeyGenerated
    46  }
    47  
    48  // UpdateResult ディスク更新結果
    49  type UpdateResult struct {
    50  	Disk *sacloud.Disk
    51  }
    52  
    53  // FromUnixBuilder Unix系パブリックアーカイブからディスクを作成するリクエスト
    54  type FromUnixBuilder struct {
    55  	OSType ostype.ArchiveOSType
    56  
    57  	Name        string
    58  	SizeGB      int
    59  	DistantFrom []types.ID
    60  	PlanID      types.ID
    61  	Connection  types.EDiskConnection
    62  	Description string
    63  	Tags        types.Tags
    64  	IconID      types.ID
    65  
    66  	EditParameter *UnixEditRequest
    67  
    68  	Client *APIClient
    69  	NoWait bool
    70  
    71  	ID types.ID
    72  
    73  	generatedSSHKey *sacloud.SSHKeyGenerated
    74  	generatedNotes  []*sacloud.Note
    75  }
    76  
    77  // Validate 設定値の検証
    78  func (d *FromUnixBuilder) Validate(ctx context.Context, zone string) error {
    79  	if !d.OSType.IsSupportDiskEdit() {
    80  		return fmt.Errorf("invalid OSType: %s", d.OSType.String())
    81  	}
    82  	if err := validateDiskPlan(ctx, d.Client, zone, d.PlanID, d.SizeGB); err != nil {
    83  		return err
    84  	}
    85  
    86  	if d.EditParameter != nil {
    87  		return d.EditParameter.Validate(ctx, d.Client)
    88  	}
    89  	return nil
    90  }
    91  
    92  // Build ディスクの構築
    93  func (d *FromUnixBuilder) Build(ctx context.Context, zone string, serverID types.ID) (*BuildResult, error) {
    94  	res, err := build(ctx, d.Client, zone, serverID, d.DistantFrom, d)
    95  	if err != nil {
    96  		return nil, err
    97  	}
    98  	d.ID = res.DiskID
    99  
   100  	if d.generatedSSHKey != nil {
   101  		res.GeneratedSSHKey = d.generatedSSHKey
   102  	}
   103  
   104  	if d.EditParameter != nil {
   105  		if d.EditParameter.IsSSHKeysEphemeral {
   106  			if err := d.Client.SSHKey.Delete(ctx, d.generatedSSHKey.ID); err != nil {
   107  				return nil, err
   108  			}
   109  		}
   110  		if d.EditParameter.IsNotesEphemeral {
   111  			for _, note := range d.generatedNotes {
   112  				if err := d.Client.Note.Delete(ctx, note.ID); err != nil {
   113  					return nil, err
   114  				}
   115  			}
   116  		}
   117  	}
   118  	return res, nil
   119  }
   120  
   121  // Update ディスクの更新
   122  func (d *FromUnixBuilder) Update(ctx context.Context, zone string) (*UpdateResult, error) {
   123  	return update(ctx, d.Client, zone, d)
   124  }
   125  
   126  // DiskID ディスクID取得
   127  func (d *FromUnixBuilder) DiskID() types.ID {
   128  	return d.ID
   129  }
   130  
   131  // UpdateLevel Update時にどのレベルの変更が必要か
   132  func (d *FromUnixBuilder) UpdateLevel(ctx context.Context, zone string, disk *sacloud.Disk) builder.UpdateLevel {
   133  	return updateLevel(disk, d.EditParameter != nil, d)
   134  }
   135  
   136  func (d *FromUnixBuilder) updateDiskParameter() *sacloud.DiskUpdateRequest {
   137  	return &sacloud.DiskUpdateRequest{
   138  		Name:        d.Name,
   139  		Description: d.Description,
   140  		Tags:        d.Tags,
   141  		IconID:      d.IconID,
   142  		Connection:  d.Connection,
   143  	}
   144  }
   145  
   146  func (d *FromUnixBuilder) createDiskParameter(ctx context.Context, client *APIClient, zone string, serverID types.ID) (*sacloud.DiskCreateRequest, *sacloud.DiskEditRequest, error) {
   147  	archive, err := archiveUtil.FindArchiveByOSType(ctx, client.Archive, zone, d.OSType)
   148  	if err != nil {
   149  		return nil, nil, err
   150  	}
   151  
   152  	createReq := &sacloud.DiskCreateRequest{
   153  		DiskPlanID:      d.PlanID,
   154  		SizeMB:          d.SizeGB * size.GiB,
   155  		Connection:      d.Connection,
   156  		SourceArchiveID: archive.ID,
   157  		ServerID:        serverID,
   158  		Name:            d.Name,
   159  		Description:     d.Description,
   160  		Tags:            d.Tags,
   161  		IconID:          d.IconID,
   162  	}
   163  
   164  	var editReq *sacloud.DiskEditRequest
   165  	if d.EditParameter != nil {
   166  		req, sshKey, notes, err := d.EditParameter.prepareDiskEditParameter(ctx, client)
   167  		if err != nil {
   168  			return nil, nil, err
   169  		}
   170  		editReq = req
   171  		if sshKey != nil {
   172  			d.generatedSSHKey = sshKey
   173  		}
   174  		if len(notes) > 0 {
   175  			d.generatedNotes = notes
   176  		}
   177  	}
   178  
   179  	return createReq, editReq, nil
   180  }
   181  
   182  func (d *FromUnixBuilder) NoWaitFlag() bool {
   183  	return d.NoWait
   184  }
   185  
   186  // FromFixedArchiveBuilder ディスクの修正をサポートしないパブリックアーカイブからディスクを作成するリクエスト
   187  type FromFixedArchiveBuilder struct {
   188  	OSType ostype.ArchiveOSType
   189  
   190  	Name        string
   191  	SizeGB      int
   192  	DistantFrom []types.ID
   193  	PlanID      types.ID
   194  	Connection  types.EDiskConnection
   195  	Description string
   196  	Tags        types.Tags
   197  	IconID      types.ID
   198  
   199  	Client *APIClient
   200  	NoWait bool
   201  
   202  	ID types.ID
   203  
   204  	generatedSSHKey *sacloud.SSHKeyGenerated
   205  }
   206  
   207  // Validate 設定値の検証
   208  func (d *FromFixedArchiveBuilder) Validate(ctx context.Context, zone string) error {
   209  	if d.OSType.IsSupportDiskEdit() || d.OSType.IsWindows() {
   210  		return fmt.Errorf("invalid OSType: %s", d.OSType.String())
   211  	}
   212  	if err := validateDiskPlan(ctx, d.Client, zone, d.PlanID, d.SizeGB); err != nil {
   213  		return err
   214  	}
   215  
   216  	return nil
   217  }
   218  
   219  // Build ディスクの構築
   220  func (d *FromFixedArchiveBuilder) Build(ctx context.Context, zone string, serverID types.ID) (*BuildResult, error) {
   221  	res, err := build(ctx, d.Client, zone, serverID, d.DistantFrom, d)
   222  	if err != nil {
   223  		return nil, err
   224  	}
   225  	d.ID = res.DiskID
   226  	if d.generatedSSHKey != nil {
   227  		res.GeneratedSSHKey = d.generatedSSHKey
   228  	}
   229  	return res, nil
   230  }
   231  
   232  // Update ディスクの更新
   233  func (d *FromFixedArchiveBuilder) Update(ctx context.Context, zone string) (*UpdateResult, error) {
   234  	return update(ctx, d.Client, zone, d)
   235  }
   236  
   237  // DiskID ディスクID取得
   238  func (d *FromFixedArchiveBuilder) DiskID() types.ID {
   239  	return d.ID
   240  }
   241  
   242  // UpdateLevel Update時にどのレベルの変更が必要か
   243  func (d *FromFixedArchiveBuilder) UpdateLevel(ctx context.Context, zone string, disk *sacloud.Disk) builder.UpdateLevel {
   244  	return updateLevel(disk, false, d)
   245  }
   246  
   247  func (d *FromFixedArchiveBuilder) updateDiskParameter() *sacloud.DiskUpdateRequest {
   248  	return &sacloud.DiskUpdateRequest{
   249  		Name:        d.Name,
   250  		Description: d.Description,
   251  		Tags:        d.Tags,
   252  		IconID:      d.IconID,
   253  		Connection:  d.Connection,
   254  	}
   255  }
   256  
   257  func (d *FromFixedArchiveBuilder) createDiskParameter(ctx context.Context, client *APIClient, zone string, serverID types.ID) (*sacloud.DiskCreateRequest, *sacloud.DiskEditRequest, error) {
   258  	archive, err := archiveUtil.FindArchiveByOSType(ctx, client.Archive, zone, d.OSType)
   259  	if err != nil {
   260  		return nil, nil, err
   261  	}
   262  
   263  	createReq := &sacloud.DiskCreateRequest{
   264  		DiskPlanID:      d.PlanID,
   265  		SizeMB:          d.SizeGB * size.GiB,
   266  		Connection:      d.Connection,
   267  		SourceArchiveID: archive.ID,
   268  		ServerID:        serverID,
   269  		Name:            d.Name,
   270  		Description:     d.Description,
   271  		Tags:            d.Tags,
   272  		IconID:          d.IconID,
   273  	}
   274  	return createReq, nil, nil
   275  }
   276  
   277  func (d *FromFixedArchiveBuilder) NoWaitFlag() bool {
   278  	return d.NoWait
   279  }
   280  
   281  // FromWindowsBuilder Windows系パブリックアーカイブからディスクを作成するリクエスト
   282  type FromWindowsBuilder struct {
   283  	OSType ostype.ArchiveOSType
   284  
   285  	Name        string
   286  	SizeGB      int
   287  	DistantFrom []types.ID
   288  	PlanID      types.ID
   289  	Connection  types.EDiskConnection
   290  	Description string
   291  	Tags        types.Tags
   292  	IconID      types.ID
   293  
   294  	EditParameter *WindowsEditRequest
   295  
   296  	Client *APIClient
   297  	NoWait bool
   298  
   299  	ID types.ID
   300  }
   301  
   302  // Validate 設定値の検証
   303  func (d *FromWindowsBuilder) Validate(ctx context.Context, zone string) error {
   304  	if !d.OSType.IsWindows() {
   305  		return fmt.Errorf("invalid OSType: %s", d.OSType.String())
   306  	}
   307  	if err := validateDiskPlan(ctx, d.Client, zone, d.PlanID, d.SizeGB); err != nil {
   308  		return err
   309  	}
   310  	return nil
   311  }
   312  
   313  // Build ディスクの構築
   314  func (d *FromWindowsBuilder) Build(ctx context.Context, zone string, serverID types.ID) (*BuildResult, error) {
   315  	res, err := build(ctx, d.Client, zone, serverID, d.DistantFrom, d)
   316  	if err != nil {
   317  		return nil, err
   318  	}
   319  	d.ID = res.DiskID
   320  	return res, nil
   321  }
   322  
   323  // Update ディスクの更新
   324  func (d *FromWindowsBuilder) Update(ctx context.Context, zone string) (*UpdateResult, error) {
   325  	return update(ctx, d.Client, zone, d)
   326  }
   327  
   328  // DiskID ディスクID取得
   329  func (d *FromWindowsBuilder) DiskID() types.ID {
   330  	return d.ID
   331  }
   332  
   333  // UpdateLevel Update時にどのレベルの変更が必要か
   334  func (d *FromWindowsBuilder) UpdateLevel(ctx context.Context, zone string, disk *sacloud.Disk) builder.UpdateLevel {
   335  	return updateLevel(disk, d.EditParameter != nil, d)
   336  }
   337  
   338  func (d *FromWindowsBuilder) updateDiskParameter() *sacloud.DiskUpdateRequest {
   339  	return &sacloud.DiskUpdateRequest{
   340  		Name:        d.Name,
   341  		Description: d.Description,
   342  		Tags:        d.Tags,
   343  		IconID:      d.IconID,
   344  		Connection:  d.Connection,
   345  	}
   346  }
   347  
   348  func (d *FromWindowsBuilder) createDiskParameter(ctx context.Context, client *APIClient, zone string, serverID types.ID) (*sacloud.DiskCreateRequest, *sacloud.DiskEditRequest, error) {
   349  	archive, err := archiveUtil.FindArchiveByOSType(ctx, client.Archive, zone, d.OSType)
   350  	if err != nil {
   351  		return nil, nil, err
   352  	}
   353  
   354  	createReq := &sacloud.DiskCreateRequest{
   355  		DiskPlanID:      d.PlanID,
   356  		SizeMB:          d.SizeGB * size.GiB,
   357  		Connection:      d.Connection,
   358  		SourceArchiveID: archive.ID,
   359  		ServerID:        serverID,
   360  		Name:            d.Name,
   361  		Description:     d.Description,
   362  		Tags:            d.Tags,
   363  		IconID:          d.IconID,
   364  	}
   365  
   366  	var editReq *sacloud.DiskEditRequest
   367  	if d.EditParameter != nil {
   368  		editReq = d.EditParameter.prepareDiskEditParameter()
   369  	}
   370  
   371  	return createReq, editReq, nil
   372  }
   373  
   374  func (d *FromWindowsBuilder) NoWaitFlag() bool {
   375  	return d.NoWait
   376  }
   377  
   378  // FromDiskOrArchiveBuilder ディスクorアーカイブからディスクを作成するリクエスト
   379  //
   380  // ディスクの修正が可能かは実行時にさくらのクラウドAPI側にて判定される
   381  type FromDiskOrArchiveBuilder struct {
   382  	SourceDiskID    types.ID
   383  	SourceArchiveID types.ID
   384  
   385  	Name        string
   386  	SizeGB      int
   387  	DistantFrom []types.ID
   388  	PlanID      types.ID
   389  	Connection  types.EDiskConnection
   390  	Description string
   391  	Tags        types.Tags
   392  	IconID      types.ID
   393  
   394  	EditParameter *UnixEditRequest
   395  
   396  	Client *APIClient
   397  
   398  	ID     types.ID
   399  	NoWait bool
   400  
   401  	generatedSSHKey *sacloud.SSHKeyGenerated
   402  	generatedNotes  []*sacloud.Note
   403  }
   404  
   405  // Validate 設定値の検証
   406  func (d *FromDiskOrArchiveBuilder) Validate(ctx context.Context, zone string) error {
   407  	if d.SourceArchiveID.IsEmpty() && d.SourceDiskID.IsEmpty() {
   408  		return errors.New("SourceArchiveID or SourceDiskID is required")
   409  	}
   410  	if err := validateDiskPlan(ctx, d.Client, zone, d.PlanID, d.SizeGB); err != nil {
   411  		return err
   412  	}
   413  
   414  	if !d.SourceArchiveID.IsEmpty() {
   415  		if _, err := d.Client.Archive.Read(ctx, zone, d.SourceArchiveID); err != nil {
   416  			return err
   417  		}
   418  	}
   419  	if !d.SourceDiskID.IsEmpty() {
   420  		if _, err := d.Client.Disk.Read(ctx, zone, d.SourceDiskID); err != nil {
   421  			return err
   422  		}
   423  	}
   424  
   425  	return nil
   426  }
   427  
   428  // Build ディスクの構築
   429  func (d *FromDiskOrArchiveBuilder) Build(ctx context.Context, zone string, serverID types.ID) (*BuildResult, error) {
   430  	res, err := build(ctx, d.Client, zone, serverID, d.DistantFrom, d)
   431  	if err != nil {
   432  		return nil, err
   433  	}
   434  	d.ID = res.DiskID
   435  	if d.generatedSSHKey != nil {
   436  		res.GeneratedSSHKey = d.generatedSSHKey
   437  	}
   438  
   439  	if d.EditParameter != nil {
   440  		if d.EditParameter.IsSSHKeysEphemeral {
   441  			if err := d.Client.SSHKey.Delete(ctx, d.generatedSSHKey.ID); err != nil {
   442  				return nil, err
   443  			}
   444  		}
   445  		if d.EditParameter.IsNotesEphemeral {
   446  			for _, note := range d.generatedNotes {
   447  				if err := d.Client.Note.Delete(ctx, note.ID); err != nil {
   448  					return nil, err
   449  				}
   450  			}
   451  		}
   452  	}
   453  	return res, nil
   454  }
   455  
   456  // Update ディスクの更新
   457  func (d *FromDiskOrArchiveBuilder) Update(ctx context.Context, zone string) (*UpdateResult, error) {
   458  	return update(ctx, d.Client, zone, d)
   459  }
   460  
   461  // DiskID ディスクID取得
   462  func (d *FromDiskOrArchiveBuilder) DiskID() types.ID {
   463  	return d.ID
   464  }
   465  
   466  // UpdateLevel Update時にどのレベルの変更が必要か
   467  func (d *FromDiskOrArchiveBuilder) UpdateLevel(ctx context.Context, zone string, disk *sacloud.Disk) builder.UpdateLevel {
   468  	return updateLevel(disk, d.EditParameter != nil, d)
   469  }
   470  
   471  func (d *FromDiskOrArchiveBuilder) updateDiskParameter() *sacloud.DiskUpdateRequest {
   472  	return &sacloud.DiskUpdateRequest{
   473  		Name:        d.Name,
   474  		Description: d.Description,
   475  		Tags:        d.Tags,
   476  		IconID:      d.IconID,
   477  		Connection:  d.Connection,
   478  	}
   479  }
   480  
   481  func (d *FromDiskOrArchiveBuilder) createDiskParameter(ctx context.Context, client *APIClient, zone string, serverID types.ID) (*sacloud.DiskCreateRequest, *sacloud.DiskEditRequest, error) {
   482  	createReq := &sacloud.DiskCreateRequest{
   483  		DiskPlanID:      d.PlanID,
   484  		SizeMB:          d.SizeGB * size.GiB,
   485  		Connection:      d.Connection,
   486  		SourceArchiveID: d.SourceArchiveID,
   487  		SourceDiskID:    d.SourceDiskID,
   488  		ServerID:        serverID,
   489  		Name:            d.Name,
   490  		Description:     d.Description,
   491  		Tags:            d.Tags,
   492  		IconID:          d.IconID,
   493  	}
   494  
   495  	var editReq *sacloud.DiskEditRequest
   496  	if d.EditParameter != nil {
   497  		req, sshKey, notes, err := d.EditParameter.prepareDiskEditParameter(ctx, client)
   498  		if err != nil {
   499  			return nil, nil, err
   500  		}
   501  		editReq = req
   502  		if sshKey != nil {
   503  			d.generatedSSHKey = sshKey
   504  		}
   505  		if len(notes) > 0 {
   506  			d.generatedNotes = notes
   507  		}
   508  	}
   509  
   510  	return createReq, editReq, nil
   511  }
   512  
   513  func (d *FromDiskOrArchiveBuilder) NoWaitFlag() bool {
   514  	return d.NoWait
   515  }
   516  
   517  // BlankBuilder ブランクディスクを作成する場合のリクエスト
   518  type BlankBuilder struct {
   519  	Name        string
   520  	SizeGB      int
   521  	DistantFrom []types.ID
   522  	PlanID      types.ID
   523  	Connection  types.EDiskConnection
   524  	Description string
   525  	Tags        types.Tags
   526  	IconID      types.ID
   527  
   528  	Client *APIClient
   529  	NoWait bool
   530  	ID     types.ID
   531  }
   532  
   533  // Validate 設定値の検証
   534  func (d *BlankBuilder) Validate(ctx context.Context, zone string) error {
   535  	if err := validateDiskPlan(ctx, d.Client, zone, d.PlanID, d.SizeGB); err != nil {
   536  		return err
   537  	}
   538  	return nil
   539  }
   540  
   541  // Build ディスクの構築
   542  func (d *BlankBuilder) Build(ctx context.Context, zone string, serverID types.ID) (*BuildResult, error) {
   543  	res, err := build(ctx, d.Client, zone, serverID, d.DistantFrom, d)
   544  	if err != nil {
   545  		return nil, err
   546  	}
   547  	d.ID = res.DiskID
   548  	return res, err
   549  }
   550  
   551  // Update ディスクの更新
   552  func (d *BlankBuilder) Update(ctx context.Context, zone string) (*UpdateResult, error) {
   553  	return update(ctx, d.Client, zone, d)
   554  }
   555  
   556  // DiskID ディスクID取得
   557  func (d *BlankBuilder) DiskID() types.ID {
   558  	return d.ID
   559  }
   560  
   561  // UpdateLevel Update時にどのレベルの変更が必要か
   562  func (d *BlankBuilder) UpdateLevel(ctx context.Context, zone string, disk *sacloud.Disk) builder.UpdateLevel {
   563  	return updateLevel(disk, false, d)
   564  }
   565  
   566  func (d *BlankBuilder) updateDiskParameter() *sacloud.DiskUpdateRequest {
   567  	return &sacloud.DiskUpdateRequest{
   568  		Name:        d.Name,
   569  		Description: d.Description,
   570  		Tags:        d.Tags,
   571  		IconID:      d.IconID,
   572  		Connection:  d.Connection,
   573  	}
   574  }
   575  
   576  func (d *BlankBuilder) createDiskParameter(ctx context.Context, client *APIClient, zone string, serverID types.ID) (*sacloud.DiskCreateRequest, *sacloud.DiskEditRequest, error) {
   577  	createReq := &sacloud.DiskCreateRequest{
   578  		DiskPlanID:  d.PlanID,
   579  		SizeMB:      d.SizeGB * size.GiB,
   580  		Connection:  d.Connection,
   581  		ServerID:    serverID,
   582  		Name:        d.Name,
   583  		Description: d.Description,
   584  		Tags:        d.Tags,
   585  		IconID:      d.IconID,
   586  	}
   587  	return createReq, nil, nil
   588  }
   589  
   590  func (d *BlankBuilder) NoWaitFlag() bool {
   591  	return d.NoWait
   592  }
   593  
   594  // ConnectedDiskBuilder 既存ディスクを接続する場合のリクエスト
   595  type ConnectedDiskBuilder struct {
   596  	ID            types.ID
   597  	EditParameter *UnixEditRequest
   598  
   599  	Name        string
   600  	Description string
   601  	Tags        types.Tags
   602  	IconID      types.ID
   603  	Connection  types.EDiskConnection
   604  
   605  	NoWait bool
   606  	Client *APIClient
   607  }
   608  
   609  // Validate 設定値の検証
   610  func (d *ConnectedDiskBuilder) Validate(ctx context.Context, zone string) error {
   611  	if d.ID.IsEmpty() {
   612  		return errors.New("DiskID is required")
   613  	}
   614  
   615  	if _, err := d.Client.Disk.Read(ctx, zone, d.ID); err != nil {
   616  		return err
   617  	}
   618  
   619  	return nil
   620  }
   621  
   622  // Build ディスクの構築
   623  func (d *ConnectedDiskBuilder) Build(ctx context.Context, zone string, serverID types.ID) (*BuildResult, error) {
   624  	res := &BuildResult{
   625  		DiskID: d.ID,
   626  	}
   627  	if !serverID.IsEmpty() {
   628  		if err := d.Client.Disk.ConnectToServer(ctx, zone, d.ID, serverID); err != nil {
   629  			return nil, err
   630  		}
   631  	}
   632  
   633  	if d.EditParameter != nil {
   634  		req, sshKey, _, err := d.EditParameter.prepareDiskEditParameter(ctx, d.Client)
   635  		if err != nil {
   636  			return nil, err
   637  		}
   638  		res.GeneratedSSHKey = sshKey
   639  		if err := d.Client.Disk.Config(ctx, zone, d.ID, req); err != nil {
   640  			return nil, err
   641  		}
   642  		waiter := sacloud.WaiterForReady(func() (interface{}, error) {
   643  			return d.Client.Disk.Read(ctx, zone, d.ID)
   644  		})
   645  		if _, err := waiter.WaitForState(ctx); err != nil {
   646  			return nil, err
   647  		}
   648  	}
   649  	return res, nil
   650  }
   651  
   652  // Update ディスクの更新
   653  func (d *ConnectedDiskBuilder) Update(ctx context.Context, zone string) (*UpdateResult, error) {
   654  	disk, err := d.Client.Disk.Update(ctx, zone, d.ID, d.updateDiskParameter())
   655  	if err != nil {
   656  		return nil, err
   657  	}
   658  
   659  	if d.EditParameter != nil {
   660  		req, _, _, err := d.EditParameter.prepareDiskEditParameter(ctx, d.Client)
   661  		if err != nil {
   662  			return nil, err
   663  		}
   664  		if err := d.Client.Disk.Config(ctx, zone, d.ID, req); err != nil {
   665  			return nil, err
   666  		}
   667  		waiter := sacloud.WaiterForReady(func() (interface{}, error) {
   668  			return d.Client.Disk.Read(ctx, zone, d.ID)
   669  		})
   670  		if _, err := waiter.WaitForState(ctx); err != nil {
   671  			return nil, err
   672  		}
   673  	}
   674  
   675  	return &UpdateResult{Disk: disk}, nil
   676  }
   677  
   678  // DiskID ディスクID取得
   679  func (d *ConnectedDiskBuilder) DiskID() types.ID {
   680  	return d.ID
   681  }
   682  
   683  // UpdateLevel Update時にどのレベルの変更が必要か
   684  func (d *ConnectedDiskBuilder) UpdateLevel(ctx context.Context, zone string, disk *sacloud.Disk) builder.UpdateLevel {
   685  	return updateLevel(disk, d.EditParameter != nil, d)
   686  }
   687  
   688  func (d *ConnectedDiskBuilder) updateDiskParameter() *sacloud.DiskUpdateRequest {
   689  	return &sacloud.DiskUpdateRequest{
   690  		Name:        d.Name,
   691  		Description: d.Description,
   692  		Tags:        d.Tags,
   693  		IconID:      d.IconID,
   694  		Connection:  d.Connection,
   695  	}
   696  }
   697  
   698  func (d *ConnectedDiskBuilder) createDiskParameter(
   699  	ctx context.Context,
   700  	client *APIClient,
   701  	zone string,
   702  	serverID types.ID,
   703  ) (*sacloud.DiskCreateRequest, *sacloud.DiskEditRequest, error) {
   704  	// noop
   705  	return nil, nil, nil
   706  }
   707  
   708  func (d *ConnectedDiskBuilder) NoWaitFlag() bool {
   709  	return d.NoWait
   710  }
   711  
   712  type diskBuilder interface {
   713  	createDiskParameter(
   714  		ctx context.Context,
   715  		client *APIClient,
   716  		zone string,
   717  		serverID types.ID,
   718  	) (*sacloud.DiskCreateRequest, *sacloud.DiskEditRequest, error)
   719  	updateDiskParameter() *sacloud.DiskUpdateRequest
   720  	DiskID() types.ID
   721  	NoWaitFlag() bool
   722  }
   723  
   724  func build(ctx context.Context, client *APIClient, zone string, serverID types.ID, distantFrom []types.ID, builder diskBuilder) (*BuildResult, error) {
   725  	var err error
   726  
   727  	diskReq, editReq, err := builder.createDiskParameter(ctx, client, zone, serverID)
   728  	if err != nil {
   729  		return nil, err
   730  	}
   731  	if diskReq == nil {
   732  		return nil, fmt.Errorf("disk create request is nil")
   733  	}
   734  	diskReq.ServerID = serverID
   735  
   736  	var disk *sacloud.Disk
   737  
   738  	if editReq == nil {
   739  		disk, err = client.Disk.Create(ctx, zone, diskReq, distantFrom)
   740  	} else {
   741  		disk, err = client.Disk.CreateWithConfig(ctx, zone, diskReq, editReq, false, distantFrom)
   742  	}
   743  	if err != nil {
   744  		if disk != nil {
   745  			return &BuildResult{DiskID: disk.ID}, err
   746  		}
   747  		return nil, err
   748  	}
   749  
   750  	if builder.NoWaitFlag() {
   751  		return &BuildResult{DiskID: disk.ID}, nil
   752  	}
   753  
   754  	waiter := sacloud.WaiterForReady(func() (interface{}, error) {
   755  		return client.Disk.Read(ctx, zone, disk.ID)
   756  	})
   757  	lastState, err := waiter.WaitForState(ctx)
   758  	if err != nil {
   759  		if lastState != nil {
   760  			return &BuildResult{DiskID: lastState.(*sacloud.Disk).ID}, err
   761  		}
   762  		return nil, err
   763  	}
   764  	disk = lastState.(*sacloud.Disk)
   765  
   766  	return &BuildResult{DiskID: disk.ID}, nil
   767  }
   768  
   769  func update(ctx context.Context, client *APIClient, zone string, builder diskBuilder) (*UpdateResult, error) {
   770  	var err error
   771  
   772  	diskID := builder.DiskID()
   773  	if diskID.IsEmpty() {
   774  		return nil, fmt.Errorf("disk id required")
   775  	}
   776  
   777  	diskReq, editReq, err := builder.createDiskParameter(ctx, client, zone, types.ID(0))
   778  	if err != nil {
   779  		return nil, err
   780  	}
   781  	if diskReq == nil {
   782  		return nil, fmt.Errorf("disk update request is nil")
   783  	}
   784  
   785  	disk, err := client.Disk.Update(ctx, zone, diskID, &sacloud.DiskUpdateRequest{
   786  		Name:        diskReq.Name,
   787  		Description: diskReq.Description,
   788  		Tags:        diskReq.Tags,
   789  		IconID:      diskReq.IconID,
   790  		Connection:  diskReq.Connection,
   791  	})
   792  	if err != nil {
   793  		return nil, err
   794  	}
   795  
   796  	if editReq != nil {
   797  		if err := client.Disk.Config(ctx, zone, disk.ID, editReq); err != nil {
   798  			return nil, err
   799  		}
   800  	}
   801  
   802  	if builder.NoWaitFlag() {
   803  		return &UpdateResult{Disk: disk}, nil
   804  	}
   805  
   806  	waiter := sacloud.WaiterForReady(func() (interface{}, error) {
   807  		return client.Disk.Read(ctx, zone, disk.ID)
   808  	})
   809  	lastState, err := waiter.WaitForState(ctx)
   810  	if err != nil {
   811  		return nil, err
   812  	}
   813  	disk = lastState.(*sacloud.Disk)
   814  
   815  	return &UpdateResult{Disk: disk}, nil
   816  }
   817  
   818  func validateDiskPlan(ctx context.Context, client *APIClient, zone string, diskPlanID types.ID, sizeGB int) error {
   819  	plan, err := client.DiskPlan.Read(ctx, zone, diskPlanID)
   820  	if err != nil {
   821  		return err
   822  	}
   823  	found := false
   824  	for _, size := range plan.Size {
   825  		if size.Availability.IsAvailable() && size.GetSizeGB() == sizeGB {
   826  			found = true
   827  			break
   828  		}
   829  	}
   830  	if !found {
   831  		return fmt.Errorf("disk plan[%s:%dGB] is not found", plan.Name, sizeGB)
   832  	}
   833  	return nil
   834  }
   835  
   836  func updateLevel(disk *sacloud.Disk, hasEditReq bool, b diskBuilder) builder.UpdateLevel {
   837  	if disk.ID != b.DiskID() || hasEditReq {
   838  		return builder.UpdateLevelNeedShutdown
   839  	}
   840  
   841  	current := &sacloud.DiskUpdateRequest{
   842  		Name:        disk.Name,
   843  		Description: disk.Description,
   844  		Tags:        disk.Tags,
   845  		IconID:      disk.IconID,
   846  		Connection:  disk.Connection,
   847  	}
   848  	desired := b.updateDiskParameter()
   849  	if desired == nil {
   850  		return builder.UpdateLevelNone
   851  	}
   852  	if reflect.DeepEqual(current, desired) {
   853  		if current.Connection != desired.Connection {
   854  			return builder.UpdateLevelNeedShutdown
   855  		}
   856  		return builder.UpdateLevelSimple
   857  	}
   858  	return builder.UpdateLevelNone
   859  }