Repository: sacloud/usacloud
Pull request #536 (2020-10-30 11:55:24)

Switching the parameter generator from using DSL to using native structs
from #534 

#535 の後続ステップ。

#535 で追加された `usacloud/tools/clitag`を用いてCLIパラメータ部分のコードを生成する。
~このPRではコード生成先の変更は行わずに従来のファイル構造を維持したままでパラメータ定義部分のみ置き換える。~

このPRではコード生成までとし、生成されたコードを利用する部分は後続PRにて対応する。

---

従来は`pkg/define`で独自DSLを用いてパラメータ定義を行なってきたが、v1では`pkg/cmd`配下に各リソースごとにパッケージを分け、その中にGoのstructとして定義する形に変更する。

### パッケージ構成

```bash
# diskのupdateコマンドの例
pkg/cmd/
    ├── base               # 各コマンド共通で利用するもの
    ├── disk               # diskリソースに対する操作を格納するパッケージ
        ├── update.go      # updateコマンドで利用するCLIパラメータをGoのStructとして定義する
        ├── update_gen.go  # CLI関連操作、コード生成される
        └── update_test.go # (任意) パラメータのテストコード
```

### パラメータ定義用のStruct

```go
type UpdateParameter struct {
	Zone string   `cli:"-" validate:"required"`
	ID   types.ID `cli:"-" validate:"required"`

	Name        *string   `cli:",category=common" validate:"omitempty,min=1"`
	Description *string   `cli:",category=common" validate:"omitempty,min=1,max=512"`
	Tags        *[]string `cli:",category=common"`
	IconID      *types.ID `cli:",category=common"`
	Connection  *string   `cli:",category=common"`

	*base.OutputParameter `cli:",squash" mapconv:"-"`
}

func NewUpdateParameter() *UpdateParameter {
	return &UpdateParameter{
		// TODO デフォルト値はここで設定する
	}
}

```

### 生成されるコード(抜粋)

```go
func (p *UpdateParameter) BuildFlags(fs *pflag.FlagSet) {
	fs.StringVarP(p.Name, "name", "", *p.Name, "")
	fs.StringVarP(p.Description, "description", "", *p.Description, "")
	fs.StringSliceVarP(p.Tags, "tags", "", *p.Tags, "")
	fs.VarP(base.NewIDFlag(p.IconID, p.IconID), "icon-id", "", "")
	fs.StringVarP(p.Connection, "connection", "", *p.Connection, "")
	fs.StringVarP(&p.OutputType, "output-type", "o", p.OutputType, "Output format: one of the following [table/json/yaml] (aliases: )")
	fs.BoolVarP(&p.Quiet, "quiet", "q", p.Quiet, "Output IDs only")
	fs.StringVarP(&p.Format, "format", "", p.Format, "Output format in Go templates (aliases: )")
	fs.StringVarP(&p.FormatFile, "format-file", "", p.FormatFile, "Output format in Go templates(from file)")
	fs.StringVarP(&p.Query, "query", "", p.Query, "JMESPath query")
	fs.StringVarP(&p.QueryFile, "query-file", "", p.QueryFile, "JMESPath query(from file)")
	fs.SetNormalizeFunc(p.normalizeFlagName)
}
```

## 残課題

- [#542] パラメータからlibsacloudのserviceを呼ぶ際の値の変換をどう扱うか(例: `ssd`をtypes.ID(2)に変換したい、など)
- [#541] ヘルプ表示で設定できる値を表示するのをどうするか(タグでは変数/定数が利用できない、かつlibsacloudなどの値を参照したい)
- [#540] toolsパッケージ配下のpkg/schemaのラッパーの整理
- [#539] パラメータのカテゴリーのデフォルトを検討(各フィールドに`cli:",category=foo"`と付けるのが面倒)


Patch:
diff --git a/go.mod b/go.mod
index f510b062..b6c01402 100644
--- a/go.mod
+++ b/go.mod
@@ -7,6 +7,7 @@ require (
 	github.com/bitly/go-simplejson v0.5.0
 	github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
 	github.com/fatih/color v1.7.0
+	github.com/fatih/structs v1.1.0
 	github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32
 	github.com/huandu/xstrings v1.3.0
 	github.com/mattn/go-colorable v0.0.9
@@ -16,7 +17,7 @@ require (
 	github.com/olekukonko/tablewriter v0.0.0-20180506121414-d4647c9c7a84
 	github.com/sacloud/ftps v1.1.0
 	github.com/sacloud/go-jmespath v0.0.0-20190125082617-862639817e08
-	github.com/sacloud/libsacloud/v2 v2.8.8
+	github.com/sacloud/libsacloud/v2 v2.8.9-0.20201104084812-02587fb2e64c
 	github.com/spf13/cobra v1.0.0
 	github.com/spf13/pflag v1.0.5
 	github.com/stretchr/testify v1.6.1
diff --git a/go.sum b/go.sum
index 00e2fb1a..f4bda0c1 100644
--- a/go.sum
+++ b/go.sum
@@ -129,8 +129,8 @@ github.com/sacloud/ftps v1.1.0 h1:cYv+b6qhrIT8msfx64XXRJzbv5S+Dqwb/rXa5Y641XA=
 github.com/sacloud/ftps v1.1.0/go.mod h1:h4awhOi3PEyhKLj1FpXjoVV5yVkmRUU+d5L95EwX2JU=
 github.com/sacloud/go-jmespath v0.0.0-20190125082617-862639817e08 h1:hGFzyq3AgiEHgI9xYPrtbK466gP7AimFCjrn3nZktc8=
 github.com/sacloud/go-jmespath v0.0.0-20190125082617-862639817e08/go.mod h1:PLy4tK2PN6G8fxd/cCWH6db2mJGmUlflYs4ASdfNulg=
-github.com/sacloud/libsacloud/v2 v2.8.8 h1:OBfeaRcvQTgFDP9Ii18d2qfuS7Ryj3NglInFr75Lk3M=
-github.com/sacloud/libsacloud/v2 v2.8.8/go.mod h1:gRNUdloSQqgQfTEMpot80TK7REly0TSME6dn5aZl3GY=
+github.com/sacloud/libsacloud/v2 v2.8.9-0.20201104084812-02587fb2e64c h1:E1wBQhJsPw1VfzmrLHXr77BDQSxqFvHRPWzTtEGkOeM=
+github.com/sacloud/libsacloud/v2 v2.8.9-0.20201104084812-02587fb2e64c/go.mod h1:gRNUdloSQqgQfTEMpot80TK7REly0TSME6dn5aZl3GY=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
diff --git a/pkg/cmd/base/common_parameter.go b/pkg/cmd/base/common_parameter.go
new file mode 100644
index 00000000..05742520
--- /dev/null
+++ b/pkg/cmd/base/common_parameter.go
@@ -0,0 +1,22 @@
+// Copyright 2017-2020 The Usacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package base
+
+// CommonParameter 全コマンド共通フィールド
+type CommonParameter struct {
+	Parameters       string `cli:",category=input,desc=Input parameters in JSON format"`
+	ParameterFile    string `cli:",category=input,desc=Input parameters in JSON format(from file)"`
+	GenerateSkeleton bool   `cli:",category=input,desc=Output skeleton of parameters with JSON format"`
+}
diff --git a/pkg/cmd/base/confirm.go b/pkg/cmd/base/confirm.go
new file mode 100644
index 00000000..c1ddd221
--- /dev/null
+++ b/pkg/cmd/base/confirm.go
@@ -0,0 +1,19 @@
+// Copyright 2017-2020 The Usacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package base
+
+type ConfirmParameter struct {
+	AssumeYes bool `cli:"assumeyes,short=y,category=input,desc=Assume that the answer to any question which would be asked is yes"`
+}
diff --git a/pkg/cmd/base/exec_context.go b/pkg/cmd/base/exec_context.go
new file mode 100644
index 00000000..b4caeffb
--- /dev/null
+++ b/pkg/cmd/base/exec_context.go
@@ -0,0 +1,23 @@
+// Copyright 2017-2020 The Usacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package base
+
+import "github.com/sacloud/usacloud/pkg/config"
+
+// ExecContext コマンド実行時のコンテキストパラメータ
+type ExecContext struct {
+	Config *config.Config `cli:"-"`
+	Input  Input          `cli:"-"`
+}
diff --git a/pkg/cmd/base/find_parameter.go b/pkg/cmd/base/find_parameter.go
new file mode 100644
index 00000000..143ca1a6
--- /dev/null
+++ b/pkg/cmd/base/find_parameter.go
@@ -0,0 +1,20 @@
+// Copyright 2017-2020 The Usacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package base
+
+type FindParameter struct {
+	Count int `cli:",aliases=max limit,category=filter"`
+	From  int `cli:",aliases=offset,category=filter"`
+}
diff --git a/pkg/cmd/base/flag_value.go b/pkg/cmd/base/flag_value.go
new file mode 100644
index 00000000..7615e4be
--- /dev/null
+++ b/pkg/cmd/base/flag_value.go
@@ -0,0 +1,69 @@
+// Copyright 2017-2020 The Usacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package base
+
+import (
+	"strings"
+
+	"github.com/sacloud/libsacloud/v2/sacloud/types"
+)
+
+type IDFlag types.ID
+
+func NewIDFlag(val *types.ID, p *types.ID) *IDFlag {
+	*p = *val
+	return (*IDFlag)(p)
+}
+
+func (v *IDFlag) String() string {
+	return types.ID(*v).String()
+}
+
+func (v *IDFlag) Set(id string) error {
+	*v = IDFlag(types.StringID(id))
+	return nil
+}
+
+func (v *IDFlag) Type() string {
+	return "types.ID"
+}
+
+type IDSliceFlag []types.ID
+
+func NewIDSliceFlag(val *[]types.ID, p *[]types.ID) *IDSliceFlag {
+	*p = *val
+	return (*IDSliceFlag)(p)
+}
+
+func (v *IDSliceFlag) String() string {
+	var ids []string
+	for _, id := range []types.ID(*v) {
+		ids = append(ids, id.String())
+	}
+	return strings.Join(ids, ",")
+}
+
+func (v *IDSliceFlag) Set(ids string) error {
+	values := strings.Split(ids, ",")
+	*v = []types.ID{}
+	for _, val := range values {
+		*v = append(*v, types.StringID(val))
+	}
+	return nil
+}
+
+func (v *IDSliceFlag) Type() string {
+	return "types.IDSlice"
+}
diff --git a/pkg/cmd/base/flagset.go b/pkg/cmd/base/flagset.go
new file mode 100644
index 00000000..262dd8f4
--- /dev/null
+++ b/pkg/cmd/base/flagset.go
@@ -0,0 +1,24 @@
+// Copyright 2017-2020 The Usacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package base
+
+import (
+	"github.com/spf13/pflag"
+)
+
+type FlagSet struct {
+	Title string
+	Flags *pflag.FlagSet
+}
diff --git a/pkg/cmd/base/input.go b/pkg/cmd/base/input.go
new file mode 100644
index 00000000..694b42ab
--- /dev/null
+++ b/pkg/cmd/base/input.go
@@ -0,0 +1,31 @@
+// Copyright 2017-2020 The Usacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package base
+
+import "github.com/sacloud/libsacloud/v2/sacloud/types"
+
+// Input 入力値を保持/参照するためのインターフェース
+type Input interface {
+	Changed(name string) bool
+	Bool(name string) (bool, error)
+	String(name string) (string, error)
+	StringSlice(name string) ([]string, error)
+	Int(name string) (int, error)
+	IntSlice(name string) ([]int, error)
+	Int64(name string) (int64, error)
+	Int64Slice(name string) ([]int64, error)
+	ID(name string) (types.ID, error)
+	IDSlice(name string) ([]types.ID, error)
+}
diff --git a/pkg/cmd/base/output_parameter.go b/pkg/cmd/base/output_parameter.go
new file mode 100644
index 00000000..7c4731cf
--- /dev/null
+++ b/pkg/cmd/base/output_parameter.go
@@ -0,0 +1,24 @@
+// Copyright 2017-2020 The Usacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package base
+
+type OutputParameter struct {
+	OutputType string `cli:",short=o,aliases=out,category=output,desc=Output format: one of the following [table/json/yaml]"`
+	Quiet      bool   `cli:",short=q,category=output,desc=Output IDs only"`
+	Format     string `cli:",aliases=fmt,category=output,desc=Output format in Go templates"`
+	FormatFile string `cli:",category=output,desc=Output format in Go templates(from file)"`
+	Query      string `cli:",category=output,desc=JMESPath query"`
+	QueryFile  string `cli:",category=output,desc=JMESPath query(from file)"`
+}
diff --git a/pkg/cmd/base/usage.go b/pkg/cmd/base/usage.go
new file mode 100644
index 00000000..5f6ac673
--- /dev/null
+++ b/pkg/cmd/base/usage.go
@@ -0,0 +1,40 @@
+// Copyright 2017-2020 The Usacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package base
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/spf13/cobra"
+)
+
+const originalFlagsUsage = `Flags:
+{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}`
+
+const flagsUsageTemplate = `  <%s>
+%s`
+
+const flagsUsageWrapperTemplate = `Flags:
+%s`
+
+func BuildFlagsUsage(cmd *cobra.Command, sets []*FlagSet) {
+	var usages []string
+	for _, fs := range sets {
+		usages = append(usages, fmt.Sprintf(flagsUsageTemplate, fs.Title, fs.Flags.FlagUsages()))
+	}
+	usage := fmt.Sprintf(flagsUsageWrapperTemplate, strings.TrimRight(strings.Join(usages, "\n"), "\n"))
+	cmd.SetUsageTemplate(strings.Replace(cmd.UsageTemplate(), originalFlagsUsage, usage, 1))
+}
diff --git a/pkg/cmd/disk/create.go b/pkg/cmd/disk/create.go
new file mode 100644
index 00000000..4866bab3
--- /dev/null
+++ b/pkg/cmd/disk/create.go
@@ -0,0 +1,48 @@
+// Copyright 2017-2020 The Usacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package disk
+
+import (
+	"github.com/sacloud/libsacloud/v2/sacloud/types"
+	"github.com/sacloud/usacloud/pkg/cmd/base"
+)
+
+type CreateParameter struct {
+	// TODO これらはコマンドのコンテキストでパラメーターに含めないべき？要検討
+	*base.ExecContext `cli:"-" mapconv:"-"`
+
+	Zone string `cli:"-" validate:"required"`
+
+	Name            string     `cli:",category=disk"`
+	Description     string     `cli:",category=disk"`
+	Tags            []string   `cli:",category=disk"`
+	IconID          types.ID   `cli:",category=disk"`
+	DiskPlanID      string     `cli:",category=disk" validate:"oneof=ssd hdd"` // TODO パラメータ変換ロジック(string -> types.ID)をコード生成と組み合わせる
+	Connection      string     `cli:",category=disk" validate:"oneof=virtio ide"`
+	SourceDiskID    types.ID   `cli:",category=disk"`
+	SourceArchiveID types.ID   `cli:",category=disk"`
+	ServerID        types.ID   `cli:",category=disk"`
+	SizeGB          int        `cli:"size,category=disk"`
+	DistantFrom     []types.ID `cli:",category=disk"`
+	OSType          string     `cli:",category=disk"` // TODO 設定できる値の例示をどうするか
+
+	*base.OutputParameter `cli:",squash" mapconv:"-"`
+}
+
+func NewCreateParameter() *CreateParameter {
+	return &CreateParameter{
+		// TODO デフォルト値はここで設定する
+	}
+}
diff --git a/pkg/cmd/disk/create_gen.go b/pkg/cmd/disk/create_gen.go
new file mode 100644
index 00000000..92a6ff72
--- /dev/null
+++ b/pkg/cmd/disk/create_gen.go
@@ -0,0 +1,103 @@
+// Copyright 2017-2020 The Usacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Code generated by 'github.com/sacloud/usacloud/tools/gen-cli-commands-v1'; DO NOT EDIT
+
+package disk
+
+import (
+	"github.com/sacloud/usacloud/pkg/cmd/base"
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+)
+
+func (p *CreateParameter) BuildFlags(fs *pflag.FlagSet) {
+	fs.StringVarP(&p.Name, "name", "", p.Name, "")
+	fs.StringVarP(&p.Description, "description", "", p.Description, "")
+	fs.StringSliceVarP(&p.Tags, "tags", "", p.Tags, "")
+	fs.VarP(base.NewIDFlag(&p.IconID, &p.IconID), "icon-id", "", "")
+	fs.StringVarP(&p.DiskPlanID, "disk-plan-id", "", p.DiskPlanID, "")
+	fs.StringVarP(&p.Connection, "connection", "", p.Connection, "")
+	fs.VarP(base.NewIDFlag(&p.SourceDiskID, &p.SourceDiskID), "source-disk-id", "", "")
+	fs.VarP(base.NewIDFlag(&p.SourceArchiveID, &p.SourceArchiveID), "source-archive-id", "", "")
+	fs.VarP(base.NewIDFlag(&p.ServerID, &p.ServerID), "server-id", "", "")
+	fs.IntVarP(&p.SizeGB, "size", "", p.SizeGB, "")
+	fs.VarP(base.NewIDSliceFlag(&p.DistantFrom, &p.DistantFrom), "distant-from", "", "")
+	fs.StringVarP(&p.OSType, "os-type", "", p.OSType, "")
+	fs.StringVarP(&p.OutputType, "output-type", "o", p.OutputType, "Output format: one of the following [table/json/yaml] (aliases: )")
+	fs.BoolVarP(&p.Quiet, "quiet", "q", p.Quiet, "Output IDs only")
+	fs.StringVarP(&p.Format, "format", "", p.Format, "Output format in Go templates (aliases: )")
+	fs.StringVarP(&p.FormatFile, "format-file", "", p.FormatFile, "Output format in Go templates(from file)")
+	fs.StringVarP(&p.Query, "query", "", p.Query, "JMESPath query")
+	fs.StringVarP(&p.QueryFile, "query-file", "", p.QueryFile, "JMESPath query(from file)")
+	fs.SetNormalizeFunc(p.normalizeFlagName)
+}
+
+func (p *CreateParameter) normalizeFlagName(_ *pflag.FlagSet, name string) pflag.NormalizedName {
+	switch name {
+	case "out":
+		name = "output-type"
+	case "fmt":
+		name = "format"
+	}
+	return pflag.NormalizedName(name)
+}
+
+func (p *CreateParameter) CategorizedFlagSets(cmd *cobra.Command) []*base.FlagSet {
+	var sets []*base.FlagSet
+	{
+		var fs *pflag.FlagSet
+		fs = pflag.NewFlagSet("disk", pflag.ContinueOnError)
+		fs.AddFlag(cmd.LocalFlags().Lookup("name"))
+		fs.AddFlag(cmd.LocalFlags().Lookup("description"))
+		fs.AddFlag(cmd.LocalFlags().Lookup("tags"))
+		fs.AddFlag(cmd.LocalFlags().Lookup("disk-plan-id"))
+		fs.AddFlag(cmd.LocalFlags().Lookup("connection"))
+		fs.AddFlag(cmd.LocalFlags().Lookup("source-disk-id"))
+		fs.AddFlag(cmd.LocalFlags().Lookup("source-archive-id"))
+		fs.AddFlag(cmd.LocalFlags().Lookup("server-id"))
+		fs.AddFlag(cmd.LocalFlags().Lookup("size"))
+		fs.AddFlag(cmd.LocalFlags().Lookup("distant-from"))
+		fs.AddFlag(cmd.LocalFlags().Lookup("os-type"))
+		sets = append(sets, &base.FlagSet{
+			Title: "Disk options",
+			Flags: fs,
+		})
+	}
+	{
+		var fs *pflag.FlagSet
+		fs = pflag.NewFlagSet("output", pflag.ContinueOnError)
+		fs.AddFlag(cmd.LocalFlags().Lookup("output-type"))
+		fs.AddFlag(cmd.LocalFlags().Lookup("quiet"))
+		fs.AddFlag(cmd.LocalFlags().Lookup("format"))
+		fs.AddFlag(cmd.LocalFlags().Lookup("format-file"))
+		fs.AddFlag(cmd.LocalFlags().Lookup("query"))
+		fs.AddFlag(cmd.LocalFlags().Lookup("query-file"))
+		sets = append(sets, &base.FlagSet{
+			Title: "Output options",
+			Flags: fs,
+		})
+	}
+	{
+		var fs *pflag.FlagSet
+		fs = pflag.NewFlagSet("default", pflag.ContinueOnError)
+		fs.AddFlag(cmd.LocalFlags().Lookup("icon-id"))
+		sets = append(sets, &base.FlagSet{
+			Title: "Other options",
+			Flags: fs,
+		})
+	}
+
+	return sets
+}
diff --git a/pkg/cmd/disk/list.go b/pkg/cmd/disk/list.go
new file mode 100644
index 00000000..5ca04db5
--- /dev/null
+++ b/pkg/cmd/disk/list.go
@@ -0,0 +1,36 @@
+// Copyright 2017-2020 The Usacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package disk
+
+import "github.com/sacloud/usacloud/pkg/cmd/base"
+
+type ListParameter struct {
+	// TODO これらはコマンドのコンテキストでパラメーターに含めないべき？要検討
+	*base.ExecContext `mapconv:"-"`
+
+	Zone string `cli:"-" validate:"required"`
+
+	Names               []string `cli:",category=filter"`
+	Tags                []string `cli:",category=filter"`
+	*base.FindParameter `cli:",squash" mapconv:",squash"`
+
+	*base.OutputParameter `cli:",squash" mapconv:"-"`
+}
+
+func NewListParameter() *ListParameter {
+	return &ListParameter{
+		// TODO デフォルト値はここで設定する
+	}
+}
diff --git a/pkg/cmd/disk/list_gen.go b/pkg/cmd/disk/list_gen.go
new file mode 100644
index 00000000..2f2830bd
--- /dev/null
+++ b/pkg/cmd/disk/list_gen.go
@@ -0,0 +1,85 @@
+// Copyright 2017-2020 The Usacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Code generated by 'github.com/sacloud/usacloud/tools/gen-cli-commands-v1'; DO NOT EDIT
+
+package disk
+
+import (
+	"github.com/sacloud/usacloud/pkg/cmd/base"
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+)
+
+func (p *ListParameter) BuildFlags(fs *pflag.FlagSet) {
+	fs.StringSliceVarP(&p.Names, "names", "", p.Names, "")
+	fs.StringSliceVarP(&p.Tags, "tags", "", p.Tags, "")
+	fs.IntVarP(&p.Count, "count", "", p.Count, " (aliases: ls, find, select)")
+	fs.IntVarP(&p.From, "from", "", p.From, " (aliases: ls, find, select)")
+	fs.StringVarP(&p.OutputType, "output-type", "o", p.OutputType, "Output format: one of the following [table/json/yaml] (aliases: ls, find, select)")
+	fs.BoolVarP(&p.Quiet, "quiet", "q", p.Quiet, "Output IDs only")
+	fs.StringVarP(&p.Format, "format", "", p.Format, "Output format in Go templates (aliases: ls, find, select)")
+	fs.StringVarP(&p.FormatFile, "format-file", "", p.FormatFile, "Output format in Go templates(from file)")
+	fs.StringVarP(&p.Query, "query", "", p.Query, "JMESPath query")
+	fs.StringVarP(&p.QueryFile, "query-file", "", p.QueryFile, "JMESPath query(from file)")
+	fs.SetNormalizeFunc(p.normalizeFlagName)
+}
+
+func (p *ListParameter) normalizeFlagName(_ *pflag.FlagSet, name string) pflag.NormalizedName {
+	switch name {
+	case "max":
+		name = "count"
+	case "limit":
+		name = "count"
+	case "offset":
+		name = "from"
+	case "out":
+		name = "output-type"
+	case "fmt":
+		name = "format"
+	}
+	return pflag.NormalizedName(name)
+}
+
+func (p *ListParameter) CategorizedFlagSets(cmd *cobra.Command) []*base.FlagSet {
+	var sets []*base.FlagSet
+	{
+		var fs *pflag.FlagSet
+		fs = pflag.NewFlagSet("filter", pflag.ContinueOnError)
+		fs.AddFlag(cmd.LocalFlags().Lookup("names"))
+		fs.AddFlag(cmd.LocalFlags().Lookup("tags"))
+		fs.AddFlag(cmd.LocalFlags().Lookup("count"))
+		fs.AddFlag(cmd.LocalFlags().Lookup("from"))
+		sets = append(sets, &base.FlagSet{
+			Title: "Filter options",
+			Flags: fs,
+		})
+	}
+	{
+		var fs *pflag.FlagSet
+		fs = pflag.NewFlagSet("output", pflag.ContinueOnError)
+		fs.AddFlag(cmd.LocalFlags().Lookup("output-type"))
+		fs.AddFlag(cmd.LocalFlags().Lookup("quiet"))
+		fs.AddFlag(cmd.LocalFlags().Lookup("format"))
+		fs.AddFlag(cmd.LocalFlags().Lookup("format-file"))
+		fs.AddFlag(cmd.LocalFlags().Lookup("query"))
+		fs.AddFlag(cmd.LocalFlags().Lookup("query-file"))
+		sets = append(sets, &base.FlagSet{
+			Title: "Output options",
+			Flags: fs,
+		})
+	}
+
+	return sets
+}
diff --git a/pkg/cmd/disk/update.go b/pkg/cmd/disk/update.go
new file mode 100644
index 00000000..b8268ac2
--- /dev/null
+++ b/pkg/cmd/disk/update.go
@@ -0,0 +1,42 @@
+// Copyright 2017-2020 The Usacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package disk
+
+import (
+	"github.com/sacloud/libsacloud/v2/sacloud/types"
+	"github.com/sacloud/usacloud/pkg/cmd/base"
+)
+
+type UpdateParameter struct {
+	// TODO これらはコマンドのコンテキストでパラメーターに含めないべき？要検討
+	*base.ExecContext `cli:"-" mapconv:"-"`
+
+	Zone string   `cli:"-" validate:"required"`
+	ID   types.ID `cli:"-" validate:"required"`
+
+	Name        *string   `cli:",category=common" validate:"omitempty,min=1"`
+	Description *string   `cli:",category=common" validate:"omitempty,min=1,max=512"`
+	Tags        *[]string `cli:",category=common"`
+	IconID      *types.ID `cli:",category=common"`
+	Connection  *string   `cli:",category=common"`
+
+	*base.OutputParameter `cli:",squash" mapconv:"-"`
+}
+
+func NewUpdateParameter() *UpdateParameter {
+	return &UpdateParameter{
+		// TODO デフォルト値はここで設定する
+	}
+}
diff --git a/pkg/cmd/disk/update_gen.go b/pkg/cmd/disk/update_gen.go
new file mode 100644
index 00000000..514b9b10
--- /dev/null
+++ b/pkg/cmd/disk/update_gen.go
@@ -0,0 +1,81 @@
+// Copyright 2017-2020 The Usacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Code generated by 'github.com/sacloud/usacloud/tools/gen-cli-commands-v1'; DO NOT EDIT
+
+package disk
+
+import (
+	"github.com/sacloud/usacloud/pkg/cmd/base"
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+)
+
+func (p *UpdateParameter) BuildFlags(fs *pflag.FlagSet) {
+	fs.StringVarP(p.Name, "name", "", *p.Name, "")
+	fs.StringVarP(p.Description, "description", "", *p.Description, "")
+	fs.StringSliceVarP(p.Tags, "tags", "", *p.Tags, "")
+	fs.VarP(base.NewIDFlag(p.IconID, p.IconID), "icon-id", "", "")
+	fs.StringVarP(p.Connection, "connection", "", *p.Connection, "")
+	fs.StringVarP(&p.OutputType, "output-type", "o", p.OutputType, "Output format: one of the following [table/json/yaml] (aliases: )")
+	fs.BoolVarP(&p.Quiet, "quiet", "q", p.Quiet, "Output IDs only")
+	fs.StringVarP(&p.Format, "format", "", p.Format, "Output format in Go templates (aliases: )")
+	fs.StringVarP(&p.FormatFile, "format-file", "", p.FormatFile, "Output format in Go templates(from file)")
+	fs.StringVarP(&p.Query, "query", "", p.Query, "JMESPath query")
+	fs.StringVarP(&p.QueryFile, "query-file", "", p.QueryFile, "JMESPath query(from file)")
+	fs.SetNormalizeFunc(p.normalizeFlagName)
+}
+
+func (p *UpdateParameter) normalizeFlagName(_ *pflag.FlagSet, name string) pflag.NormalizedName {
+	switch name {
+	case "out":
+		name = "output-type"
+	case "fmt":
+		name = "format"
+	}
+	return pflag.NormalizedName(name)
+}
+
+func (p *UpdateParameter) CategorizedFlagSets(cmd *cobra.Command) []*base.FlagSet {
+	var sets []*base.FlagSet
+	{
+		var fs *pflag.FlagSet
+		fs = pflag.NewFlagSet("common", pflag.ContinueOnError)
+		fs.AddFlag(cmd.LocalFlags().Lookup("name"))
+		fs.AddFlag(cmd.LocalFlags().Lookup("description"))
+		fs.AddFlag(cmd.LocalFlags().Lookup("tags"))
+		fs.AddFlag(cmd.LocalFlags().Lookup("icon-id"))
+		fs.AddFlag(cmd.LocalFlags().Lookup("connection"))
+		sets = append(sets, &base.FlagSet{
+			Title: "Common options",
+			Flags: fs,
+		})
+	}
+	{
+		var fs *pflag.FlagSet
+		fs = pflag.NewFlagSet("output", pflag.ContinueOnError)
+		fs.AddFlag(cmd.LocalFlags().Lookup("output-type"))
+		fs.AddFlag(cmd.LocalFlags().Lookup("quiet"))
+		fs.AddFlag(cmd.LocalFlags().Lookup("format"))
+		fs.AddFlag(cmd.LocalFlags().Lookup("format-file"))
+		fs.AddFlag(cmd.LocalFlags().Lookup("query"))
+		fs.AddFlag(cmd.LocalFlags().Lookup("query-file"))
+		sets = append(sets, &base.FlagSet{
+			Title: "Output options",
+			Flags: fs,
+		})
+	}
+
+	return sets
+}
diff --git a/pkg/config/config.go b/pkg/config/config.go
index bbf11563..3e1cf3cc 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -25,7 +25,6 @@ import (
 
 	"github.com/sacloud/libsacloud/v2/sacloud"
 	"github.com/sacloud/libsacloud/v2/sacloud/profile"
-	"github.com/sacloud/usacloud/pkg/define"
 	"github.com/sacloud/usacloud/pkg/validation"
 	"github.com/spf13/pflag"
 )
@@ -231,7 +230,7 @@ func (o *Config) Validate(skipCred bool) []error {
 		errs = append(errs, validation.Required("zone", o.Zone)...)
 		errs = append(errs, validation.StringInSlice("zone", o.Zone, o.Zones)...)
 	}
-	errs = append(errs, validation.StringInSlice("default-output-type", o.DefaultOutputType, define.AllowOutputTypes)...)
+	errs = append(errs, validation.StringInSlice("default-output-type", o.DefaultOutputType, AllowOutputTypes)...)
 
 	return errs
 }
diff --git a/pkg/config/globals.go b/pkg/config/globals.go
new file mode 100644
index 00000000..d3baedfe
--- /dev/null
+++ b/pkg/config/globals.go
@@ -0,0 +1,20 @@
+// Copyright 2017-2020 The Usacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package config
+
+var (
+	AllowZones       = []string{"is1a", "is1b", "tk1a", "tk1v"}
+	AllowOutputTypes = []string{"table", "json", "yaml", "csv", "tsv"}
+)
diff --git a/pkg/define/config.go b/pkg/define/config.go
index 522d409f..a5f0ea0c 100644
--- a/pkg/define/config.go
+++ b/pkg/define/config.go
@@ -15,6 +15,7 @@
 package define
 
 import (
+	"github.com/sacloud/usacloud/pkg/config"
 	"github.com/sacloud/usacloud/pkg/schema"
 )
 
@@ -82,9 +83,6 @@ func ConfigResource() *schema.Resource {
 	}
 }
 
-var AllowZones = []string{"is1a", "is1b", "tk1a", "tk1v"}
-var AllowOutputTypes = []string{"table", "json", "yaml", "csv", "tsv"}
-
 func configEditParam() map[string]*schema.Schema {
 	return map[string]*schema.Schema{
 		"token": {
@@ -105,7 +103,7 @@ func configEditParam() map[string]*schema.Schema {
 			Type:         schema.TypeString,
 			HandlerType:  schema.HandlerNoop,
 			Description:  "Target zone of SakuraCloud",
-			ValidateFunc: validateInStrValues(AllowZones...),
+			ValidateFunc: validateInStrValues(config.AllowZones...),
 			Category:     "config",
 			Order:        30,
 		},
@@ -113,7 +111,7 @@ func configEditParam() map[string]*schema.Schema {
 			Type:         schema.TypeString,
 			HandlerType:  schema.HandlerNoop,
 			Description:  "Default output format type",
-			ValidateFunc: validateInStrValues(AllowOutputTypes...),
+			ValidateFunc: validateInStrValues(config.AllowOutputTypes...),
 			Category:     "config",
 			Order:        40,
 		},
diff --git a/pkg/define/disk.go b/pkg/define/disk.go
index 65dbc82d..3d9a07ff 100644
--- a/pkg/define/disk.go
+++ b/pkg/define/disk.go
@@ -19,6 +19,7 @@ import (
 
 	"github.com/sacloud/libsacloud/v2/sacloud"
 	"github.com/sacloud/libsacloud/v2/sacloud/types"
+	"github.com/sacloud/usacloud/pkg/cmd/disk"
 	"github.com/sacloud/usacloud/pkg/output"
 	"github.com/sacloud/usacloud/pkg/schema"
 )
@@ -28,6 +29,7 @@ func DiskResource() *schema.Resource {
 		"list": {
 			Type:               schema.CommandList,
 			Aliases:            []string{"ls", "find", "select"},
+			Parameters:         &disk.ListParameter{}, // TODO v1向け試験実装
 			Params:             diskListParam(),
 			TableType:          output.TableSimple,
 			TableColumnDefines: diskListColumns(),
@@ -36,6 +38,7 @@ func DiskResource() *schema.Resource {
 		},
 		"create": {
 			Type:             schema.CommandCreate,
+			Parameters:       &disk.CreateParameter{}, // TODO v1向け試験実装
 			Params:           diskCreateParam(),
 			IncludeFields:    diskDetailIncludes(),
 			ExcludeFields:    diskDetailExcludes(),
@@ -53,6 +56,7 @@ func DiskResource() *schema.Resource {
 		},
 		"update": {
 			Type:             schema.CommandUpdate,
+			Parameters:       &disk.UpdateParameter{}, // TODO v1向け試験実装
 			Params:           diskUpdateParam(),
 			IncludeFields:    diskDetailIncludes(),
 			ExcludeFields:    diskDetailExcludes(),
diff --git a/pkg/define/resources.go b/pkg/define/resources.go
index 3cefd2d2..4e3f2372 100644
--- a/pkg/define/resources.go
+++ b/pkg/define/resources.go
@@ -20,6 +20,7 @@
 //go:generate go run github.com/sacloud/usacloud/tools/gen-cli-usage/
 //go:generate go run github.com/sacloud/usacloud/tools/gen-cli-normalize-flag-name/
 //go:generate go run github.com/sacloud/usacloud/tools/gen-cli-root-command/
+//go:generate go run github.com/sacloud/usacloud/tools/gen-cli-commands-v1/            # TODO 実験的実装
 package define
 
 import "github.com/sacloud/usacloud/pkg/schema"
diff --git a/pkg/define/summary.go b/pkg/define/summary.go
index 2eb1d50b..042cd980 100644
--- a/pkg/define/summary.go
+++ b/pkg/define/summary.go
@@ -15,6 +15,7 @@
 package define
 
 import (
+	"github.com/sacloud/usacloud/pkg/config"
 	"github.com/sacloud/usacloud/pkg/output"
 	"github.com/sacloud/usacloud/pkg/schema"
 )
@@ -54,7 +55,7 @@ func summaryShowColumns() []output.ColumnDef {
 		{Name: "Name"},
 	}
 
-	for _, zone := range AllowZones {
+	for _, zone := range config.AllowZones {
 		defs = append(defs, output.ColumnDef{Name: zone})
 	}
 
diff --git a/pkg/funcs/config/edit.go b/pkg/funcs/config/edit.go
index 5b336387..93b5c81f 100644
--- a/pkg/funcs/config/edit.go
+++ b/pkg/funcs/config/edit.go
@@ -18,12 +18,10 @@ import (
 	"fmt"
 	"strings"
 
-	"github.com/sacloud/usacloud/pkg/config"
-
 	"github.com/fatih/color"
 	"github.com/sacloud/libsacloud/v2/sacloud/profile"
 	"github.com/sacloud/usacloud/pkg/cli"
-	"github.com/sacloud/usacloud/pkg/define"
+	"github.com/sacloud/usacloud/pkg/config"
 	"github.com/sacloud/usacloud/pkg/params"
 	"github.com/sacloud/usacloud/pkg/printer"
 )
@@ -157,10 +155,10 @@ func Edit(ctx cli.Context, params *params.EditConfigParam) error {
 			// read input
 			var input string
 			for {
-				fmt.Fprintf(out, "\n\t%s[%s]: ", "Enter zone", strings.Join(define.AllowZones, "/"))
+				fmt.Fprintf(out, "\n\t%s[%s]: ", "Enter zone", strings.Join(config.AllowZones, "/"))
 				fmt.Fscanln(in, &input)
 
-				if errs := cli.ValidateInStrValues("", input, define.AllowZones...); len(errs) == 0 {
+				if errs := cli.ValidateInStrValues("", input, config.AllowZones...); len(errs) == 0 {
 					break
 				}
 
@@ -200,10 +198,10 @@ func Edit(ctx cli.Context, params *params.EditConfigParam) error {
 			// read input
 			var input string
 			for {
-				fmt.Fprintf(out, "\n\t%s[%s]: ", "Enter default-output-type", strings.Join(define.AllowOutputTypes, "/"))
+				fmt.Fprintf(out, "\n\t%s[%s]: ", "Enter default-output-type", strings.Join(config.AllowOutputTypes, "/"))
 				fmt.Fscanln(in, &input)
 
-				if errs := cli.ValidateInStrValues("", input, define.AllowOutputTypes...); len(errs) == 0 {
+				if errs := cli.ValidateInStrValues("", input, config.AllowOutputTypes...); len(errs) == 0 {
 					break
 				}
 
diff --git a/pkg/params/zz_archive_gen.go b/pkg/params/zz_archive_gen.go
index 6b36232e..c0f1484f 100644
--- a/pkg/params/zz_archive_gen.go
+++ b/pkg/params/zz_archive_gen.go
@@ -207,7 +207,7 @@ func (p *ListArchiveParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
@@ -617,7 +617,7 @@ func (p *CreateArchiveParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
@@ -912,7 +912,7 @@ func (p *ReadArchiveParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
@@ -1215,7 +1215,7 @@ func (p *UpdateArchiveParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
@@ -1501,7 +1501,7 @@ func (p *DeleteArchiveParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
@@ -1772,7 +1772,7 @@ func (p *UploadArchiveParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
@@ -2209,7 +2209,7 @@ func (p *FTPOpenArchiveParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
diff --git a/pkg/params/zz_auth_status_gen.go b/pkg/params/zz_auth_status_gen.go
index 1a6f3145..19af9408 100644
--- a/pkg/params/zz_auth_status_gen.go
+++ b/pkg/params/zz_auth_status_gen.go
@@ -110,7 +110,7 @@ func (p *ShowAuthStatusParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
diff --git a/pkg/params/zz_auto_backup_gen.go b/pkg/params/zz_auto_backup_gen.go
index 067bf54e..efb8487f 100644
--- a/pkg/params/zz_auto_backup_gen.go
+++ b/pkg/params/zz_auto_backup_gen.go
@@ -171,7 +171,7 @@ func (p *ListAutoBackupParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
@@ -530,7 +530,7 @@ func (p *CreateAutoBackupParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
@@ -818,7 +818,7 @@ func (p *ReadAutoBackupParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
@@ -1147,7 +1147,7 @@ func (p *UpdateAutoBackupParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
@@ -1447,7 +1447,7 @@ func (p *DeleteAutoBackupParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
diff --git a/pkg/params/zz_bill_gen.go b/pkg/params/zz_bill_gen.go
index ffc34a32..2e623f7d 100644
--- a/pkg/params/zz_bill_gen.go
+++ b/pkg/params/zz_bill_gen.go
@@ -283,7 +283,7 @@ func (p *ListBillParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
diff --git a/pkg/params/zz_bridge_gen.go b/pkg/params/zz_bridge_gen.go
index c62bb32c..a968f5ad 100644
--- a/pkg/params/zz_bridge_gen.go
+++ b/pkg/params/zz_bridge_gen.go
@@ -159,7 +159,7 @@ func (p *ListBridgeParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
@@ -427,7 +427,7 @@ func (p *CreateBridgeParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
@@ -675,7 +675,7 @@ func (p *ReadBridgeParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
@@ -940,7 +940,7 @@ func (p *UpdateBridgeParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
@@ -1200,7 +1200,7 @@ func (p *DeleteBridgeParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
diff --git a/pkg/params/zz_coupon_gen.go b/pkg/params/zz_coupon_gen.go
index ce7a3977..137589fd 100644
--- a/pkg/params/zz_coupon_gen.go
+++ b/pkg/params/zz_coupon_gen.go
@@ -114,7 +114,7 @@ func (p *ListCouponParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
diff --git a/pkg/params/zz_database_gen.go b/pkg/params/zz_database_gen.go
index 9cbe2a4b..69d5671f 100644
--- a/pkg/params/zz_database_gen.go
+++ b/pkg/params/zz_database_gen.go
@@ -171,7 +171,7 @@ func (p *ListDatabaseParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
@@ -693,7 +693,7 @@ func (p *CreateDatabaseParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
@@ -1065,7 +1065,7 @@ func (p *ReadDatabaseParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
@@ -1463,7 +1463,7 @@ func (p *UpdateDatabaseParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
@@ -1817,7 +1817,7 @@ func (p *DeleteDatabaseParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
@@ -3013,7 +3013,7 @@ func (p *BackupInfoDatabaseParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
@@ -3259,7 +3259,7 @@ func (p *BackupCreateDatabaseParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
@@ -3525,7 +3525,7 @@ func (p *BackupRestoreDatabaseParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
@@ -3798,7 +3798,7 @@ func (p *BackupLockDatabaseParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
@@ -4071,7 +4071,7 @@ func (p *BackupUnlockDatabaseParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
@@ -4344,7 +4344,7 @@ func (p *BackupRemoveDatabaseParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
@@ -4813,7 +4813,7 @@ func (p *CloneDatabaseParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
@@ -5289,7 +5289,7 @@ func (p *ReplicaCreateDatabaseParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
@@ -5632,7 +5632,7 @@ func (p *MonitorCPUDatabaseParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
@@ -5940,7 +5940,7 @@ func (p *MonitorMemoryDatabaseParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
@@ -6248,7 +6248,7 @@ func (p *MonitorNicDatabaseParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
@@ -6556,7 +6556,7 @@ func (p *MonitorSystemDiskDatabaseParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
@@ -6864,7 +6864,7 @@ func (p *MonitorBackupDiskDatabaseParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
@@ -7172,7 +7172,7 @@ func (p *MonitorSystemDiskSizeDatabaseParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
@@ -7480,7 +7480,7 @@ func (p *MonitorBackupDiskSizeDatabaseParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
diff --git a/pkg/params/zz_disk_gen.go b/pkg/params/zz_disk_gen.go
index 2f209e08..8b7816c9 100644
--- a/pkg/params/zz_disk_gen.go
+++ b/pkg/params/zz_disk_gen.go
@@ -211,7 +211,7 @@ func (p *ListDiskParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
@@ -646,7 +646,7 @@ func (p *CreateDiskParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
@@ -955,7 +955,7 @@ func (p *ReadDiskParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
@@ -1263,7 +1263,7 @@ func (p *UpdateDiskParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
@@ -1556,7 +1556,7 @@ func (p *DeleteDiskParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
@@ -1880,7 +1880,7 @@ func (p *EditDiskParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
@@ -2194,7 +2194,7 @@ func (p *ResizePartitionDiskParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
@@ -3429,7 +3429,7 @@ func (p *MonitorDiskParam) validate() error {
 	}
 
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type", p.OutputType)
 		if errs != nil {
 			errors = append(errors, errs...)
diff --git a/pkg/schema/command.go b/pkg/schema/command.go
index 66b44ebf..586bd2c6 100644
--- a/pkg/schema/command.go
+++ b/pkg/schema/command.go
@@ -31,8 +31,13 @@ type Command struct {
 	Category string
 	Order    int
 
-	Params              map[string]*Schema
-	ParamCategories     []Category
+	// TODO v0向け、あとで消す
+	Params          map[string]*Schema
+	ParamCategories []Category
+
+	// v1向け
+	Parameters interface{} // cmd/xxx配下の各コマンドパラメータstruct
+
 	AltResource         string // 空の場合はResourceのキーをCamelizeしてsacloud.XXXを対象とする。
 	ListResultFieldName string
 	SkipAuth            bool
diff --git a/tools/clitag/parse_tag.go b/tools/clitag/parse_tag.go
index a5f536f7..c1413d31 100644
--- a/tools/clitag/parse_tag.go
+++ b/tools/clitag/parse_tag.go
@@ -44,7 +44,7 @@ func (p *Parser) parseTag(t string) (Tag, error) {
 	}
 
 	if name != "" {
-		tag.Name = name
+		tag.FlagName = name
 	}
 	if len(tokens) > 1 {
 		for _, token := range tokens[1:] {
diff --git a/tools/clitag/parser.go b/tools/clitag/parser.go
index d30dbd56..d2b1699b 100644
--- a/tools/clitag/parser.go
+++ b/tools/clitag/parser.go
@@ -19,7 +19,7 @@ import (
 	"fmt"
 	"reflect"
 
-	"github.com/sacloud/usacloud/tools"
+	"github.com/sacloud/usacloud/tools/utils"
 )
 
 // DefaultTagName ftagのデフォルト名
@@ -83,20 +83,22 @@ func (p *Parser) parseField(prefix string, f reflect.StructField) ([]StructField
 	}
 
 	// handle tag values
-	if tag.Name == "" {
-		tag.Name = tools.ToDashedName(f.Name)
+	if tag.FlagName == "" {
+		tag.FlagName = utils.ToDashedName(f.Name)
 	}
 	if !tag.Squash {
-		if prefix != "" && tag.Name != "" {
-			tag.Name = fmt.Sprintf("%s-%s", prefix, tag.Name)
+		if prefix != "" && tag.FlagName != "" {
+			tag.FlagName = fmt.Sprintf("%s-%s", prefix, tag.FlagName)
 		}
-		prefix = tag.Name
+		prefix = tag.FlagName
 	}
 
 	kind := f.Type.Kind()
 	switch kind {
 	case reflect.Ptr:
-		return p.parseFields(prefix, f.Type.Elem())
+		if f.Type.Elem().Kind() == reflect.Struct {
+			return p.parseFields(prefix, f.Type.Elem())
+		}
 	case reflect.Struct:
 		return p.parseFields(prefix, f.Type)
 	}
diff --git a/tools/clitag/types.go b/tools/clitag/types.go
index 0cbd9621..9cf27064 100644
--- a/tools/clitag/types.go
+++ b/tools/clitag/types.go
@@ -25,7 +25,7 @@ type StructField struct {
 // Tag structにつけられたftagの値
 //
 // 例: タグが`example,aliases=foo bar,short=e,desc=foobar`の場合
-// - Name: example
+// - FlagName: example
 // - Aliases: foo, bar
 // - Shorthand: e
 // - Description: foobar
@@ -34,7 +34,7 @@ type StructField struct {
 // - Category: (empty)
 // - Order: 0
 type Tag struct {
-	Name        string
+	FlagName    string
 	Aliases     []string
 	Shorthand   string
 	Description string
diff --git a/tools/command.go b/tools/command.go
index e24782db..8070649f 100644
--- a/tools/command.go
+++ b/tools/command.go
@@ -16,11 +16,16 @@ package tools
 
 import (
 	"fmt"
+	"reflect"
 	"sort"
+	"strings"
 
-	"github.com/sacloud/usacloud/pkg/util"
-
+	"github.com/fatih/structs"
+	"github.com/sacloud/libsacloud/v2/sacloud/types"
 	"github.com/sacloud/usacloud/pkg/schema"
+	"github.com/sacloud/usacloud/pkg/util"
+	"github.com/sacloud/usacloud/tools/clitag"
+	"github.com/sacloud/usacloud/tools/utils"
 )
 
 // Command コード生成時に利用するコマンド定義
@@ -40,6 +45,11 @@ type CategorizedParameters struct {
 	Params []*Parameter
 }
 
+type CategorizedParameterFields struct {
+	*schema.Category
+	Fields []clitag.StructField
+}
+
 func NewCommand(name string, command *schema.Command, category *schema.Category, resource *Resource) *Command {
 	c := &Command{
 		Command:  command,
@@ -113,7 +123,7 @@ func (c *Command) ExperimentWarning() string {
 func (c *Command) Usage() string {
 	usage := c.Command.Usage
 	if usage == "" {
-		usage = fmt.Sprintf("%s %s", ToCamelCaseName(c.Name), ToCamelCaseName(c.Resource.Name))
+		usage = fmt.Sprintf("%s %s", utils.ToCamelCaseName(c.Name), utils.ToCamelCaseName(c.Resource.Name))
 	}
 	if c.Resource.DefaultCommand == c.Name {
 		usage = fmt.Sprintf("%s (default)", usage)
@@ -138,7 +148,7 @@ func (c *Command) ArgsUsage() string {
 }
 
 func (c *Command) AliasesLiteral() string {
-	return FlattenStringList(c.Command.Aliases)
+	return utils.FlattenStringList(c.Command.Aliases)
 }
 
 func (c *Command) HasIDParam() bool {
@@ -155,31 +165,31 @@ func (c *Command) HasOutputOption() bool {
 }
 
 func (c *Command) CLIName() string {
-	return ToDashedName(c.Name)
+	return utils.ToDashedName(c.Name)
 }
 
 func (c *Command) CLIVariableFuncName() string {
-	return fmt.Sprintf("%s%sCmd", ToCamelWithFirstLower(c.Resource.Name), ToCamelCaseName(c.Name))
+	return fmt.Sprintf("%s%sCmd", utils.ToCamelWithFirstLower(c.Resource.Name), utils.ToCamelCaseName(c.Name))
 }
 
 func (c *Command) CLIv2CommandsFileName() string {
-	return fmt.Sprintf("%s_gen.go", ToSnakeCaseName(c.Resource.Name))
+	return fmt.Sprintf("%s_gen.go", utils.ToSnakeCaseName(c.Resource.Name))
 }
 
 func (c *Command) CLINormalizeFlagsFuncName() string {
-	return fmt.Sprintf("%s%sNormalizeFlagNames", ToCamelWithFirstLower(c.Resource.Name), ToCamelCaseName(c.Name))
+	return fmt.Sprintf("%s%sNormalizeFlagNames", utils.ToCamelWithFirstLower(c.Resource.Name), utils.ToCamelCaseName(c.Name))
 }
 
 func (c *Command) InputParameterVariable() string {
-	return fmt.Sprintf("%s%sParam", ToCamelWithFirstLower(c.Resource.Name), ToCamelCaseName(c.Name))
+	return fmt.Sprintf("%s%sParam", utils.ToCamelWithFirstLower(c.Resource.Name), utils.ToCamelCaseName(c.Name))
 }
 
 func (c *Command) InputParameterTypeName() string {
-	return fmt.Sprintf("%s%sParam", ToCamelCaseName(c.Name), ToCamelCaseName(c.Resource.Name))
+	return fmt.Sprintf("%s%sParam", utils.ToCamelCaseName(c.Name), utils.ToCamelCaseName(c.Resource.Name))
 }
 
 func (c *Command) FunctionName() string {
-	return ToCamelCaseName(c.Name)
+	return utils.ToCamelCaseName(c.Name)
 }
 
 func (c *Command) NeedConfirm() bool {
@@ -188,7 +198,7 @@ func (c *Command) NeedConfirm() bool {
 
 func (c *Command) ConfirmMessage() string {
 	if c.Command.ConfirmMessage == "" {
-		return ToDashedName(c.Name)
+		return utils.ToDashedName(c.Name)
 	}
 	return c.Command.ConfirmMessage
 }
@@ -206,19 +216,19 @@ func (c *Command) MultipleArgToIdParams() bool {
 }
 
 func (c *Command) ArgToIdFunc() string {
-	return fmt.Sprintf("find%s%sTargets", ToCamelCaseName(c.Resource.Name), ToCamelCaseName(c.Name))
+	return fmt.Sprintf("find%s%sTargets", utils.ToCamelCaseName(c.Resource.Name), utils.ToCamelCaseName(c.Name))
 }
 
 func (c *Command) FlagOrderFunc() string {
-	return fmt.Sprintf("%s%sFlagOrder", ToCamelWithFirstLower(c.Resource.Name), ToCamelCaseName(c.Name))
+	return fmt.Sprintf("%s%sFlagOrder", utils.ToCamelWithFirstLower(c.Resource.Name), utils.ToCamelCaseName(c.Name))
 }
 
 func (c *Command) TargetAPIName() string {
-	return util.FirstNonEmptyString(c.AltResource, c.Resource.AltResource, ToCamelCaseName(c.Resource.Name))
+	return util.FirstNonEmptyString(c.AltResource, c.Resource.AltResource, utils.ToCamelCaseName(c.Resource.Name))
 }
 
 func (c *Command) FindResultFieldName() string {
-	return util.FirstNonEmptyString(c.ListResultFieldName, c.Resource.ListResultFieldName, ToCamelCaseName(c.Resource.Name)+"s")
+	return util.FirstNonEmptyString(c.ListResultFieldName, c.Resource.ListResultFieldName, utils.ToCamelCaseName(c.Resource.Name)+"s")
 }
 
 func (c *Command) RequireSingleID() bool {
@@ -240,15 +250,15 @@ func (c *Command) CommandFileName() string {
 	if c.UseCustomCommand {
 		format = "%s.go"
 	}
-	return fmt.Sprintf(format, ToSnakeCaseName(c.Name))
+	return fmt.Sprintf(format, utils.ToSnakeCaseName(c.Name))
 }
 
 func (c *Command) ResourceName() string {
-	return util.FirstNonEmptyString(c.AltResource, c.Resource.AltResource, ToCamelCaseName(c.Resource.Name))
+	return util.FirstNonEmptyString(c.AltResource, c.Resource.AltResource, utils.ToCamelCaseName(c.Resource.Name))
 }
 
 func (c *Command) InputModelTypeName() string {
-	return fmt.Sprintf("%s%sParam", ToCamelCaseName(c.Name), ToCamelCaseName(c.Resource.Name))
+	return fmt.Sprintf("%s%sParam", utils.ToCamelCaseName(c.Name), utils.ToCamelCaseName(c.Resource.Name))
 }
 
 func (c *Command) APIRequestTypeName() string {
@@ -256,10 +266,157 @@ func (c *Command) APIRequestTypeName() string {
 	case schema.CommandList:
 		return "FindCondition"
 	default:
-		return fmt.Sprintf("%s%sRequest", c.ResourceName(), ToCamelCaseName(c.Name))
+		return fmt.Sprintf("%s%sRequest", c.ResourceName(), utils.ToCamelCaseName(c.Name))
 	}
 }
 
 func (c *Command) PackageDirName() string {
 	return c.Resource.PackageDirName()
 }
+
+/*
+  TODO gen-cli-command-v1用、あとでschema.Commandを整理する際に移動するかも
+*/
+
+func (c *Command) CLICommandGeneratedSourceFile() string {
+	return fmt.Sprintf("%s_gen.go", utils.ToSnakeCaseName(c.Name))
+}
+
+func (c *Command) CLICommandParameterTypeName() string {
+	if c.Command.Parameters == nil {
+		return ""
+	}
+	return structs.Name(c.Command.Parameters)
+}
+
+func (c *Command) CategorizedParameterFields() []*CategorizedParameterFields {
+	if c.Parameters == nil {
+		return nil
+	}
+
+	m := map[string]*CategorizedParameterFields{}
+	for _, f := range c.Fields() {
+		cp, ok := m[f.Category]
+		if !ok {
+			cp = &CategorizedParameterFields{
+				Category: c.Command.ParamCategory(f.Category),
+			}
+		}
+		cp.Fields = append(cp.Fields, f)
+		m[f.Category] = cp
+	}
+	var categorizedFields []*CategorizedParameterFields
+	for _, cat := range m {
+		categorizedFields = append(categorizedFields, cat)
+	}
+	sort.Slice(categorizedFields, func(i, j int) bool {
+		if categorizedFields[i].Order == categorizedFields[j].Order {
+			return categorizedFields[i].Key < categorizedFields[j].Key
+		}
+		return categorizedFields[i].Order < categorizedFields[j].Order
+	})
+
+	return categorizedFields
+}
+
+func (c *Command) HasAliases() bool {
+	if c.Command.Parameters == nil {
+		return false
+	}
+	for _, f := range c.Fields() {
+		if len(f.Aliases) > 0 {
+			return true
+		}
+	}
+	return false
+}
+
+func (c *Command) Fields() []clitag.StructField {
+	fields, err := clitag.Parse(c.Command.Parameters)
+	if err != nil {
+		panic(err)
+	}
+	return fields
+}
+
+func (c *Command) CLIFlagDefinitionStatements(parameterVariableName, flagSetVariableName string) string {
+	if c.Command.Parameters == nil {
+		return ""
+	}
+
+	var statements []string
+	for _, f := range c.Fields() {
+		s := c.cliFlagDefinitionStatement(parameterVariableName, f)
+		if s != "" {
+			statements = append(statements, fmt.Sprintf("%s.%s", flagSetVariableName, s))
+		}
+	}
+	return strings.Join(statements, "\n")
+}
+
+func (c *Command) cliFlagDefinitionStatement(parameterVariableName string, field clitag.StructField) string {
+	fieldVar := fmt.Sprintf("%s.%s", parameterVariableName, field.Name)
+	fieldPointerVar := fieldVar
+	if field.Type.Kind() == reflect.Ptr {
+		fieldVar = "*" + fieldVar
+	} else {
+		fieldPointerVar = "&" + fieldPointerVar
+	}
+
+	name := field.FlagName
+	shorthands := field.Shorthand
+	//value := p.DefaultValueOnSource()
+	usage := field.Description
+	if len(field.Aliases) > 0 {
+		usage = fmt.Sprintf("%s (aliases: %s)", usage, strings.Join(c.Aliases, ", "))
+	}
+
+	statement := ""
+	fieldType := dereferencePtrType(field.Type)
+	if isLibsacloudIDType(fieldType) {
+		statement = `VarP(base.NewIDFlag(%s, %s), "%s", "%s", "%s")`
+		return fmt.Sprintf(statement, fieldPointerVar, fieldPointerVar, name, shorthands, usage)
+	} else {
+		switch fieldType.Kind() {
+		case reflect.Bool:
+			statement = `BoolVarP(%s, "%s", "%s", %s, "%s")`
+		case reflect.Int:
+			statement = `IntVarP(%s, "%s", "%s", %s, "%s")`
+		case reflect.Int64:
+			statement = `Int64VarP(%s, "%s", "%s", %s, "%s")`
+		case reflect.Float64:
+			statement = `Float64VarP(%s, "%s", "%s", %s, "%s")`
+		case reflect.String:
+			statement = `StringVarP(%s, "%s", "%s", %s, "%s")`
+		case reflect.Slice:
+			if isLibsacloudIDType(fieldType.Elem()) {
+				statement = `VarP(base.NewIDSliceFlag(%s, %s), "%s", "%s", "%s")`
+				return fmt.Sprintf(statement, fieldPointerVar, fieldPointerVar, name, shorthands, usage)
+			} else {
+				switch fieldType.Elem().Kind() {
+				case reflect.Int64:
+					statement = `Int64SliceVarP(%s, "%s", "%s", %s, "%s")`
+				case reflect.String:
+					statement = `StringSliceVarP(%s, "%s", "%s", %s, "%s")`
+				default:
+					panic(fmt.Sprintf("unsupported type: field: %s, type: []%s", field.Name, fieldType.Elem().Kind().String()))
+				}
+			}
+		default:
+			panic(fmt.Sprintf("unsupported type: field: %s, type: %s", field.Name, fieldType.Kind().String()))
+		}
+	}
+
+	return fmt.Sprintf(statement, fieldPointerVar, name, shorthands, fieldVar, usage)
+}
+
+func isLibsacloudIDType(t reflect.Type) bool {
+	return reflect.TypeOf(types.ID(0)) == t
+}
+
+func dereferencePtrType(t reflect.Type) reflect.Type {
+	if t.Kind() != reflect.Ptr {
+		return t
+	}
+	return dereferencePtrType(t.Elem())
+}
diff --git a/tools/gen-cli-commands-v1/main.go b/tools/gen-cli-commands-v1/main.go
new file mode 100644
index 00000000..96875de7
--- /dev/null
+++ b/tools/gen-cli-commands-v1/main.go
@@ -0,0 +1,117 @@
+// Copyright 2017-2020 The Usacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+	"bytes"
+	"fmt"
+	"io/ioutil"
+	"log"
+	"path/filepath"
+	"text/template"
+
+	"github.com/sacloud/usacloud/tools/utils"
+
+	"github.com/sacloud/usacloud/tools"
+)
+
+var (
+	destination = "src/github.com/sacloud/usacloud/pkg/cmd"
+	ctx         = tools.NewGenerateContext()
+)
+
+func main() {
+	log.SetFlags(0)
+	log.SetPrefix("gen-cli-commands: ")
+
+	for _, resource := range ctx.Resources {
+		for _, command := range resource.Commands {
+			// TODO 実験的実装、パラメータが設定されている場合だけ処理する
+			if command.Parameters == nil {
+				continue
+			}
+
+			filePath := filepath.Join(destination, resource.PackageDirName(), command.CLICommandGeneratedSourceFile())
+			fileFullPath := filepath.Join(ctx.Gopath(), filePath)
+
+			src, err := generateSource(command)
+			if err != nil {
+				log.Fatalf("writing output: %s", err)
+			}
+
+			err = ioutil.WriteFile(fileFullPath, utils.Sformat([]byte(src)), 0644)
+			if err != nil {
+				log.Fatalf("writing output: %s", err)
+			}
+			fmt.Printf("generated: %s\n", filePath)
+		}
+	}
+}
+
+func generateSource(command *tools.Command) (string, error) {
+	buf := bytes.NewBufferString("")
+	t := template.New("t")
+	template.Must(t.Parse(srcTemplate))
+	err := t.Execute(buf, command)
+	command.Resource.PackageDirName()
+	return buf.String(), err
+}
+
+var srcTemplate = `// Code generated by 'github.com/sacloud/usacloud/tools/gen-cli-commands-v1'; DO NOT EDIT
+
+package {{ .PackageDirName }}
+
+import (
+	"github.com/sacloud/libsacloud/v2/sacloud/pointer"
+	"github.com/sacloud/libsacloud/v2/sacloud/types"
+	"github.com/sacloud/usacloud/pkg/cmd/base"
+	"github.com/spf13/pflag"
+)
+
+func (p *{{.CLICommandParameterTypeName}}) BuildFlags(fs *pflag.FlagSet) {
+	{{ .CLIFlagDefinitionStatements "p" "fs" }} 
+	fs.SetNormalizeFunc(p.normalizeFlagName)
+}
+
+func (p *{{.CLICommandParameterTypeName}}) normalizeFlagName(_ *pflag.FlagSet, name string) pflag.NormalizedName {
+{{ if .HasAliases -}}
+	switch name {
+{{ range .Fields }}{{ $name := .FlagName }}{{ range .Aliases -}}
+	case "{{.}}":
+		name = "{{ $name }}"
+{{ end }}{{ end -}}
+	}
+{{ end -}}
+	return pflag.NormalizedName(name)
+}
+
+func (p *{{.CLICommandParameterTypeName}}) CategorizedFlagSets(cmd *cobra.Command) []*base.FlagSet {
+	var sets []*base.FlagSet
+	{{ range .CategorizedParameterFields -}}
+	{
+		var fs *pflag.FlagSet
+		fs = pflag.NewFlagSet("{{.Key}}", pflag.ContinueOnError)
+		{{ range .Fields -}}
+		fs.AddFlag(cmd.LocalFlags().Lookup("{{ .FlagName }}"))
+		{{ end -}}
+		sets = append(sets, &base.FlagSet{
+			Title: "{{.DisplayName}}",
+			Flags: fs,
+		})
+	}
+	{{ end }}
+	return sets	
+}
+`
diff --git a/tools/gen-cli-commands/main.go b/tools/gen-cli-commands/main.go
index 35e9edd6..c861e2d1 100644
--- a/tools/gen-cli-commands/main.go
+++ b/tools/gen-cli-commands/main.go
@@ -22,6 +22,8 @@ import (
 	"path/filepath"
 	"text/template"
 
+	"github.com/sacloud/usacloud/tools/utils"
+
 	"github.com/sacloud/usacloud/tools"
 )
 
@@ -43,7 +45,7 @@ func main() {
 			log.Fatalf("writing output: %s", err)
 		}
 
-		err = ioutil.WriteFile(fileFullPath, tools.Sformat([]byte(src)), 0644)
+		err = ioutil.WriteFile(fileFullPath, utils.Sformat([]byte(src)), 0644)
 		if err != nil {
 			log.Fatalf("writing output: %s", err)
 		}
diff --git a/tools/gen-cli-normalize-flag-name/main.go b/tools/gen-cli-normalize-flag-name/main.go
index d492daa1..a941417c 100644
--- a/tools/gen-cli-normalize-flag-name/main.go
+++ b/tools/gen-cli-normalize-flag-name/main.go
@@ -22,6 +22,8 @@ import (
 	"path/filepath"
 	"text/template"
 
+	"github.com/sacloud/usacloud/tools/utils"
+
 	"github.com/sacloud/usacloud/tools"
 )
 
@@ -43,7 +45,7 @@ func main() {
 			log.Fatalf("writing output: %s", err)
 		}
 
-		err = ioutil.WriteFile(fileFullPath, tools.Sformat([]byte(src)), 0644)
+		err = ioutil.WriteFile(fileFullPath, utils.Sformat([]byte(src)), 0644)
 		if err != nil {
 			log.Fatalf("writing output: %s", err)
 		}
diff --git a/tools/gen-cli-resource-finder/main.go b/tools/gen-cli-resource-finder/main.go
index 2cb5cdeb..4f5d9e4d 100644
--- a/tools/gen-cli-resource-finder/main.go
+++ b/tools/gen-cli-resource-finder/main.go
@@ -22,6 +22,8 @@ import (
 	"path/filepath"
 	"text/template"
 
+	"github.com/sacloud/usacloud/tools/utils"
+
 	"github.com/sacloud/usacloud/tools"
 )
 
@@ -43,7 +45,7 @@ func main() {
 			log.Fatalf("writing output: %s", err)
 		}
 
-		err = ioutil.WriteFile(fileFullPath, tools.Sformat([]byte(src)), 0644)
+		err = ioutil.WriteFile(fileFullPath, utils.Sformat([]byte(src)), 0644)
 		if err != nil {
 			log.Fatalf("writing output: %s", err)
 		}
diff --git a/tools/gen-cli-root-command/main.go b/tools/gen-cli-root-command/main.go
index 7c5bb805..f1c9645f 100644
--- a/tools/gen-cli-root-command/main.go
+++ b/tools/gen-cli-root-command/main.go
@@ -22,6 +22,8 @@ import (
 	"path/filepath"
 	"text/template"
 
+	"github.com/sacloud/usacloud/tools/utils"
+
 	"github.com/sacloud/usacloud/tools"
 )
 
@@ -42,7 +44,7 @@ func main() {
 		log.Fatalf("writing output: %s", err)
 	}
 
-	err = ioutil.WriteFile(fileFullPath, tools.Sformat([]byte(src)), 0644)
+	err = ioutil.WriteFile(fileFullPath, utils.Sformat([]byte(src)), 0644)
 	if err != nil {
 		log.Fatalf("writing output: %s", err)
 	}
diff --git a/tools/gen-cli-usage/main.go b/tools/gen-cli-usage/main.go
index ec80c52b..3213e8a6 100644
--- a/tools/gen-cli-usage/main.go
+++ b/tools/gen-cli-usage/main.go
@@ -22,6 +22,8 @@ import (
 	"path/filepath"
 	"text/template"
 
+	"github.com/sacloud/usacloud/tools/utils"
+
 	"github.com/sacloud/usacloud/tools"
 )
 
@@ -43,7 +45,7 @@ func main() {
 			log.Fatalf("writing output: %s", err)
 		}
 
-		err = ioutil.WriteFile(fileFullPath, tools.Sformat([]byte(src)), 0644)
+		err = ioutil.WriteFile(fileFullPath, utils.Sformat([]byte(src)), 0644)
 		if err != nil {
 			log.Fatalf("writing output: %s", err)
 		}
diff --git a/tools/gen-command-funcs/main.go b/tools/gen-command-funcs/main.go
index f6ed7043..cdbf3780 100644
--- a/tools/gen-command-funcs/main.go
+++ b/tools/gen-command-funcs/main.go
@@ -23,6 +23,8 @@ import (
 	"path/filepath"
 	"text/template"
 
+	"github.com/sacloud/usacloud/tools/utils"
+
 	"github.com/sacloud/usacloud/pkg/schema"
 	"github.com/sacloud/usacloud/tools"
 )
@@ -64,7 +66,7 @@ func generateResource(resource *tools.Resource) error {
 		// target file is exist?
 		_, err = os.Stat(outputName)
 		if !c.UseCustomCommand || err != nil {
-			err = ioutil.WriteFile(outputName, tools.Sformat([]byte(src)), 0644)
+			err = ioutil.WriteFile(outputName, utils.Sformat([]byte(src)), 0644)
 			if err != nil {
 				return err
 			}
diff --git a/tools/gen-command-params/main.go b/tools/gen-command-params/main.go
index c8eadb25..b0294fb5 100644
--- a/tools/gen-command-params/main.go
+++ b/tools/gen-command-params/main.go
@@ -24,6 +24,8 @@ import (
 	"strings"
 	"text/template"
 
+	"github.com/sacloud/usacloud/tools/utils"
+
 	"github.com/sacloud/usacloud/tools"
 )
 
@@ -62,7 +64,7 @@ func main() {
 		filePath := filepath.Join(destination, strings.ToLower(baseName))
 		fileFullPath := filepath.Join(ctx.Gopath(), filePath)
 
-		err = ioutil.WriteFile(fileFullPath, tools.Sformat([]byte(src)), 0644)
+		err = ioutil.WriteFile(fileFullPath, utils.Sformat([]byte(src)), 0644)
 		if err != nil {
 			log.Fatalf("writing output: %s", err)
 		}
@@ -92,7 +94,6 @@ import (
 	"github.com/sacloud/usacloud/pkg/cli"
 	"github.com/sacloud/usacloud/pkg/config"
 	"github.com/sacloud/usacloud/pkg/define"
-	"github.com/sacloud/usacloud/pkg/config"
 	"github.com/sacloud/usacloud/pkg/schema"
 	"github.com/sacloud/usacloud/pkg/output"
 	"github.com/sacloud/usacloud/pkg/util"
@@ -174,7 +175,7 @@ func (p *{{.InputParameterTypeName}}) validate() error {
 	}
 	{{ if .HasOutputOption }}
 	{
-		validator := schema.ValidateInStrValues(define.AllowOutputTypes...)
+		validator := schema.ValidateInStrValues(config.AllowOutputTypes...)
 		errs := validator("--output-type" , p.OutputType )
 		if errs != nil {
 			errors = append(errors , errs...)
diff --git a/tools/params.go b/tools/params.go
index 290dbda0..712c4f1c 100644
--- a/tools/params.go
+++ b/tools/params.go
@@ -20,6 +20,8 @@ import (
 	"strings"
 	"text/template"
 
+	"github.com/sacloud/usacloud/tools/utils"
+
 	"github.com/sacloud/libsacloud/v2/sacloud/types"
 	"github.com/sacloud/usacloud/pkg/schema"
 )
@@ -53,15 +55,15 @@ func (p *Parameter) LongAliases() []string {
 }
 
 func (p *Parameter) FlagName() string {
-	return ToDashedName(p.Name)
+	return utils.ToDashedName(p.Name)
 }
 
 func (p *Parameter) FieldName() string {
-	return ToCamelCaseName(p.Name)
+	return utils.ToCamelCaseName(p.Name)
 }
 
 func (p *Parameter) FlagNameWithDash() string {
-	return ToCLIFlagName(p.Name)
+	return utils.ToCLIFlagName(p.Name)
 }
 
 func (p *Parameter) FlagNameShorthands() string {
@@ -326,8 +328,8 @@ func (p *Parameter) ValidatorStatements() string {
 		conflictsWith := map[string]interface{}{}
 
 		for _, key := range p.ConflictsWith {
-			k := ToCLIFlagName(key)
-			v := ToCamelCaseName(key)
+			k := utils.ToCLIFlagName(key)
+			v := utils.ToCamelCaseName(key)
 			conflictsWith[k] = v
 		}
 
@@ -360,7 +362,7 @@ func (p *Parameter) ValidatorStatements() string {
 func (p *Parameter) SetterFuncName() string {
 	n := p.DestinationProp
 	if n == "" {
-		n = fmt.Sprintf("Set%s", ToCamelCaseName(p.Name))
+		n = fmt.Sprintf("Set%s", utils.ToCamelCaseName(p.Name))
 	}
 	return n
 }
@@ -368,7 +370,7 @@ func (p *Parameter) SetterFuncName() string {
 func (p *Parameter) DestinationName() string {
 	n := p.DestinationProp
 	if n == "" {
-		n = ToCamelCaseName(p.Name)
+		n = utils.ToCamelCaseName(p.Name)
 	}
 	return n
 }
diff --git a/tools/resource.go b/tools/resource.go
index 8da001ac..c422e67b 100644
--- a/tools/resource.go
+++ b/tools/resource.go
@@ -18,6 +18,8 @@ import (
 	"fmt"
 	"sort"
 
+	"github.com/sacloud/usacloud/tools/utils"
+
 	"github.com/sacloud/usacloud/pkg/schema"
 )
 
@@ -76,11 +78,11 @@ func (r *Resource) buildCategorizedCommands() {
 }
 
 func (r *Resource) CLIName() string {
-	return ToDashedName(r.Name)
+	return utils.ToDashedName(r.Name)
 }
 
 func (r *Resource) AliasesLiteral() string {
-	return FlattenStringList(r.Resource.Aliases)
+	return utils.FlattenStringList(r.Resource.Aliases)
 }
 
 func (r *Resource) Usage() string {
@@ -92,35 +94,35 @@ func (r *Resource) Usage() string {
 }
 
 func (r *Resource) CLIVariableFuncName() string {
-	return fmt.Sprintf("%sCmd", ToCamelWithFirstLower(r.Name))
+	return fmt.Sprintf("%sCmd", utils.ToCamelWithFirstLower(r.Name))
 }
 
 func (r *Resource) CLISourceFileName() string {
-	return fmt.Sprintf("zz_%s_gen.go", ToSnakeCaseName(r.Name))
+	return fmt.Sprintf("zz_%s_gen.go", utils.ToSnakeCaseName(r.Name))
 }
 
 func (r *Resource) CLIResourceFinderSourceFileName() string {
-	return fmt.Sprintf("zz_%s_finder_gen.go", ToSnakeCaseName(r.Name))
+	return fmt.Sprintf("zz_%s_finder_gen.go", utils.ToSnakeCaseName(r.Name))
 }
 
 func (r *Resource) CLIUsageFileName() string {
-	return fmt.Sprintf("zz_%s_usage_gen.go", ToSnakeCaseName(r.Name))
+	return fmt.Sprintf("zz_%s_usage_gen.go", utils.ToSnakeCaseName(r.Name))
 }
 
 func (r *Resource) CLINormalizeFlagsFileName() string {
-	return fmt.Sprintf("zz_%s_normalize_flag_names_gen.go", ToSnakeCaseName(r.Name))
+	return fmt.Sprintf("zz_%s_normalize_flag_names_gen.go", utils.ToSnakeCaseName(r.Name))
 }
 
 func (r *Resource) ParameterSourceFileName() string {
-	return fmt.Sprintf("zz_%s_gen.go", ToSnakeCaseName(r.Name))
+	return fmt.Sprintf("zz_%s_gen.go", utils.ToSnakeCaseName(r.Name))
 }
 
 func (r *Resource) CommandOrderFunc() string {
-	return fmt.Sprintf("%sCommandOrder", ToCamelWithFirstLower(r.Name))
+	return fmt.Sprintf("%sCommandOrder", utils.ToCamelWithFirstLower(r.Name))
 }
 
 func (r *Resource) PackageDirName() string {
-	n := ToLowerName(r.Name)
+	n := utils.ToLowerName(r.Name)
 	switch n {
 	case "switch":
 		return "swytch"
diff --git a/tools/functions.go b/tools/utils/functions.go
similarity index 99%
rename from tools/functions.go
rename to tools/utils/functions.go
index 2ed23c97..8f3760d4 100644
--- a/tools/functions.go
+++ b/tools/utils/functions.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package tools
+package utils
 
 import (
 	"fmt"
diff --git a/vendor/github.com/sacloud/libsacloud/v2/helper/builder/disk/api_client.go b/vendor/github.com/sacloud/libsacloud/v2/helper/builder/disk/api_client.go
new file mode 100644
index 00000000..1f3b9fa2
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/helper/builder/disk/api_client.go
@@ -0,0 +1,84 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package disk
+
+import (
+	"context"
+
+	"github.com/sacloud/libsacloud/v2/sacloud"
+	"github.com/sacloud/libsacloud/v2/sacloud/types"
+)
+
+// APIClient builderが利用するAPIクライアント群
+type APIClient struct {
+	Archive  ArchiveFinder
+	Disk     CreateDiskHandler
+	DiskPlan PlanReader
+	Note     NoteHandler
+	SSHKey   SSHKeyHandler
+}
+
+// ArchiveFinder アーカイブ検索のためのインターフェース
+type ArchiveFinder interface {
+	Find(ctx context.Context, zone string, conditions *sacloud.FindCondition) (*sacloud.ArchiveFindResult, error)
+	Read(ctx context.Context, zone string, id types.ID) (*sacloud.Archive, error)
+}
+
+// CreateDiskHandler ディスク操作のためのインターフェース
+type CreateDiskHandler interface {
+	Create(ctx context.Context, zone string, createParam *sacloud.DiskCreateRequest, distantFrom []types.ID) (*sacloud.Disk, error)
+	CreateWithConfig(
+		ctx context.Context,
+		zone string,
+		createParam *sacloud.DiskCreateRequest,
+		editParam *sacloud.DiskEditRequest,
+		bootAtAvailable bool,
+		distantFrom []types.ID,
+	) (*sacloud.Disk, error)
+	Update(ctx context.Context, zone string, id types.ID, updateParam *sacloud.DiskUpdateRequest) (*sacloud.Disk, error)
+	Config(ctx context.Context, zone string, id types.ID, editParam *sacloud.DiskEditRequest) error
+	Read(ctx context.Context, zone string, id types.ID) (*sacloud.Disk, error)
+	ConnectToServer(ctx context.Context, zone string, id types.ID, serverID types.ID) error
+}
+
+// PlanReader ディスクプラン取得のためのインターフェース
+type PlanReader interface {
+	Read(ctx context.Context, zone string, id types.ID) (*sacloud.DiskPlan, error)
+}
+
+// NoteHandler スタートアップスクリプト参照のためのインターフェース
+type NoteHandler interface {
+	Read(ctx context.Context, id types.ID) (*sacloud.Note, error)
+	Create(ctx context.Context, param *sacloud.NoteCreateRequest) (*sacloud.Note, error)
+	Delete(ctx context.Context, id types.ID) error
+}
+
+// SSHKeyHandler SSHKey参照のためのインターフェース
+type SSHKeyHandler interface {
+	Read(ctx context.Context, id types.ID) (*sacloud.SSHKey, error)
+	Generate(ctx context.Context, param *sacloud.SSHKeyGenerateRequest) (*sacloud.SSHKeyGenerated, error)
+	Delete(ctx context.Context, id types.ID) error
+}
+
+// NewBuildersAPIClient APIクライアントの作成
+func NewBuildersAPIClient(caller sacloud.APICaller) *APIClient {
+	return &APIClient{
+		Archive:  sacloud.NewArchiveOp(caller),
+		Disk:     sacloud.NewDiskOp(caller),
+		DiskPlan: sacloud.NewDiskPlanOp(caller),
+		Note:     sacloud.NewNoteOp(caller),
+		SSHKey:   sacloud.NewSSHKeyOp(caller),
+	}
+}
diff --git a/vendor/github.com/sacloud/libsacloud/v2/helper/builder/disk/builder.go b/vendor/github.com/sacloud/libsacloud/v2/helper/builder/disk/builder.go
new file mode 100644
index 00000000..c559fb20
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/helper/builder/disk/builder.go
@@ -0,0 +1,806 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package disk
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"reflect"
+
+	"github.com/sacloud/libsacloud/v2/helper/builder"
+	"github.com/sacloud/libsacloud/v2/pkg/size"
+
+	archiveUtil "github.com/sacloud/libsacloud/v2/helper/query"
+	"github.com/sacloud/libsacloud/v2/sacloud"
+	"github.com/sacloud/libsacloud/v2/sacloud/ostype"
+	"github.com/sacloud/libsacloud/v2/sacloud/types"
+)
+
+// Builder ディスクの構築インターフェース
+type Builder interface {
+	Validate(ctx context.Context, zone string) error
+	Build(ctx context.Context, zone string, serverID types.ID) (*BuildResult, error)
+	Update(ctx context.Context, zone string) (*UpdateResult, error)
+	DiskID() types.ID
+	UpdateLevel(ctx context.Context, zone string, disk *sacloud.Disk) builder.UpdateLevel
+}
+
+// BuildResult ディスク構築結果
+type BuildResult struct {
+	DiskID          types.ID
+	GeneratedSSHKey *sacloud.SSHKeyGenerated
+}
+
+// UpdateResult ディスク更新結果
+type UpdateResult struct {
+	Disk *sacloud.Disk
+}
+
+// FromUnixBuilder Unix系パブリックアーカイブからディスクを作成するリクエスト
+type FromUnixBuilder struct {
+	OSType ostype.ArchiveOSType
+
+	Name        string
+	SizeGB      int
+	DistantFrom []types.ID
+	PlanID      types.ID
+	Connection  types.EDiskConnection
+	Description string
+	Tags        types.Tags
+	IconID      types.ID
+
+	EditParameter *UnixEditRequest
+
+	Client *APIClient
+
+	ID types.ID
+
+	generatedSSHKey *sacloud.SSHKeyGenerated
+	generatedNotes  []*sacloud.Note
+}
+
+// Validate 設定値の検証
+func (d *FromUnixBuilder) Validate(ctx context.Context, zone string) error {
+	if !d.OSType.IsSupportDiskEdit() {
+		return fmt.Errorf("invalid OSType: %s", d.OSType.String())
+	}
+	if err := validateDiskPlan(ctx, d.Client, zone, d.PlanID, d.SizeGB); err != nil {
+		return err
+	}
+
+	if d.EditParameter != nil {
+		return d.EditParameter.Validate(ctx, d.Client)
+	}
+	return nil
+}
+
+// Build ディスクの構築
+func (d *FromUnixBuilder) Build(ctx context.Context, zone string, serverID types.ID) (*BuildResult, error) {
+	res, err := build(ctx, d.Client, zone, serverID, d.DistantFrom, d)
+	if err != nil {
+		return nil, err
+	}
+	d.ID = res.DiskID
+
+	if d.generatedSSHKey != nil {
+		res.GeneratedSSHKey = d.generatedSSHKey
+	}
+
+	if d.EditParameter != nil {
+		if d.EditParameter.IsSSHKeysEphemeral {
+			if err := d.Client.SSHKey.Delete(ctx, d.generatedSSHKey.ID); err != nil {
+				return nil, err
+			}
+		}
+		if d.EditParameter.IsNotesEphemeral {
+			for _, note := range d.generatedNotes {
+				if err := d.Client.Note.Delete(ctx, note.ID); err != nil {
+					return nil, err
+				}
+			}
+		}
+	}
+	return res, nil
+}
+
+// Update ディスクの更新
+func (d *FromUnixBuilder) Update(ctx context.Context, zone string) (*UpdateResult, error) {
+	return update(ctx, d.Client, zone, d)
+}
+
+// DiskID ディスクID取得
+func (d *FromUnixBuilder) DiskID() types.ID {
+	return d.ID
+}
+
+// UpdateLevel Update時にどのレベルの変更が必要か
+func (d *FromUnixBuilder) UpdateLevel(ctx context.Context, zone string, disk *sacloud.Disk) builder.UpdateLevel {
+	return updateLevel(disk, d.EditParameter != nil, d)
+}
+
+func (d *FromUnixBuilder) updateDiskParameter() *sacloud.DiskUpdateRequest {
+	return &sacloud.DiskUpdateRequest{
+		Name:        d.Name,
+		Description: d.Description,
+		Tags:        d.Tags,
+		IconID:      d.IconID,
+		Connection:  d.Connection,
+	}
+}
+
+func (d *FromUnixBuilder) createDiskParameter(ctx context.Context, client *APIClient, zone string, serverID types.ID) (*sacloud.DiskCreateRequest, *sacloud.DiskEditRequest, error) {
+	archive, err := archiveUtil.FindArchiveByOSType(ctx, client.Archive, zone, d.OSType)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	createReq := &sacloud.DiskCreateRequest{
+		DiskPlanID:      d.PlanID,
+		SizeMB:          d.SizeGB * size.GiB,
+		Connection:      d.Connection,
+		SourceArchiveID: archive.ID,
+		ServerID:        serverID,
+		Name:            d.Name,
+		Description:     d.Description,
+		Tags:            d.Tags,
+		IconID:          d.IconID,
+	}
+
+	var editReq *sacloud.DiskEditRequest
+	if d.EditParameter != nil {
+		req, sshKey, notes, err := d.EditParameter.prepareDiskEditParameter(ctx, client)
+		if err != nil {
+			return nil, nil, err
+		}
+		editReq = req
+		if sshKey != nil {
+			d.generatedSSHKey = sshKey
+		}
+		if len(notes) > 0 {
+			d.generatedNotes = notes
+		}
+	}
+
+	return createReq, editReq, nil
+}
+
+// FromFixedArchiveBuilder ディスクの修正をサポートしないパブリックアーカイブからディスクを作成するリクエスト
+type FromFixedArchiveBuilder struct {
+	OSType ostype.ArchiveOSType
+
+	Name        string
+	SizeGB      int
+	DistantFrom []types.ID
+	PlanID      types.ID
+	Connection  types.EDiskConnection
+	Description string
+	Tags        types.Tags
+	IconID      types.ID
+
+	Client *APIClient
+
+	ID types.ID
+
+	generatedSSHKey *sacloud.SSHKeyGenerated
+}
+
+// Validate 設定値の検証
+func (d *FromFixedArchiveBuilder) Validate(ctx context.Context, zone string) error {
+	if d.OSType.IsSupportDiskEdit() || d.OSType.IsWindows() {
+		return fmt.Errorf("invalid OSType: %s", d.OSType.String())
+	}
+	if err := validateDiskPlan(ctx, d.Client, zone, d.PlanID, d.SizeGB); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// Build ディスクの構築
+func (d *FromFixedArchiveBuilder) Build(ctx context.Context, zone string, serverID types.ID) (*BuildResult, error) {
+	res, err := build(ctx, d.Client, zone, serverID, d.DistantFrom, d)
+	if err != nil {
+		return nil, err
+	}
+	d.ID = res.DiskID
+	if d.generatedSSHKey != nil {
+		res.GeneratedSSHKey = d.generatedSSHKey
+	}
+	return res, nil
+}
+
+// Update ディスクの更新
+func (d *FromFixedArchiveBuilder) Update(ctx context.Context, zone string) (*UpdateResult, error) {
+	return update(ctx, d.Client, zone, d)
+}
+
+// DiskID ディスクID取得
+func (d *FromFixedArchiveBuilder) DiskID() types.ID {
+	return d.ID
+}
+
+// UpdateLevel Update時にどのレベルの変更が必要か
+func (d *FromFixedArchiveBuilder) UpdateLevel(ctx context.Context, zone string, disk *sacloud.Disk) builder.UpdateLevel {
+	return updateLevel(disk, false, d)
+}
+
+func (d *FromFixedArchiveBuilder) updateDiskParameter() *sacloud.DiskUpdateRequest {
+	return &sacloud.DiskUpdateRequest{
+		Name:        d.Name,
+		Description: d.Description,
+		Tags:        d.Tags,
+		IconID:      d.IconID,
+		Connection:  d.Connection,
+	}
+}
+
+func (d *FromFixedArchiveBuilder) createDiskParameter(ctx context.Context, client *APIClient, zone string, serverID types.ID) (*sacloud.DiskCreateRequest, *sacloud.DiskEditRequest, error) {
+	archive, err := archiveUtil.FindArchiveByOSType(ctx, client.Archive, zone, d.OSType)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	createReq := &sacloud.DiskCreateRequest{
+		DiskPlanID:      d.PlanID,
+		SizeMB:          d.SizeGB * size.GiB,
+		Connection:      d.Connection,
+		SourceArchiveID: archive.ID,
+		ServerID:        serverID,
+		Name:            d.Name,
+		Description:     d.Description,
+		Tags:            d.Tags,
+		IconID:          d.IconID,
+	}
+	return createReq, nil, nil
+}
+
+// FromWindowsBuilder Windows系パブリックアーカイブからディスクを作成するリクエスト
+type FromWindowsBuilder struct {
+	OSType ostype.ArchiveOSType
+
+	Name        string
+	SizeGB      int
+	DistantFrom []types.ID
+	PlanID      types.ID
+	Connection  types.EDiskConnection
+	Description string
+	Tags        types.Tags
+	IconID      types.ID
+
+	EditParameter *WindowsEditRequest
+
+	Client *APIClient
+
+	ID types.ID
+}
+
+// Validate 設定値の検証
+func (d *FromWindowsBuilder) Validate(ctx context.Context, zone string) error {
+	if !d.OSType.IsWindows() {
+		return fmt.Errorf("invalid OSType: %s", d.OSType.String())
+	}
+	if err := validateDiskPlan(ctx, d.Client, zone, d.PlanID, d.SizeGB); err != nil {
+		return err
+	}
+	return nil
+}
+
+// Build ディスクの構築
+func (d *FromWindowsBuilder) Build(ctx context.Context, zone string, serverID types.ID) (*BuildResult, error) {
+	res, err := build(ctx, d.Client, zone, serverID, d.DistantFrom, d)
+	if err != nil {
+		return nil, err
+	}
+	d.ID = res.DiskID
+	return res, nil
+}
+
+// Update ディスクの更新
+func (d *FromWindowsBuilder) Update(ctx context.Context, zone string) (*UpdateResult, error) {
+	return update(ctx, d.Client, zone, d)
+}
+
+// DiskID ディスクID取得
+func (d *FromWindowsBuilder) DiskID() types.ID {
+	return d.ID
+}
+
+// UpdateLevel Update時にどのレベルの変更が必要か
+func (d *FromWindowsBuilder) UpdateLevel(ctx context.Context, zone string, disk *sacloud.Disk) builder.UpdateLevel {
+	return updateLevel(disk, d.EditParameter != nil, d)
+}
+
+func (d *FromWindowsBuilder) updateDiskParameter() *sacloud.DiskUpdateRequest {
+	return &sacloud.DiskUpdateRequest{
+		Name:        d.Name,
+		Description: d.Description,
+		Tags:        d.Tags,
+		IconID:      d.IconID,
+		Connection:  d.Connection,
+	}
+}
+
+func (d *FromWindowsBuilder) createDiskParameter(ctx context.Context, client *APIClient, zone string, serverID types.ID) (*sacloud.DiskCreateRequest, *sacloud.DiskEditRequest, error) {
+	archive, err := archiveUtil.FindArchiveByOSType(ctx, client.Archive, zone, d.OSType)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	createReq := &sacloud.DiskCreateRequest{
+		DiskPlanID:      d.PlanID,
+		SizeMB:          d.SizeGB * size.GiB,
+		Connection:      d.Connection,
+		SourceArchiveID: archive.ID,
+		ServerID:        serverID,
+		Name:            d.Name,
+		Description:     d.Description,
+		Tags:            d.Tags,
+		IconID:          d.IconID,
+	}
+
+	var editReq *sacloud.DiskEditRequest
+	if d.EditParameter != nil {
+		editReq = d.EditParameter.prepareDiskEditParameter()
+	}
+
+	return createReq, editReq, nil
+}
+
+// FromDiskOrArchiveBuilder ディスクorアーカイブからディスクを作成するリクエスト
+//
+// ディスクの修正が可能かは実行時にさくらのクラウドAPI側にて判定される
+type FromDiskOrArchiveBuilder struct {
+	SourceDiskID    types.ID
+	SourceArchiveID types.ID
+
+	Name        string
+	SizeGB      int
+	DistantFrom []types.ID
+	PlanID      types.ID
+	Connection  types.EDiskConnection
+	Description string
+	Tags        types.Tags
+	IconID      types.ID
+
+	EditParameter *UnixEditRequest
+
+	Client *APIClient
+
+	ID types.ID
+
+	generatedSSHKey *sacloud.SSHKeyGenerated
+	generatedNotes  []*sacloud.Note
+}
+
+// Validate 設定値の検証
+func (d *FromDiskOrArchiveBuilder) Validate(ctx context.Context, zone string) error {
+	if d.SourceArchiveID.IsEmpty() && d.SourceDiskID.IsEmpty() {
+		return errors.New("SourceArchiveID or SourceDiskID is required")
+	}
+	if err := validateDiskPlan(ctx, d.Client, zone, d.PlanID, d.SizeGB); err != nil {
+		return err
+	}
+
+	if !d.SourceArchiveID.IsEmpty() {
+		if _, err := d.Client.Archive.Read(ctx, zone, d.SourceArchiveID); err != nil {
+			return err
+		}
+	}
+	if !d.SourceDiskID.IsEmpty() {
+		if _, err := d.Client.Disk.Read(ctx, zone, d.SourceDiskID); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// Build ディスクの構築
+func (d *FromDiskOrArchiveBuilder) Build(ctx context.Context, zone string, serverID types.ID) (*BuildResult, error) {
+	res, err := build(ctx, d.Client, zone, serverID, d.DistantFrom, d)
+	if err != nil {
+		return nil, err
+	}
+	d.ID = res.DiskID
+	if d.generatedSSHKey != nil {
+		res.GeneratedSSHKey = d.generatedSSHKey
+	}
+
+	if d.EditParameter != nil {
+		if d.EditParameter.IsSSHKeysEphemeral {
+			if err := d.Client.SSHKey.Delete(ctx, d.generatedSSHKey.ID); err != nil {
+				return nil, err
+			}
+		}
+		if d.EditParameter.IsNotesEphemeral {
+			for _, note := range d.generatedNotes {
+				if err := d.Client.Note.Delete(ctx, note.ID); err != nil {
+					return nil, err
+				}
+			}
+		}
+	}
+	return res, nil
+}
+
+// Update ディスクの更新
+func (d *FromDiskOrArchiveBuilder) Update(ctx context.Context, zone string) (*UpdateResult, error) {
+	return update(ctx, d.Client, zone, d)
+}
+
+// DiskID ディスクID取得
+func (d *FromDiskOrArchiveBuilder) DiskID() types.ID {
+	return d.ID
+}
+
+// UpdateLevel Update時にどのレベルの変更が必要か
+func (d *FromDiskOrArchiveBuilder) UpdateLevel(ctx context.Context, zone string, disk *sacloud.Disk) builder.UpdateLevel {
+	return updateLevel(disk, d.EditParameter != nil, d)
+}
+
+func (d *FromDiskOrArchiveBuilder) updateDiskParameter() *sacloud.DiskUpdateRequest {
+	return &sacloud.DiskUpdateRequest{
+		Name:        d.Name,
+		Description: d.Description,
+		Tags:        d.Tags,
+		IconID:      d.IconID,
+		Connection:  d.Connection,
+	}
+}
+
+func (d *FromDiskOrArchiveBuilder) createDiskParameter(ctx context.Context, client *APIClient, zone string, serverID types.ID) (*sacloud.DiskCreateRequest, *sacloud.DiskEditRequest, error) {
+	createReq := &sacloud.DiskCreateRequest{
+		DiskPlanID:      d.PlanID,
+		SizeMB:          d.SizeGB * size.GiB,
+		Connection:      d.Connection,
+		SourceArchiveID: d.SourceArchiveID,
+		SourceDiskID:    d.SourceDiskID,
+		ServerID:        serverID,
+		Name:            d.Name,
+		Description:     d.Description,
+		Tags:            d.Tags,
+		IconID:          d.IconID,
+	}
+
+	var editReq *sacloud.DiskEditRequest
+	if d.EditParameter != nil {
+		req, sshKey, notes, err := d.EditParameter.prepareDiskEditParameter(ctx, client)
+		if err != nil {
+			return nil, nil, err
+		}
+		editReq = req
+		if sshKey != nil {
+			d.generatedSSHKey = sshKey
+		}
+		if len(notes) > 0 {
+			d.generatedNotes = notes
+		}
+	}
+
+	return createReq, editReq, nil
+}
+
+// BlankBuilder ブランクディスクを作成する場合のリクエスト
+type BlankBuilder struct {
+	Name        string
+	SizeGB      int
+	DistantFrom []types.ID
+	PlanID      types.ID
+	Connection  types.EDiskConnection
+	Description string
+	Tags        types.Tags
+	IconID      types.ID
+
+	Client *APIClient
+
+	ID types.ID
+}
+
+// Validate 設定値の検証
+func (d *BlankBuilder) Validate(ctx context.Context, zone string) error {
+	if err := validateDiskPlan(ctx, d.Client, zone, d.PlanID, d.SizeGB); err != nil {
+		return err
+	}
+	return nil
+}
+
+// Build ディスクの構築
+func (d *BlankBuilder) Build(ctx context.Context, zone string, serverID types.ID) (*BuildResult, error) {
+	res, err := build(ctx, d.Client, zone, serverID, d.DistantFrom, d)
+	if err != nil {
+		return nil, err
+	}
+	d.ID = res.DiskID
+	return res, err
+}
+
+// Update ディスクの更新
+func (d *BlankBuilder) Update(ctx context.Context, zone string) (*UpdateResult, error) {
+	return update(ctx, d.Client, zone, d)
+}
+
+// DiskID ディスクID取得
+func (d *BlankBuilder) DiskID() types.ID {
+	return d.ID
+}
+
+// UpdateLevel Update時にどのレベルの変更が必要か
+func (d *BlankBuilder) UpdateLevel(ctx context.Context, zone string, disk *sacloud.Disk) builder.UpdateLevel {
+	return updateLevel(disk, false, d)
+}
+
+func (d *BlankBuilder) updateDiskParameter() *sacloud.DiskUpdateRequest {
+	return &sacloud.DiskUpdateRequest{
+		Name:        d.Name,
+		Description: d.Description,
+		Tags:        d.Tags,
+		IconID:      d.IconID,
+		Connection:  d.Connection,
+	}
+}
+
+func (d *BlankBuilder) createDiskParameter(ctx context.Context, client *APIClient, zone string, serverID types.ID) (*sacloud.DiskCreateRequest, *sacloud.DiskEditRequest, error) {
+	createReq := &sacloud.DiskCreateRequest{
+		DiskPlanID:  d.PlanID,
+		SizeMB:      d.SizeGB * size.GiB,
+		Connection:  d.Connection,
+		ServerID:    serverID,
+		Name:        d.Name,
+		Description: d.Description,
+		Tags:        d.Tags,
+		IconID:      d.IconID,
+	}
+	return createReq, nil, nil
+}
+
+// ConnectedDiskBuilder 既存ディスクを接続する場合のリクエスト
+type ConnectedDiskBuilder struct {
+	ID            types.ID
+	EditParameter *UnixEditRequest
+
+	Client *APIClient
+}
+
+// Validate 設定値の検証
+func (d *ConnectedDiskBuilder) Validate(ctx context.Context, zone string) error {
+	if d.ID.IsEmpty() {
+		return errors.New("DiskID is required")
+	}
+
+	if _, err := d.Client.Disk.Read(ctx, zone, d.ID); err != nil {
+		return err
+	}
+	return nil
+}
+
+// Build ディスクの構築
+func (d *ConnectedDiskBuilder) Build(ctx context.Context, zone string, serverID types.ID) (*BuildResult, error) {
+	res := &BuildResult{
+		DiskID: d.ID,
+	}
+	if !serverID.IsEmpty() {
+		if err := d.Client.Disk.ConnectToServer(ctx, zone, d.ID, serverID); err != nil {
+			return nil, err
+		}
+	}
+
+	if d.EditParameter != nil {
+		req, sshKey, _, err := d.EditParameter.prepareDiskEditParameter(ctx, d.Client)
+		if err != nil {
+			return nil, err
+		}
+		res.GeneratedSSHKey = sshKey
+		if err := d.Client.Disk.Config(ctx, zone, d.ID, req); err != nil {
+			return nil, err
+		}
+		waiter := sacloud.WaiterForReady(func() (interface{}, error) {
+			return d.Client.Disk.Read(ctx, zone, d.ID)
+		})
+		if _, err := waiter.WaitForState(ctx); err != nil {
+			return nil, err
+		}
+	}
+	return res, nil
+}
+
+// Update ディスクの更新
+func (d *ConnectedDiskBuilder) Update(ctx context.Context, zone string) (*UpdateResult, error) {
+	disk, err := d.Client.Disk.Read(ctx, zone, d.ID)
+	if err != nil {
+		return nil, err
+	}
+
+	if d.EditParameter != nil {
+		req, _, _, err := d.EditParameter.prepareDiskEditParameter(ctx, d.Client)
+		if err != nil {
+			return nil, err
+		}
+		if err := d.Client.Disk.Config(ctx, zone, d.ID, req); err != nil {
+			return nil, err
+		}
+		waiter := sacloud.WaiterForReady(func() (interface{}, error) {
+			return d.Client.Disk.Read(ctx, zone, d.ID)
+		})
+		if _, err := waiter.WaitForState(ctx); err != nil {
+			return nil, err
+		}
+	}
+
+	return &UpdateResult{Disk: disk}, nil
+}
+
+// DiskID ディスクID取得
+func (d *ConnectedDiskBuilder) DiskID() types.ID {
+	return d.ID
+}
+
+// UpdateLevel Update時にどのレベルの変更が必要か
+func (d *ConnectedDiskBuilder) UpdateLevel(ctx context.Context, zone string, disk *sacloud.Disk) builder.UpdateLevel {
+	return updateLevel(disk, d.EditParameter != nil, d)
+}
+
+func (d *ConnectedDiskBuilder) updateDiskParameter() *sacloud.DiskUpdateRequest {
+	return nil
+}
+
+func (d *ConnectedDiskBuilder) createDiskParameter(
+	ctx context.Context,
+	client *APIClient,
+	zone string,
+	serverID types.ID,
+) (*sacloud.DiskCreateRequest, *sacloud.DiskEditRequest, error) {
+	// noop
+	return nil, nil, nil
+}
+
+type diskBuilder interface {
+	createDiskParameter(
+		ctx context.Context,
+		client *APIClient,
+		zone string,
+		serverID types.ID,
+	) (*sacloud.DiskCreateRequest, *sacloud.DiskEditRequest, error)
+	updateDiskParameter() *sacloud.DiskUpdateRequest
+	DiskID() types.ID
+}
+
+func build(ctx context.Context, client *APIClient, zone string, serverID types.ID, distantFrom []types.ID, builder diskBuilder) (*BuildResult, error) {
+	var err error
+
+	diskReq, editReq, err := builder.createDiskParameter(ctx, client, zone, serverID)
+	if err != nil {
+		return nil, err
+	}
+	if diskReq == nil {
+		return nil, fmt.Errorf("disk create request is nil")
+	}
+	diskReq.ServerID = serverID
+
+	var disk *sacloud.Disk
+
+	if editReq == nil {
+		disk, err = client.Disk.Create(ctx, zone, diskReq, distantFrom)
+	} else {
+		disk, err = client.Disk.CreateWithConfig(ctx, zone, diskReq, editReq, false, distantFrom)
+	}
+	if err != nil {
+		if disk != nil {
+			return &BuildResult{DiskID: disk.ID}, err
+		}
+		return nil, err
+	}
+
+	waiter := sacloud.WaiterForReady(func() (interface{}, error) {
+		return client.Disk.Read(ctx, zone, disk.ID)
+	})
+	lastState, err := waiter.WaitForState(ctx)
+	if err != nil {
+		if lastState != nil {
+			return &BuildResult{DiskID: lastState.(*sacloud.Disk).ID}, err
+		}
+		return nil, err
+	}
+	disk = lastState.(*sacloud.Disk)
+
+	return &BuildResult{DiskID: disk.ID}, nil
+}
+
+func update(ctx context.Context, client *APIClient, zone string, builder diskBuilder) (*UpdateResult, error) {
+	var err error
+
+	diskID := builder.DiskID()
+	if diskID.IsEmpty() {
+		return nil, fmt.Errorf("disk id required")
+	}
+
+	diskReq, editReq, err := builder.createDiskParameter(ctx, client, zone, types.ID(0))
+	if err != nil {
+		return nil, err
+	}
+	if diskReq == nil {
+		return nil, fmt.Errorf("disk update request is nil")
+	}
+
+	disk, err := client.Disk.Update(ctx, zone, diskID, &sacloud.DiskUpdateRequest{
+		Name:        diskReq.Name,
+		Description: diskReq.Description,
+		Tags:        diskReq.Tags,
+		IconID:      diskReq.IconID,
+		Connection:  diskReq.Connection,
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	if editReq != nil {
+		if err := client.Disk.Config(ctx, zone, disk.ID, editReq); err != nil {
+			return nil, err
+		}
+	}
+
+	waiter := sacloud.WaiterForReady(func() (interface{}, error) {
+		return client.Disk.Read(ctx, zone, disk.ID)
+	})
+	lastState, err := waiter.WaitForState(ctx)
+	if err != nil {
+		return nil, err
+	}
+	disk = lastState.(*sacloud.Disk)
+
+	return &UpdateResult{Disk: disk}, nil
+}
+
+func validateDiskPlan(ctx context.Context, client *APIClient, zone string, diskPlanID types.ID, sizeGB int) error {
+	plan, err := client.DiskPlan.Read(ctx, zone, diskPlanID)
+	if err != nil {
+		return err
+	}
+	found := false
+	for _, size := range plan.Size {
+		if size.Availability.IsAvailable() && size.GetSizeGB() == sizeGB {
+			found = true
+			break
+		}
+	}
+	if !found {
+		return fmt.Errorf("disk plan[%s:%dGB] is not found", plan.Name, sizeGB)
+	}
+	return nil
+}
+
+func updateLevel(disk *sacloud.Disk, hasEditReq bool, b diskBuilder) builder.UpdateLevel {
+	if disk.ID != b.DiskID() || hasEditReq {
+		return builder.UpdateLevelNeedShutdown
+	}
+
+	current := &sacloud.DiskUpdateRequest{
+		Name:        disk.Name,
+		Description: disk.Description,
+		Tags:        disk.Tags,
+		IconID:      disk.IconID,
+		Connection:  disk.Connection,
+	}
+	desired := b.updateDiskParameter()
+	if desired == nil {
+		return builder.UpdateLevelNone
+	}
+	if reflect.DeepEqual(current, desired) {
+		if current.Connection != desired.Connection {
+			return builder.UpdateLevelNeedShutdown
+		}
+		return builder.UpdateLevelSimple
+	}
+	return builder.UpdateLevelNone
+}
diff --git a/vendor/github.com/sacloud/libsacloud/v2/helper/builder/disk/director.go b/vendor/github.com/sacloud/libsacloud/v2/helper/builder/disk/director.go
new file mode 100644
index 00000000..2d4c7711
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/helper/builder/disk/director.go
@@ -0,0 +1,125 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package disk
+
+import (
+	"github.com/sacloud/libsacloud/v2/sacloud/ostype"
+	"github.com/sacloud/libsacloud/v2/sacloud/types"
+)
+
+// Director パラメータに応じて適切なDiskBuilderを構築する
+type Director struct {
+	OSType ostype.ArchiveOSType
+
+	Name        string
+	SizeGB      int
+	DistantFrom []types.ID
+	PlanID      types.ID
+	Connection  types.EDiskConnection
+	Description string
+	Tags        types.Tags
+	IconID      types.ID
+
+	DiskID          types.ID
+	SourceDiskID    types.ID
+	SourceArchiveID types.ID
+
+	EditParameter *EditRequest
+	Client        *APIClient
+}
+
+// Builder パラメータに応じて適切なDiskBuilderを返す
+func (d *Director) Builder() Builder {
+	switch {
+	case d.OSType == ostype.Custom:
+		switch {
+		case !d.DiskID.IsEmpty():
+			return &ConnectedDiskBuilder{
+				ID:            d.DiskID,
+				EditParameter: d.EditParameter.ToUnixDiskEditRequest(),
+				Client:        d.Client,
+			}
+		case !d.SourceDiskID.IsEmpty(), !d.SourceArchiveID.IsEmpty():
+			return &FromDiskOrArchiveBuilder{
+				SourceDiskID:    d.SourceDiskID,
+				SourceArchiveID: d.SourceArchiveID,
+				Name:            d.Name,
+				SizeGB:          d.SizeGB,
+				DistantFrom:     d.DistantFrom,
+				PlanID:          d.PlanID,
+				Connection:      d.Connection,
+				Description:     d.Description,
+				Tags:            d.Tags,
+				IconID:          d.IconID,
+				EditParameter:   d.EditParameter.ToUnixDiskEditRequest(),
+				Client:          d.Client,
+			}
+		default:
+			return &BlankBuilder{
+				Name:        d.Name,
+				SizeGB:      d.SizeGB,
+				DistantFrom: d.DistantFrom,
+				PlanID:      d.PlanID,
+				Connection:  d.Connection,
+				Description: d.Description,
+				Tags:        d.Tags,
+				IconID:      d.IconID,
+				Client:      d.Client,
+			}
+		}
+	case d.OSType.IsSupportDiskEdit():
+		return &FromUnixBuilder{
+			OSType:        d.OSType,
+			Name:          d.Name,
+			SizeGB:        d.SizeGB,
+			DistantFrom:   d.DistantFrom,
+			PlanID:        d.PlanID,
+			Connection:    d.Connection,
+			Description:   d.Description,
+			Tags:          d.Tags,
+			IconID:        d.IconID,
+			EditParameter: d.EditParameter.ToUnixDiskEditRequest(),
+			Client:        d.Client,
+		}
+	case d.OSType.IsWindows():
+		return &FromWindowsBuilder{
+			OSType:        d.OSType,
+			Name:          d.Name,
+			SizeGB:        d.SizeGB,
+			DistantFrom:   d.DistantFrom,
+			PlanID:        d.PlanID,
+			Connection:    d.Connection,
+			Description:   d.Description,
+			Tags:          d.Tags,
+			IconID:        d.IconID,
+			EditParameter: d.EditParameter.ToWindowsDiskEditRequest(),
+			Client:        d.Client,
+		}
+	default:
+		// 現在はOSTypeにディスクの修正不可のアーカイブはないためここには到達しない
+		return &FromFixedArchiveBuilder{
+			OSType:      d.OSType,
+			Name:        d.Name,
+			SizeGB:      d.SizeGB,
+			DistantFrom: d.DistantFrom,
+			PlanID:      d.PlanID,
+			Connection:  d.Connection,
+			Description: d.Description,
+			Tags:        d.Tags,
+			IconID:      d.IconID,
+			Client:      d.Client,
+		}
+	}
+}
diff --git a/vendor/github.com/sacloud/libsacloud/v2/helper/builder/disk/edit_request.go b/vendor/github.com/sacloud/libsacloud/v2/helper/builder/disk/edit_request.go
new file mode 100644
index 00000000..cd1f967f
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/helper/builder/disk/edit_request.go
@@ -0,0 +1,193 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package disk
+
+import (
+	"context"
+	"fmt"
+	"time"
+
+	"github.com/sacloud/libsacloud/v2/sacloud"
+	"github.com/sacloud/libsacloud/v2/sacloud/types"
+)
+
+// EditRequest 汎用ディスクの修正リクエストパラメータ DiskDirectorが利用する
+type EditRequest UnixEditRequest
+
+// ToUnixDiskEditRequest Unix系パラメータへの変換
+func (d *EditRequest) ToUnixDiskEditRequest() *UnixEditRequest {
+	if d == nil {
+		return nil
+	}
+	req := UnixEditRequest(*d)
+	return &req
+}
+
+// ToWindowsDiskEditRequest Windows系パラメータへの変換
+func (d *EditRequest) ToWindowsDiskEditRequest() *WindowsEditRequest {
+	if d == nil {
+		return nil
+	}
+	return &WindowsEditRequest{
+		IPAddress:      d.IPAddress,
+		NetworkMaskLen: d.NetworkMaskLen,
+		DefaultRoute:   d.DefaultRoute,
+	}
+}
+
+// UnixEditRequest Unix系の場合のディスクの修正リクエスト
+type UnixEditRequest struct {
+	HostName string
+	Password string
+
+	DisablePWAuth       bool
+	EnableDHCP          bool
+	ChangePartitionUUID bool
+
+	IPAddress      string
+	NetworkMaskLen int
+	DefaultRoute   string
+
+	SSHKeys   []string
+	SSHKeyIDs []types.ID
+
+	// IsSSHKeysEphemeral trueの場合、SSHキーを生成する場合に生成したSSHキーリソースをサーバ作成後に削除する
+	IsSSHKeysEphemeral bool
+	// GenerateSSHKeyName 設定されていた場合、クラウドAPIを用いてキーペアを生成する。
+	GenerateSSHKeyName        string
+	GenerateSSHKeyPassPhrase  string
+	GenerateSSHKeyDescription string
+
+	IsNotesEphemeral bool
+	NoteContents     []string
+	Notes            []*sacloud.DiskEditNote
+}
+
+// Validate 設定値の検証
+func (u *UnixEditRequest) Validate(ctx context.Context, client *APIClient) error {
+	for _, id := range u.SSHKeyIDs {
+		if _, err := client.SSHKey.Read(ctx, id); err != nil {
+			return err
+		}
+	}
+	for _, note := range u.Notes {
+		if _, err := client.Note.Read(ctx, note.ID); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (u *UnixEditRequest) prepareDiskEditParameter(ctx context.Context, client *APIClient) (*sacloud.DiskEditRequest, *sacloud.SSHKeyGenerated, []*sacloud.Note, error) {
+	editReq := &sacloud.DiskEditRequest{
+		Background:          true,
+		Password:            u.Password,
+		DisablePWAuth:       u.DisablePWAuth,
+		EnableDHCP:          u.EnableDHCP,
+		ChangePartitionUUID: u.ChangePartitionUUID,
+		HostName:            u.HostName,
+	}
+
+	if u.IPAddress != "" {
+		editReq.UserIPAddress = u.IPAddress
+	}
+	if u.NetworkMaskLen > 0 || u.DefaultRoute != "" {
+		editReq.UserSubnet = &sacloud.DiskEditUserSubnet{
+			NetworkMaskLen: u.NetworkMaskLen,
+			DefaultRoute:   u.DefaultRoute,
+		}
+	}
+
+	// ssh key
+	var sshKeys []*sacloud.DiskEditSSHKey
+	for _, key := range u.SSHKeys {
+		sshKeys = append(sshKeys, &sacloud.DiskEditSSHKey{
+			PublicKey: key,
+		})
+	}
+	for _, id := range u.SSHKeyIDs {
+		sshKeys = append(sshKeys, &sacloud.DiskEditSSHKey{
+			ID: id,
+		})
+	}
+
+	var generatedSSHKey *sacloud.SSHKeyGenerated
+	if u.GenerateSSHKeyName != "" {
+		generated, err := client.SSHKey.Generate(ctx, &sacloud.SSHKeyGenerateRequest{
+			Name:        u.GenerateSSHKeyName,
+			PassPhrase:  u.GenerateSSHKeyPassPhrase,
+			Description: u.GenerateSSHKeyDescription,
+		})
+		if err != nil {
+			return nil, nil, nil, err
+		}
+		generatedSSHKey = generated
+		sshKeys = append(sshKeys, &sacloud.DiskEditSSHKey{
+			ID: generated.ID,
+		})
+	}
+	editReq.SSHKeys = sshKeys
+
+	// startup script
+	var notes []*sacloud.DiskEditNote
+	var generatedNotes []*sacloud.Note
+
+	for _, note := range u.NoteContents {
+		created, err := client.Note.Create(ctx, &sacloud.NoteCreateRequest{
+			Name:    fmt.Sprintf("note-%s", time.Now().Format(time.RFC3339)),
+			Class:   "shell",
+			Content: note,
+		})
+		if err != nil {
+			return nil, nil, nil, err
+		}
+		notes = append(notes, &sacloud.DiskEditNote{
+			ID: created.ID,
+		})
+		generatedNotes = append(generatedNotes, created)
+	}
+	for _, note := range u.Notes {
+		notes = append(notes, &sacloud.DiskEditNote{
+			ID:        note.ID,
+			APIKeyID:  note.APIKeyID,
+			Variables: note.Variables,
+		})
+	}
+	editReq.Notes = notes
+
+	return editReq, generatedSSHKey, generatedNotes, nil
+}
+
+// WindowsEditRequest Windows系の場合のディスクの修正リクエスト
+type WindowsEditRequest struct {
+	IPAddress      string
+	NetworkMaskLen int
+	DefaultRoute   string
+}
+
+func (w *WindowsEditRequest) prepareDiskEditParameter() *sacloud.DiskEditRequest {
+	editReq := &sacloud.DiskEditRequest{Background: true}
+
+	if w.IPAddress != "" {
+		editReq.UserIPAddress = w.IPAddress
+	}
+	if w.NetworkMaskLen > 0 || w.DefaultRoute != "" {
+		editReq.UserSubnet = &sacloud.DiskEditUserSubnet{
+			NetworkMaskLen: w.NetworkMaskLen,
+			DefaultRoute:   w.DefaultRoute,
+		}
+	}
+	return editReq
+}
diff --git a/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/apply_request.go b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/apply_request.go
new file mode 100644
index 00000000..85493c77
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/apply_request.go
@@ -0,0 +1,103 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package disk
+
+import (
+	diskBuilder "github.com/sacloud/libsacloud/v2/helper/builder/disk"
+	"github.com/sacloud/libsacloud/v2/helper/service"
+	"github.com/sacloud/libsacloud/v2/helper/validate"
+	"github.com/sacloud/libsacloud/v2/sacloud"
+	"github.com/sacloud/libsacloud/v2/sacloud/ostype"
+	"github.com/sacloud/libsacloud/v2/sacloud/types"
+)
+
+type ApplyRequest struct {
+	Zone string   `request:"-" validate:"required"`
+	ID   types.ID `request:"-"` // TODO Builderを更新対応させる(変更できない値の場合はエラーにするとか)
+
+	Name            string `validate:"required"`
+	Description     string `validate:"min=0,max=512"`
+	Tags            types.Tags
+	IconID          types.ID
+	DiskPlanID      types.ID
+	Connection      types.EDiskConnection
+	SourceDiskID    types.ID
+	SourceArchiveID types.ID
+	ServerID        types.ID
+	SizeGB          int
+	DistantFrom     []types.ID
+
+	OSType        ostype.ArchiveOSType
+	EditParameter *EditParameter
+}
+
+// EditParameter ディスクの修正用パラメータ
+type EditParameter struct {
+	HostName string
+	Password string
+
+	DisablePWAuth       bool
+	EnableDHCP          bool
+	ChangePartitionUUID bool
+
+	IPAddress      string
+	NetworkMaskLen int
+	DefaultRoute   string
+
+	SSHKeys   []string
+	SSHKeyIDs []types.ID
+
+	// IsSSHKeysEphemeral trueの場合、SSHキーを生成する場合に生成したSSHキーリソースをサーバ作成後に削除する
+	IsSSHKeysEphemeral bool
+	// GenerateSSHKeyName 設定されていた場合、クラウドAPIを用いてキーペアを生成する。
+	GenerateSSHKeyName        string
+	GenerateSSHKeyPassPhrase  string
+	GenerateSSHKeyDescription string
+
+	IsNotesEphemeral bool
+	NoteContents     []string
+	Notes            []*sacloud.DiskEditNote
+}
+
+func (req *ApplyRequest) Validate() error {
+	return validate.Struct(req)
+}
+
+func (req *ApplyRequest) Builder(caller sacloud.APICaller) (diskBuilder.Builder, error) {
+	var editParameter *diskBuilder.EditRequest
+	if req.EditParameter != nil {
+		if err := service.RequestConvertTo(req.EditParameter, editParameter); err != nil {
+			return nil, err
+		}
+	}
+
+	director := &diskBuilder.Director{
+		OSType:          req.OSType,
+		Name:            req.Name,
+		SizeGB:          req.SizeGB,
+		DistantFrom:     req.DistantFrom,
+		PlanID:          req.DiskPlanID,
+		Connection:      req.Connection,
+		Description:     req.Description,
+		Tags:            req.Tags,
+		IconID:          req.IconID,
+		DiskID:          req.ID,
+		SourceDiskID:    req.SourceDiskID,
+		SourceArchiveID: req.SourceArchiveID,
+		EditParameter:   editParameter,
+		Client:          diskBuilder.NewBuildersAPIClient(caller),
+	}
+	return director.Builder(), nil
+}
diff --git a/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/apply_service.go b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/apply_service.go
new file mode 100644
index 00000000..35b10b3c
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/apply_service.go
@@ -0,0 +1,57 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package disk
+
+import (
+	"context"
+
+	"github.com/sacloud/libsacloud/v2/sacloud"
+)
+
+func (s *Service) Apply(req *ApplyRequest) (*sacloud.Disk, *sacloud.SSHKeyGenerated, error) {
+	return s.ApplyWithContext(context.Background(), req)
+}
+
+func (s *Service) ApplyWithContext(ctx context.Context, req *ApplyRequest) (*sacloud.Disk, *sacloud.SSHKeyGenerated, error) {
+	if err := req.Validate(); err != nil {
+		return nil, nil, err
+	}
+
+	builder, err := req.Builder(s.caller)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	// create
+	if req.ID.IsEmpty() {
+		res, err := builder.Build(ctx, req.Zone, req.ServerID)
+		if err != nil {
+			return nil, nil, err
+		}
+		diskOp := sacloud.NewDiskOp(s.caller)
+		disk, err := diskOp.Read(ctx, req.Zone, res.DiskID)
+		if err != nil {
+			return nil, nil, err
+		}
+		return disk, res.GeneratedSSHKey, nil
+	}
+
+	// update
+	res, err := builder.Update(ctx, req.Zone)
+	if err != nil {
+		return nil, nil, err
+	}
+	return res.Disk, nil, nil
+}
diff --git a/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/connect_to_server_request.go b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/connect_to_server_request.go
new file mode 100644
index 00000000..9c78d7a1
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/connect_to_server_request.go
@@ -0,0 +1,31 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package disk
+
+import (
+	"github.com/sacloud/libsacloud/v2/helper/validate"
+	"github.com/sacloud/libsacloud/v2/sacloud/types"
+)
+
+type ConnectToServerRequest struct {
+	Zone string   `request:"-" validate:"required"`
+	ID   types.ID `request:"-" validate:"required"`
+
+	ServerID types.ID `validate:"required"`
+}
+
+func (req *ConnectToServerRequest) Validate() error {
+	return validate.Struct(req)
+}
diff --git a/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/connect_to_server_service.go b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/connect_to_server_service.go
new file mode 100644
index 00000000..64317094
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/connect_to_server_service.go
@@ -0,0 +1,34 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package disk
+
+import (
+	"context"
+
+	"github.com/sacloud/libsacloud/v2/sacloud"
+)
+
+func (s *Service) ConnectToServer(req *ConnectToServerRequest) error {
+	return s.ConnectToServerWithContext(context.Background(), req)
+}
+
+func (s *Service) ConnectToServerWithContext(ctx context.Context, req *ConnectToServerRequest) error {
+	if err := req.Validate(); err != nil {
+		return err
+	}
+
+	client := sacloud.NewDiskOp(s.caller)
+	return client.ConnectToServer(ctx, req.Zone, req.ID, req.ServerID)
+}
diff --git a/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/create_request.go b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/create_request.go
new file mode 100644
index 00000000..96d96b6c
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/create_request.go
@@ -0,0 +1,70 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package disk
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/sacloud/libsacloud/v2/helper/query"
+	"github.com/sacloud/libsacloud/v2/helper/service"
+	"github.com/sacloud/libsacloud/v2/helper/validate"
+	"github.com/sacloud/libsacloud/v2/sacloud"
+	"github.com/sacloud/libsacloud/v2/sacloud/ostype"
+	"github.com/sacloud/libsacloud/v2/sacloud/types"
+)
+
+// CreateRequest ディスク作成リクエスト
+type CreateRequest struct {
+	Zone string `request:"-" validate:"required"`
+
+	Name            string `validate:"required"`
+	Description     string `validate:"min=0,max=512"`
+	Tags            types.Tags
+	IconID          types.ID
+	DiskPlanID      types.ID              `validate:"oneof=4 2"`
+	Connection      types.EDiskConnection `validate:"oneof=virtio ide"`
+	SourceDiskID    types.ID
+	SourceArchiveID types.ID
+	ServerID        types.ID
+	SizeGB          int
+	DistantFrom     []types.ID
+	OSType          ostype.ArchiveOSType
+}
+
+func (req *CreateRequest) Validate() error {
+	if req.OSType != ostype.Custom {
+		if !req.SourceDiskID.IsEmpty() || !req.SourceArchiveID.IsEmpty() {
+			return fmt.Errorf("SourceDiskID or SourceArchiveID must be empty if OSType has a value")
+		}
+	}
+	return validate.Struct(req)
+}
+
+func (req *CreateRequest) ToRequestParameter(ctx context.Context, caller sacloud.APICaller) (*sacloud.DiskCreateRequest, error) {
+	if req.OSType != ostype.Custom {
+		archive, err := query.FindArchiveByOSType(ctx, sacloud.NewArchiveOp(caller), req.Zone, req.OSType)
+		if err != nil {
+			return nil, err
+		}
+		req.SourceArchiveID = archive.ID
+	}
+
+	params := &sacloud.DiskCreateRequest{}
+	if err := service.RequestConvertTo(req, params); err != nil {
+		return nil, err
+	}
+	return params, nil
+}
diff --git a/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/create_service.go b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/create_service.go
new file mode 100644
index 00000000..6a40202b
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/create_service.go
@@ -0,0 +1,39 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package disk
+
+import (
+	"context"
+
+	"github.com/sacloud/libsacloud/v2/sacloud"
+)
+
+func (s *Service) Create(req *CreateRequest) (*sacloud.Disk, error) {
+	return s.CreateWithContext(context.Background(), req)
+}
+
+func (s *Service) CreateWithContext(ctx context.Context, req *CreateRequest) (*sacloud.Disk, error) {
+	if err := req.Validate(); err != nil {
+		return nil, err
+	}
+
+	params, err := req.ToRequestParameter(ctx, s.caller)
+	if err != nil {
+		return nil, err
+	}
+
+	client := sacloud.NewDiskOp(s.caller)
+	return client.Create(ctx, req.Zone, params, req.DistantFrom)
+}
diff --git a/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/delete_request.go b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/delete_request.go
new file mode 100644
index 00000000..cd3dcf1e
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/delete_request.go
@@ -0,0 +1,31 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package disk
+
+import (
+	"github.com/sacloud/libsacloud/v2/helper/validate"
+	"github.com/sacloud/libsacloud/v2/sacloud/types"
+)
+
+type DeleteRequest struct {
+	Zone string   `request:"-" validate:"required"`
+	ID   types.ID `request:"-" validate:"required"`
+
+	FailIfNotFound bool `request:"-"`
+}
+
+func (req *DeleteRequest) Validate() error {
+	return validate.Struct(req)
+}
diff --git a/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/delete_service.go b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/delete_service.go
new file mode 100644
index 00000000..3978d2d0
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/delete_service.go
@@ -0,0 +1,38 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package disk
+
+import (
+	"context"
+
+	"github.com/sacloud/libsacloud/v2/helper/service"
+	"github.com/sacloud/libsacloud/v2/sacloud"
+)
+
+func (s *Service) Delete(req *DeleteRequest) error {
+	return s.DeleteWithContext(context.Background(), req)
+}
+
+func (s *Service) DeleteWithContext(ctx context.Context, req *DeleteRequest) error {
+	if err := req.Validate(); err != nil {
+		return err
+	}
+
+	client := sacloud.NewDiskOp(s.caller)
+	if err := client.Delete(ctx, req.Zone, req.ID); err != nil {
+		return service.HandleNotFoundError(err, !req.FailIfNotFound)
+	}
+	return nil
+}
diff --git a/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/disconnect_from_server_request.go b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/disconnect_from_server_request.go
new file mode 100644
index 00000000..cb63b389
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/disconnect_from_server_request.go
@@ -0,0 +1,29 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package disk
+
+import (
+	"github.com/sacloud/libsacloud/v2/helper/validate"
+	"github.com/sacloud/libsacloud/v2/sacloud/types"
+)
+
+type DisconnectFromServerRequest struct {
+	Zone string   `request:"-" validate:"required"`
+	ID   types.ID `request:"-" validate:"required"`
+}
+
+func (req *DisconnectFromServerRequest) Validate() error {
+	return validate.Struct(req)
+}
diff --git a/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/disconnect_from_server_service.go b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/disconnect_from_server_service.go
new file mode 100644
index 00000000..39e1a7d7
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/disconnect_from_server_service.go
@@ -0,0 +1,34 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package disk
+
+import (
+	"context"
+
+	"github.com/sacloud/libsacloud/v2/sacloud"
+)
+
+func (s *Service) DisconnectFromServer(req *DisconnectFromServerRequest) error {
+	return s.DisconnectFromServerWithContext(context.Background(), req)
+}
+
+func (s *Service) DisconnectFromServerWithContext(ctx context.Context, req *DisconnectFromServerRequest) error {
+	if err := req.Validate(); err != nil {
+		return err
+	}
+
+	client := sacloud.NewDiskOp(s.caller)
+	return client.DisconnectFromServer(ctx, req.Zone, req.ID)
+}
diff --git a/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/find_request.go b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/find_request.go
new file mode 100644
index 00000000..31538608
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/find_request.go
@@ -0,0 +1,55 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package disk
+
+import (
+	"github.com/sacloud/libsacloud/v2/helper/service"
+	"github.com/sacloud/libsacloud/v2/helper/validate"
+	"github.com/sacloud/libsacloud/v2/pkg/util"
+	"github.com/sacloud/libsacloud/v2/sacloud"
+	"github.com/sacloud/libsacloud/v2/sacloud/search"
+)
+
+type FindRequest struct {
+	Zone string `request:"-" validate:"required"`
+
+	Names []string `request:"-"`
+	Tags  []string `request:"-"`
+
+	Sort  search.SortKeys
+	Count int
+	From  int
+}
+
+func (req *FindRequest) Validate() error {
+	return validate.Struct(req)
+}
+
+func (req *FindRequest) ToRequestParameter() (*sacloud.FindCondition, error) {
+	condition := &sacloud.FindCondition{
+		Filter: map[search.FilterKey]interface{}{},
+	}
+	if err := service.RequestConvertTo(req, condition); err != nil {
+		return nil, err
+	}
+
+	if !util.IsEmpty(req.Names) {
+		condition.Filter[search.Key("Name")] = search.AndEqual(req.Names...)
+	}
+	if !util.IsEmpty(req.Tags) {
+		condition.Filter[search.Key("Tags.Name")] = search.TagsAndEqual(req.Tags...)
+	}
+	return condition, nil
+}
diff --git a/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/find_service.go b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/find_service.go
new file mode 100644
index 00000000..a0a4de9c
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/find_service.go
@@ -0,0 +1,43 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package disk
+
+import (
+	"context"
+
+	"github.com/sacloud/libsacloud/v2/sacloud"
+)
+
+func (s *Service) Find(req *FindRequest) ([]*sacloud.Disk, error) {
+	return s.FindWithContext(context.Background(), req)
+}
+
+func (s *Service) FindWithContext(ctx context.Context, req *FindRequest) ([]*sacloud.Disk, error) {
+	if err := req.Validate(); err != nil {
+		return nil, err
+	}
+
+	params, err := req.ToRequestParameter()
+	if err != nil {
+		return nil, err
+	}
+
+	client := sacloud.NewDiskOp(s.caller)
+	found, err := client.Find(ctx, req.Zone, params)
+	if err != nil {
+		return nil, err
+	}
+	return found.Disks, nil
+}
diff --git a/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/monitor_disk_request.go b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/monitor_disk_request.go
new file mode 100644
index 00000000..768cc246
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/monitor_disk_request.go
@@ -0,0 +1,34 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package disk
+
+import (
+	"time"
+
+	"github.com/sacloud/libsacloud/v2/helper/validate"
+	"github.com/sacloud/libsacloud/v2/sacloud/types"
+)
+
+type MonitorDiskRequest struct {
+	Zone string   `request:"-" validate:"required"`
+	ID   types.ID `request:"-" validate:"required"`
+
+	Start time.Time
+	End   time.Time
+}
+
+func (req *MonitorDiskRequest) Validate() error {
+	return validate.Struct(req)
+}
diff --git a/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/monitor_disk_service.go b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/monitor_disk_service.go
new file mode 100644
index 00000000..0515de96
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/monitor_disk_service.go
@@ -0,0 +1,44 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package disk
+
+import (
+	"context"
+
+	"github.com/sacloud/libsacloud/v2/helper/service"
+	"github.com/sacloud/libsacloud/v2/sacloud"
+)
+
+func (s *Service) MonitorDisk(req *MonitorDiskRequest) ([]*sacloud.MonitorDiskValue, error) {
+	return s.MonitorDiskWithContext(context.Background(), req)
+}
+
+func (s *Service) MonitorDiskWithContext(ctx context.Context, req *MonitorDiskRequest) ([]*sacloud.MonitorDiskValue, error) {
+	if err := req.Validate(); err != nil {
+		return nil, err
+	}
+
+	client := sacloud.NewDiskOp(s.caller)
+	cond, err := service.MonitorCondition(req.Start, req.End)
+	if err != nil {
+		return nil, err
+	}
+
+	values, err := client.MonitorDisk(ctx, req.Zone, req.ID, cond)
+	if err != nil {
+		return nil, err
+	}
+	return values.Values, nil
+}
diff --git a/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/read_request.go b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/read_request.go
new file mode 100644
index 00000000..50209602
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/read_request.go
@@ -0,0 +1,29 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package disk
+
+import (
+	"github.com/sacloud/libsacloud/v2/helper/validate"
+	"github.com/sacloud/libsacloud/v2/sacloud/types"
+)
+
+type ReadRequest struct {
+	Zone string   `request:"-" validate:"required"`
+	ID   types.ID `request:"-" validate:"required"`
+}
+
+func (req *ReadRequest) Validate() error {
+	return validate.Struct(req)
+}
diff --git a/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/read_service.go b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/read_service.go
new file mode 100644
index 00000000..5444f6d7
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/read_service.go
@@ -0,0 +1,33 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package disk
+
+import (
+	"context"
+
+	"github.com/sacloud/libsacloud/v2/sacloud"
+)
+
+func (s *Service) Read(req *ReadRequest) (*sacloud.Disk, error) {
+	return s.ReadWithContext(context.Background(), req)
+}
+
+func (s *Service) ReadWithContext(ctx context.Context, req *ReadRequest) (*sacloud.Disk, error) {
+	if err := req.Validate(); err != nil {
+		return nil, err
+	}
+	client := sacloud.NewDiskOp(s.caller)
+	return client.Read(ctx, req.Zone, req.ID)
+}
diff --git a/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/reinstall_from_archive_request.go b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/reinstall_from_archive_request.go
new file mode 100644
index 00000000..27a77e92
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/reinstall_from_archive_request.go
@@ -0,0 +1,36 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package disk
+
+import (
+	"github.com/sacloud/libsacloud/v2/helper/validate"
+	"github.com/sacloud/libsacloud/v2/sacloud"
+	"github.com/sacloud/libsacloud/v2/sacloud/ostype"
+	"github.com/sacloud/libsacloud/v2/sacloud/types"
+)
+
+type ReinstallFromArchiveRequest struct {
+	Zone string   `request:"-" validate:"required"`
+	ID   types.ID `request:"-" validate:"required"`
+
+	OSType          ostype.ArchiveOSType
+	SourceArchiveID types.ID
+	EditParameter   *sacloud.DiskEditRequest
+	DistantFrom     []types.ID
+}
+
+func (req *ReinstallFromArchiveRequest) Validate() error {
+	return validate.Struct(req)
+}
diff --git a/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/reinstall_from_archive_request_ext.go b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/reinstall_from_archive_request_ext.go
new file mode 100644
index 00000000..49b637f8
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/reinstall_from_archive_request_ext.go
@@ -0,0 +1,37 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package disk
+
+import (
+	"context"
+
+	"github.com/sacloud/libsacloud/v2/helper/query"
+	"github.com/sacloud/libsacloud/v2/sacloud"
+)
+
+func (req *ReinstallFromArchiveRequest) toRequestParameter(ctx context.Context, caller sacloud.APICaller) (*sacloud.DiskInstallRequest, error) {
+	param := &sacloud.DiskInstallRequest{
+		SourceArchiveID: req.SourceArchiveID,
+	}
+	if req.SourceArchiveID.IsEmpty() {
+		// find archive by ostype
+		archive, err := query.FindArchiveByOSType(ctx, sacloud.NewArchiveOp(caller), req.Zone, req.OSType)
+		if err != nil {
+			return nil, err
+		}
+		param.SourceArchiveID = archive.ID
+	}
+	return param, nil
+}
diff --git a/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/reinstall_from_archive_service.go b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/reinstall_from_archive_service.go
new file mode 100644
index 00000000..70ea6c2d
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/reinstall_from_archive_service.go
@@ -0,0 +1,62 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package disk
+
+import (
+	"context"
+
+	"github.com/sacloud/libsacloud/v2/helper/wait"
+
+	"github.com/sacloud/libsacloud/v2/sacloud"
+)
+
+func (s *Service) ReinstallFromArchive(req *ReinstallFromArchiveRequest) (*sacloud.Disk, error) {
+	return s.ReinstallFromArchiveWithContext(context.Background(), req)
+}
+
+func (s *Service) ReinstallFromArchiveWithContext(ctx context.Context, req *ReinstallFromArchiveRequest) (*sacloud.Disk, error) {
+	if err := req.Validate(); err != nil {
+		return nil, err
+	}
+
+	params, err := req.toRequestParameter(ctx, s.caller)
+	if err != nil {
+		return nil, err
+	}
+
+	client := sacloud.NewDiskOp(s.caller)
+	disk, err := client.Install(ctx, req.Zone, req.ID, params, req.DistantFrom)
+	if err != nil {
+		return nil, err
+	}
+	// wait for ready
+	disk, err = wait.UntilDiskIsReady(ctx, client, req.Zone, disk.ID)
+	if err != nil {
+		return disk, err
+	}
+
+	// edit disk
+	if req.EditParameter != nil {
+		if err := client.Config(ctx, req.Zone, disk.ID, req.EditParameter); err != nil {
+			return nil, err
+		}
+		disk, err = wait.UntilDiskIsReady(ctx, client, req.Zone, disk.ID)
+		if err != nil {
+			return disk, err
+		}
+	}
+
+	return disk, nil
+}
diff --git a/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/reinstall_from_blank_request.go b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/reinstall_from_blank_request.go
new file mode 100644
index 00000000..f46fa283
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/reinstall_from_blank_request.go
@@ -0,0 +1,31 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package disk
+
+import (
+	"github.com/sacloud/libsacloud/v2/helper/validate"
+	"github.com/sacloud/libsacloud/v2/sacloud/types"
+)
+
+type ReinstallFromBlankRequest struct {
+	Zone string   `request:"-" validate:"required"`
+	ID   types.ID `request:"-" validate:"required"`
+
+	DistantFrom []types.ID
+}
+
+func (req *ReinstallFromBlankRequest) Validate() error {
+	return validate.Struct(req)
+}
diff --git a/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/reinstall_from_blank_service.go b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/reinstall_from_blank_service.go
new file mode 100644
index 00000000..7b859f38
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/reinstall_from_blank_service.go
@@ -0,0 +1,51 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package disk
+
+import (
+	"context"
+
+	"github.com/sacloud/libsacloud/v2/helper/wait"
+
+	"github.com/sacloud/libsacloud/v2/sacloud"
+)
+
+func (s *Service) ReinstallFromBlank(req *ReinstallFromBlankRequest) (*sacloud.Disk, error) {
+	return s.ReinstallFromBlankWithContext(context.Background(), req)
+}
+
+func (s *Service) ReinstallFromBlankWithContext(ctx context.Context, req *ReinstallFromBlankRequest) (*sacloud.Disk, error) {
+	if err := req.Validate(); err != nil {
+		return nil, err
+	}
+
+	params, err := req.toRequestParameter(ctx, s.caller)
+	if err != nil {
+		return nil, err
+	}
+
+	client := sacloud.NewDiskOp(s.caller)
+	disk, err := client.Install(ctx, req.Zone, req.ID, params, req.DistantFrom)
+	if err != nil {
+		return nil, err
+	}
+	// wait for ready
+	disk, err = wait.UntilDiskIsReady(ctx, client, req.Zone, disk.ID)
+	if err != nil {
+		return disk, err
+	}
+
+	return disk, nil
+}
diff --git a/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/reinstall_from_brank_request_ext.go b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/reinstall_from_brank_request_ext.go
new file mode 100644
index 00000000..b4e1a893
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/reinstall_from_brank_request_ext.go
@@ -0,0 +1,32 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package disk
+
+import (
+	"context"
+
+	"github.com/sacloud/libsacloud/v2/sacloud"
+)
+
+func (req *ReinstallFromBlankRequest) toRequestParameter(ctx context.Context, caller sacloud.APICaller) (*sacloud.DiskInstallRequest, error) {
+	disk, err := sacloud.NewDiskOp(caller).Read(ctx, req.Zone, req.ID)
+	if err != nil {
+		return nil, err
+	}
+
+	return &sacloud.DiskInstallRequest{
+		SizeMB: disk.SizeMB,
+	}, nil
+}
diff --git a/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/reinstall_from_disk_request.go b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/reinstall_from_disk_request.go
new file mode 100644
index 00000000..ce5a9fda
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/reinstall_from_disk_request.go
@@ -0,0 +1,36 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package disk
+
+import (
+	"github.com/sacloud/libsacloud/v2/helper/validate"
+	"github.com/sacloud/libsacloud/v2/sacloud"
+	"github.com/sacloud/libsacloud/v2/sacloud/ostype"
+	"github.com/sacloud/libsacloud/v2/sacloud/types"
+)
+
+type ReinstallFromDiskRequest struct {
+	Zone string   `request:"-" validate:"required"`
+	ID   types.ID `request:"-" validate:"required"`
+
+	OSType        ostype.ArchiveOSType
+	SourceDiskID  types.ID
+	EditParameter *sacloud.DiskEditRequest
+	DistantFrom   []types.ID
+}
+
+func (req *ReinstallFromDiskRequest) Validate() error {
+	return validate.Struct(req)
+}
diff --git a/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/reinstall_from_disk_request_ext.go b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/reinstall_from_disk_request_ext.go
new file mode 100644
index 00000000..9c6b3eba
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/reinstall_from_disk_request_ext.go
@@ -0,0 +1,25 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package disk
+
+import (
+	"github.com/sacloud/libsacloud/v2/sacloud"
+)
+
+func (req *ReinstallFromDiskRequest) toRequestParameter() (*sacloud.DiskInstallRequest, error) {
+	return &sacloud.DiskInstallRequest{
+		SourceDiskID: req.SourceDiskID,
+	}, nil
+}
diff --git a/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/reinstall_from_disk_service.go b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/reinstall_from_disk_service.go
new file mode 100644
index 00000000..d4930338
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/reinstall_from_disk_service.go
@@ -0,0 +1,62 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package disk
+
+import (
+	"context"
+
+	"github.com/sacloud/libsacloud/v2/helper/wait"
+
+	"github.com/sacloud/libsacloud/v2/sacloud"
+)
+
+func (s *Service) ReinstallFromDisk(req *ReinstallFromDiskRequest) (*sacloud.Disk, error) {
+	return s.ReinstallFromDiskWithContext(context.Background(), req)
+}
+
+func (s *Service) ReinstallFromDiskWithContext(ctx context.Context, req *ReinstallFromDiskRequest) (*sacloud.Disk, error) {
+	if err := req.Validate(); err != nil {
+		return nil, err
+	}
+
+	params, err := req.toRequestParameter()
+	if err != nil {
+		return nil, err
+	}
+
+	client := sacloud.NewDiskOp(s.caller)
+	disk, err := client.Install(ctx, req.Zone, req.ID, params, req.DistantFrom)
+	if err != nil {
+		return nil, err
+	}
+	// wait for ready
+	disk, err = wait.UntilDiskIsReady(ctx, client, req.Zone, disk.ID)
+	if err != nil {
+		return disk, err
+	}
+
+	// edit disk
+	if req.EditParameter != nil {
+		if err := client.Config(ctx, req.Zone, disk.ID, req.EditParameter); err != nil {
+			return nil, err
+		}
+		disk, err = wait.UntilDiskIsReady(ctx, client, req.Zone, disk.ID)
+		if err != nil {
+			return disk, err
+		}
+	}
+
+	return disk, nil
+}
diff --git a/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/resize_partition_request.go b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/resize_partition_request.go
new file mode 100644
index 00000000..5100eded
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/resize_partition_request.go
@@ -0,0 +1,29 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package disk
+
+import (
+	"github.com/sacloud/libsacloud/v2/helper/validate"
+	"github.com/sacloud/libsacloud/v2/sacloud/types"
+)
+
+type ResizePartitionRequest struct {
+	Zone string   `request:"-" validate:"required"`
+	ID   types.ID `request:"-" validate:"required"`
+}
+
+func (req *ResizePartitionRequest) Validate() error {
+	return validate.Struct(req)
+}
diff --git a/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/resize_partition_service.go b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/resize_partition_service.go
new file mode 100644
index 00000000..d06ba627
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/resize_partition_service.go
@@ -0,0 +1,41 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package disk
+
+import (
+	"context"
+
+	"github.com/sacloud/libsacloud/v2/helper/wait"
+
+	"github.com/sacloud/libsacloud/v2/sacloud"
+)
+
+func (s *Service) ResizePartition(req *ResizePartitionRequest) error {
+	return s.ResizePartitionWithContext(context.Background(), req)
+}
+
+func (s *Service) ResizePartitionWithContext(ctx context.Context, req *ResizePartitionRequest) error {
+	if err := req.Validate(); err != nil {
+		return err
+	}
+
+	client := sacloud.NewDiskOp(s.caller)
+	if err := client.ResizePartition(ctx, req.Zone, req.ID, &sacloud.DiskResizePartitionRequest{Background: true}); err != nil {
+		return err
+	}
+
+	_, err := wait.UntilDiskIsReady(ctx, client, req.Zone, req.ID)
+	return err
+}
diff --git a/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/service.go b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/service.go
new file mode 100644
index 00000000..dbb0bea7
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/service.go
@@ -0,0 +1,27 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package disk
+
+import "github.com/sacloud/libsacloud/v2/sacloud"
+
+// Service provides a high-level API of for Disk
+type Service struct {
+	caller sacloud.APICaller
+}
+
+// New returns new service instance of Disk
+func New(caller sacloud.APICaller) *Service {
+	return &Service{caller: caller}
+}
diff --git a/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/update_request.go b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/update_request.go
new file mode 100644
index 00000000..be1babcb
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/update_request.go
@@ -0,0 +1,48 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package disk
+
+import (
+	"github.com/sacloud/libsacloud/v2/helper/service"
+	"github.com/sacloud/libsacloud/v2/helper/validate"
+	"github.com/sacloud/libsacloud/v2/sacloud"
+	"github.com/sacloud/libsacloud/v2/sacloud/types"
+)
+
+type UpdateRequest struct {
+	Zone string   `request:"-" validate:"required"`
+	ID   types.ID `request:"-" validate:"required"`
+
+	Name        *string                `request:",omitempty" validate:"omitempty,min=1"`
+	Description *string                `request:",omitempty" validate:"omitempty,min=1,max=512"`
+	Tags        *types.Tags            `request:",omitempty"`
+	IconID      *types.ID              `request:",omitempty"`
+	Connection  *types.EDiskConnection `request:",omitempty"`
+}
+
+func (req *UpdateRequest) Validate() error {
+	return validate.Struct(req)
+}
+
+func (req *UpdateRequest) ToRequestParameter(current *sacloud.Disk) (*sacloud.DiskUpdateRequest, error) {
+	r := &sacloud.DiskUpdateRequest{}
+	if err := service.RequestConvertTo(current, r); err != nil {
+		return nil, err
+	}
+	if err := service.RequestConvertTo(req, r); err != nil {
+		return nil, err
+	}
+	return r, nil
+}
diff --git a/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/update_service.go b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/update_service.go
new file mode 100644
index 00000000..fb362b38
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/update_service.go
@@ -0,0 +1,45 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package disk
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/sacloud/libsacloud/v2/sacloud"
+)
+
+func (s *Service) Update(req *UpdateRequest) (*sacloud.Disk, error) {
+	return s.UpdateWithContext(context.Background(), req)
+}
+
+func (s *Service) UpdateWithContext(ctx context.Context, req *UpdateRequest) (*sacloud.Disk, error) {
+	if err := req.Validate(); err != nil {
+		return nil, err
+	}
+
+	client := sacloud.NewDiskOp(s.caller)
+	current, err := client.Read(ctx, req.Zone, req.ID)
+	if err != nil {
+		return nil, fmt.Errorf("reading Disk[%s] failed: %s", req.ID, err)
+	}
+
+	params, err := req.ToRequestParameter(current)
+	if err != nil {
+		return nil, fmt.Errorf("processing request parameter failed: %s", err)
+	}
+
+	return client.Update(ctx, req.Zone, req.ID, params)
+}
diff --git a/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/wait_ready_request.go b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/wait_ready_request.go
new file mode 100644
index 00000000..44bf79b4
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/wait_ready_request.go
@@ -0,0 +1,29 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package disk
+
+import (
+	"github.com/sacloud/libsacloud/v2/helper/validate"
+	"github.com/sacloud/libsacloud/v2/sacloud/types"
+)
+
+type WaitReadyRequest struct {
+	Zone string   `request:"-" validate:"required"`
+	ID   types.ID `request:"-" validate:"required"`
+}
+
+func (req *WaitReadyRequest) Validate() error {
+	return validate.Struct(req)
+}
diff --git a/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/wait_ready_service.go b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/wait_ready_service.go
new file mode 100644
index 00000000..a1671caf
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/helper/service/disk/wait_ready_service.go
@@ -0,0 +1,36 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package disk
+
+import (
+	"context"
+
+	"github.com/sacloud/libsacloud/v2/helper/wait"
+	"github.com/sacloud/libsacloud/v2/sacloud"
+)
+
+func (s *Service) WaitReady(req *WaitReadyRequest) error {
+	return s.WaitReadyWithContext(context.Background(), req)
+}
+
+func (s *Service) WaitReadyWithContext(ctx context.Context, req *WaitReadyRequest) error {
+	if err := req.Validate(); err != nil {
+		return err
+	}
+
+	client := sacloud.NewDiskOp(s.caller)
+	_, err := wait.UntilDiskIsReady(ctx, client, req.Zone, req.ID)
+	return err
+}
diff --git a/vendor/github.com/sacloud/libsacloud/v2/helper/service/doc.go b/vendor/github.com/sacloud/libsacloud/v2/helper/service/doc.go
new file mode 100644
index 00000000..2e53ce04
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/helper/service/doc.go
@@ -0,0 +1,17 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package service さくらのクラウド API 高レベルライブラリ
+// Experimental: This API is experimental and may change in the future.
+package service
diff --git a/vendor/github.com/sacloud/libsacloud/v2/helper/service/util.go b/vendor/github.com/sacloud/libsacloud/v2/helper/service/util.go
new file mode 100644
index 00000000..da6c3ad3
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/helper/service/util.go
@@ -0,0 +1,52 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package service
+
+import (
+	"fmt"
+	"time"
+
+	"github.com/sacloud/libsacloud/v2/pkg/mapconv"
+
+	"github.com/sacloud/libsacloud/v2/sacloud"
+)
+
+func HandleNotFoundError(err error, ignoreNotFoundError bool) error {
+	if ignoreNotFoundError && sacloud.IsNotFoundError(err) {
+		return nil // ignore: 404 not found
+	}
+	return err
+}
+
+func MonitorCondition(start, end time.Time) (*sacloud.MonitorCondition, error) {
+	e := end
+	if e.IsZero() {
+		e = time.Now()
+	}
+
+	s := start
+	if s.IsZero() {
+		s = e.Add(-1 * time.Hour)
+	}
+	if !(s.Unix() <= e.Unix()) {
+		return nil, fmt.Errorf("start(%s) or end(%s) is invalid", start.String(), end.String())
+	}
+	return &sacloud.MonitorCondition{Start: s, End: e}, nil
+}
+
+func RequestConvertTo(source interface{}, dest interface{}) error {
+	decoder := &mapconv.Decoder{Config: &mapconv.DecoderConfig{TagName: "request"}}
+	return decoder.ConvertTo(source, dest)
+}
diff --git a/vendor/github.com/sacloud/libsacloud/v2/helper/wait/wait.go b/vendor/github.com/sacloud/libsacloud/v2/helper/wait/wait.go
new file mode 100644
index 00000000..bcae8e28
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/helper/wait/wait.go
@@ -0,0 +1,227 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package wait
+
+import (
+	"context"
+
+	"github.com/sacloud/libsacloud/v2/sacloud"
+	"github.com/sacloud/libsacloud/v2/sacloud/types"
+)
+
+var (
+	// ApplianceNotFoundRetryCount アプライアンスの待ち処理時に404エラーとなった場合のリトライ回数
+	ApplianceNotFoundRetryCount = 30
+	// InternetNotFoundRetryCount ルータの作成待ち処理時に404エラーとなった場合のリトライ回数
+	InternetNotFoundRetryCount = 360
+)
+
+// UntilArchiveIsReady コピー完了まで待機
+func UntilArchiveIsReady(ctx context.Context, client sacloud.ArchiveAPI, zone string, id types.ID) (*sacloud.Archive, error) {
+	lastState, err := sacloud.WaiterForReady(func() (interface{}, error) {
+		return client.Read(ctx, zone, id)
+	}).WaitForState(ctx)
+	if lastState != nil {
+		return lastState.(*sacloud.Archive), err
+	}
+	return nil, err
+}
+
+// UntilDatabaseIsUp 起動まで待機
+func UntilDatabaseIsUp(ctx context.Context, client sacloud.DatabaseAPI, zone string, id types.ID) (*sacloud.Database, error) {
+	var database *sacloud.Database
+	lastState, err := sacloud.WaiterForApplianceUp(func() (interface{}, error) {
+		return client.Read(ctx, zone, id)
+	}, ApplianceNotFoundRetryCount).WaitForState(ctx)
+	if lastState != nil {
+		database = lastState.(*sacloud.Database)
+	}
+	if err != nil {
+		return nil, err
+	}
+	// [HACK] データベースアプライアンス場合のみ/appliance/:id/statusも考慮する
+	waiter := sacloud.WaiterForUp(func() (interface{}, error) {
+		return client.Status(ctx, zone, id)
+	})
+	waiter.SetPollingInterval(sacloud.DefaultDBStatusPollingInterval) // HACK 現状は決め打ち、ユースケースが出たら修正する
+	_, err = waiter.WaitForState(ctx)
+
+	return database, err
+}
+
+// UntilDatabaseIsDown シャットダウンまで待機
+func UntilDatabaseIsDown(ctx context.Context, client sacloud.DatabaseAPI, zone string, id types.ID) (*sacloud.Database, error) {
+	lastState, err := sacloud.WaiterForDown(func() (interface{}, error) {
+		return client.Read(ctx, zone, id)
+	}).WaitForState(ctx)
+	if lastState != nil {
+		return lastState.(*sacloud.Database), err
+	}
+	return nil, err
+}
+
+// UntilDiskIsReady コピー完了/ディスク修正完了まで待機
+func UntilDiskIsReady(ctx context.Context, client sacloud.DiskAPI, zone string, id types.ID) (*sacloud.Disk, error) {
+	lastState, err := sacloud.WaiterForReady(func() (interface{}, error) {
+		return client.Read(ctx, zone, id)
+	}).WaitForState(ctx)
+	if lastState != nil {
+		return lastState.(*sacloud.Disk), err
+	}
+	return nil, err
+}
+
+// UntilInternetIsReady 準備完了まで待機
+func UntilInternetIsReady(ctx context.Context, client sacloud.InternetAPI, zone string, id types.ID) (*sacloud.Internet, error) {
+	lastState, err := sacloud.WaiterForApplianceUp(func() (interface{}, error) {
+		return client.Read(ctx, zone, id)
+	}, InternetNotFoundRetryCount).WaitForState(ctx)
+	if lastState != nil {
+		return lastState.(*sacloud.Internet), err
+	}
+	return nil, err
+}
+
+// UntilLoadBalancerIsUp 起動完了まで待機
+func UntilLoadBalancerIsUp(ctx context.Context, client sacloud.LoadBalancerAPI, zone string, id types.ID) (*sacloud.LoadBalancer, error) {
+	lastState, err := sacloud.WaiterForApplianceUp(func() (interface{}, error) {
+		return client.Read(ctx, zone, id)
+	}, ApplianceNotFoundRetryCount).WaitForState(ctx)
+	if lastState != nil {
+		return lastState.(*sacloud.LoadBalancer), err
+	}
+	return nil, err
+}
+
+// UntilLoadBalancerIsDown シャットダウンまで待機
+func UntilLoadBalancerIsDown(ctx context.Context, client sacloud.LoadBalancerAPI, zone string, id types.ID) (*sacloud.LoadBalancer, error) {
+	lastState, err := sacloud.WaiterForDown(func() (interface{}, error) {
+		return client.Read(ctx, zone, id)
+	}).WaitForState(ctx)
+	if lastState != nil {
+		return lastState.(*sacloud.LoadBalancer), err
+	}
+	return nil, err
+}
+
+// UntilMobileGatewayIsReady コピー完了まで待機
+func UntilMobileGatewayIsReady(ctx context.Context, client sacloud.MobileGatewayAPI, zone string, id types.ID) (*sacloud.MobileGateway, error) {
+	lastState, err := sacloud.WaiterForReady(func() (interface{}, error) {
+		return client.Read(ctx, zone, id)
+	}).WaitForState(ctx)
+	if lastState != nil {
+		return lastState.(*sacloud.MobileGateway), err
+	}
+	return nil, err
+}
+
+// UntilMobileGatewayIsUp 起動まで待機
+func UntilMobileGatewayIsUp(ctx context.Context, client sacloud.MobileGatewayAPI, zone string, id types.ID) (*sacloud.MobileGateway, error) {
+	lastState, err := sacloud.WaiterForApplianceUp(func() (interface{}, error) {
+		return client.Read(ctx, zone, id)
+	}, ApplianceNotFoundRetryCount).WaitForState(ctx)
+	if lastState != nil {
+		return lastState.(*sacloud.MobileGateway), err
+	}
+	return nil, err
+}
+
+// UntilMobileGatewayIsDown シャットダウンまで待機
+func UntilMobileGatewayIsDown(ctx context.Context, client sacloud.MobileGatewayAPI, zone string, id types.ID) (*sacloud.MobileGateway, error) {
+	lastState, err := sacloud.WaiterForDown(func() (interface{}, error) {
+		return client.Read(ctx, zone, id)
+	}).WaitForState(ctx)
+	if lastState != nil {
+		return lastState.(*sacloud.MobileGateway), err
+	}
+	return nil, err
+}
+
+// UntilNFSIsUp 起動まで待機
+func UntilNFSIsUp(ctx context.Context, client sacloud.NFSAPI, zone string, id types.ID) (*sacloud.NFS, error) {
+	lastState, err := sacloud.WaiterForApplianceUp(func() (interface{}, error) {
+		return client.Read(ctx, zone, id)
+	}, ApplianceNotFoundRetryCount).WaitForState(ctx)
+	if lastState != nil {
+		return lastState.(*sacloud.NFS), err
+	}
+	return nil, err
+}
+
+// UntilNFSIsDown シャットダウンまで待機
+func UntilNFSIsDown(ctx context.Context, client sacloud.NFSAPI, zone string, id types.ID) (*sacloud.NFS, error) {
+	lastState, err := sacloud.WaiterForDown(func() (interface{}, error) {
+		return client.Read(ctx, zone, id)
+	}).WaitForState(ctx)
+	if lastState != nil {
+		return lastState.(*sacloud.NFS), err
+	}
+	return nil, err
+}
+
+// UntilServerIsUp 起動まで待機
+func UntilServerIsUp(ctx context.Context, client sacloud.ServerAPI, zone string, id types.ID) (*sacloud.Server, error) {
+	lastState, err := sacloud.WaiterForUp(func() (interface{}, error) {
+		return client.Read(ctx, zone, id)
+	}).WaitForState(ctx)
+	if lastState != nil {
+		return lastState.(*sacloud.Server), err
+	}
+	return nil, err
+}
+
+// UntilServerIsDown シャットダウンまで待機
+func UntilServerIsDown(ctx context.Context, client sacloud.ServerAPI, zone string, id types.ID) (*sacloud.Server, error) {
+	lastState, err := sacloud.WaiterForDown(func() (interface{}, error) {
+		return client.Read(ctx, zone, id)
+	}).WaitForState(ctx)
+	if lastState != nil {
+		return lastState.(*sacloud.Server), err
+	}
+	return nil, err
+}
+
+// UntilVPCRouterIsReady コピー完了まで待機
+func UntilVPCRouterIsReady(ctx context.Context, client sacloud.VPCRouterAPI, zone string, id types.ID) (*sacloud.VPCRouter, error) {
+	lastState, err := sacloud.WaiterForReady(func() (interface{}, error) {
+		return client.Read(ctx, zone, id)
+	}).WaitForState(ctx)
+	if lastState != nil {
+		return lastState.(*sacloud.VPCRouter), err
+	}
+	return nil, err
+}
+
+// UntilVPCRouterIsUp 起動まで待機
+func UntilVPCRouterIsUp(ctx context.Context, client sacloud.VPCRouterAPI, zone string, id types.ID) (*sacloud.VPCRouter, error) {
+	lastState, err := sacloud.WaiterForApplianceUp(func() (interface{}, error) {
+		return client.Read(ctx, zone, id)
+	}, ApplianceNotFoundRetryCount).WaitForState(ctx)
+	if lastState != nil {
+		return lastState.(*sacloud.VPCRouter), err
+	}
+	return nil, err
+}
+
+// UntilVPCRouterIsDown シャットダウンまで待機
+func UntilVPCRouterIsDown(ctx context.Context, client sacloud.VPCRouterAPI, zone string, id types.ID) (*sacloud.VPCRouter, error) {
+	lastState, err := sacloud.WaiterForDown(func() (interface{}, error) {
+		return client.Read(ctx, zone, id)
+	}).WaitForState(ctx)
+	if lastState != nil {
+		return lastState.(*sacloud.VPCRouter), err
+	}
+	return nil, err
+}
diff --git a/vendor/github.com/sacloud/libsacloud/v2/pkg/util/empty.go b/vendor/github.com/sacloud/libsacloud/v2/pkg/util/empty.go
new file mode 100644
index 00000000..ddb95af0
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/pkg/util/empty.go
@@ -0,0 +1,46 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package util
+
+import (
+	"reflect"
+)
+
+// IsEmpty is copied from github.com/stretchr/testify/assert/assetions.go
+func IsEmpty(object interface{}) bool {
+	// get nil case out of the way
+	if object == nil {
+		return true
+	}
+
+	objValue := reflect.ValueOf(object)
+
+	switch objValue.Kind() {
+	// collection types are empty when they have no element
+	case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
+		return objValue.Len() == 0
+	// pointers are empty if nil or if the value they point to is empty
+	case reflect.Ptr:
+		if objValue.IsNil() {
+			return true
+		}
+		deref := objValue.Elem().Interface()
+		return IsEmpty(deref)
+	// for all other types, compare against the zero value
+	default:
+		zero := reflect.Zero(objValue.Type())
+		return reflect.DeepEqual(object, zero.Interface())
+	}
+}
diff --git a/vendor/github.com/sacloud/libsacloud/v2/pkg/util/equal.go b/vendor/github.com/sacloud/libsacloud/v2/pkg/util/equal.go
new file mode 100644
index 00000000..3a908f8b
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/pkg/util/equal.go
@@ -0,0 +1,21 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package util
+
+import "reflect"
+
+func DeepEqual(x, y interface{}) bool {
+	return reflect.DeepEqual(x, y)
+}
diff --git a/vendor/github.com/sacloud/libsacloud/v2/sacloud/pointer/primitive.go b/vendor/github.com/sacloud/libsacloud/v2/sacloud/pointer/primitive.go
new file mode 100644
index 00000000..6d622ea5
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/sacloud/pointer/primitive.go
@@ -0,0 +1,63 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package pointer
+
+// NewBool returns a pointer to the given bool value.
+func NewBool(b bool) *bool { return &b }
+
+// NewString returns a pointer to the given string value.
+func NewString(s string) *string { return &s }
+
+// NewInt returns a pointer to the given int value.
+func NewInt(i int) *int { return &i }
+
+// NewInt8 returns a pointer to the given int8 value.
+func NewInt8(i int8) *int8 { return &i }
+
+// NewInt16 returns a pointer to the given int16 value.
+func NewInt16(i int16) *int16 { return &i }
+
+// NewInt32 returns a pointer to the given int32 value.
+func NewInt32(i int32) *int32 { return &i }
+
+// NewInt64 returns a pointer to the given int64 value.
+func NewInt64(i int64) *int64 { return &i }
+
+// NewUint returns a pointer to the given uint value.
+func NewUint(i uint) *uint { return &i }
+
+// NewUint8 returns a pointer to the given uint8 value.
+func NewUint8(i uint8) *uint8 { return &i }
+
+// NewUint16 returns a pointer to the given uint16 value.
+func NewUint16(i uint16) *uint16 { return &i }
+
+// NewUint32 returns a pointer to the given uint32 value.
+func NewUint32(i uint32) *uint32 { return &i }
+
+// NewUint64 returns a pointer to the given uint64 value.
+func NewUint64(i uint64) *uint64 { return &i }
+
+// NewFloat32 returns a pointer to the given float32 value.
+func NewFloat32(f float32) *float32 { return &f }
+
+// NewFloat64 returns a pointer to the given float64 value.
+func NewFloat64(f float64) *float64 { return &f }
+
+// NewByte returns a pointer to the given byte value.
+func NewByte(b byte) *byte { return &b }
+
+// NewRune returns a pointer to the given rune value.
+func NewRune(r rune) *rune { return &r }
diff --git a/vendor/github.com/sacloud/libsacloud/v2/sacloud/pointer/slice.go b/vendor/github.com/sacloud/libsacloud/v2/sacloud/pointer/slice.go
new file mode 100644
index 00000000..e1c21c1f
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/sacloud/pointer/slice.go
@@ -0,0 +1,33 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package pointer
+
+// NewStringSlice returns a pointer to the given tags value
+func NewStringSlice(v []string) *[]string { return &v }
+
+// NewIntSlice returns a pointer to the given tags value
+func NewIntSlice(v []int) *[]int { return &v }
+
+// NewInt64Slice returns a pointer to the given tags value
+func NewInt64Slice(v []int64) *[]int64 { return &v }
+
+// NewUintSlice returns a pointer to the given tags value
+func NewUintSlice(v []uint) *[]uint { return &v }
+
+// NewUint64Slice returns a pointer to the given tags value
+func NewUint64Slice(v []uint64) *[]uint64 { return &v }
+
+// NewByteSlice returns a pointer to the given tags value
+func NewByteSlice(v []byte) *[]byte { return &v }
diff --git a/vendor/github.com/sacloud/libsacloud/v2/sacloud/pointer/types.go b/vendor/github.com/sacloud/libsacloud/v2/sacloud/pointer/types.go
new file mode 100644
index 00000000..975d0ef9
--- /dev/null
+++ b/vendor/github.com/sacloud/libsacloud/v2/sacloud/pointer/types.go
@@ -0,0 +1,29 @@
+// Copyright 2016-2020 The Libsacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package pointer
+
+import "github.com/sacloud/libsacloud/v2/sacloud/types"
+
+// NewID returns a pointer to the given value
+func NewID(id types.ID) *types.ID { return &id }
+
+// NewIDSlice returns a pointer to the given value
+func NewIDSlice(ids []types.ID) *[]types.ID { return &ids }
+
+// NewTags returns a pointer to the given tags value
+func NewTags(tags types.Tags) *types.Tags { return &tags }
+
+// NewDNSRecordType returns a pointer to the given type value
+func NewDNSRecordType(t types.EDNSRecordType) *types.EDNSRecordType { return &t }
diff --git a/vendor/modules.txt b/vendor/modules.txt
index a939c666..7f66a455 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -47,25 +47,31 @@ github.com/pmezard/go-difflib/difflib
 github.com/sacloud/ftps
 # github.com/sacloud/go-jmespath v0.0.0-20190125082617-862639817e08
 github.com/sacloud/go-jmespath
-# github.com/sacloud/libsacloud/v2 v2.8.8
+# github.com/sacloud/libsacloud/v2 v2.8.9-0.20201104084812-02587fb2e64c
 github.com/sacloud/libsacloud/v2
 github.com/sacloud/libsacloud/v2/helper/api
 github.com/sacloud/libsacloud/v2/helper/builder
 github.com/sacloud/libsacloud/v2/helper/builder/archive
 github.com/sacloud/libsacloud/v2/helper/builder/database
+github.com/sacloud/libsacloud/v2/helper/builder/disk
 github.com/sacloud/libsacloud/v2/helper/power
 github.com/sacloud/libsacloud/v2/helper/query
+github.com/sacloud/libsacloud/v2/helper/service
+github.com/sacloud/libsacloud/v2/helper/service/disk
 github.com/sacloud/libsacloud/v2/helper/setup
 github.com/sacloud/libsacloud/v2/helper/validate
+github.com/sacloud/libsacloud/v2/helper/wait
 github.com/sacloud/libsacloud/v2/pkg/cidr
 github.com/sacloud/libsacloud/v2/pkg/mapconv
 github.com/sacloud/libsacloud/v2/pkg/mutexkv
 github.com/sacloud/libsacloud/v2/pkg/size
+github.com/sacloud/libsacloud/v2/pkg/util
 github.com/sacloud/libsacloud/v2/sacloud
 github.com/sacloud/libsacloud/v2/sacloud/accessor
 github.com/sacloud/libsacloud/v2/sacloud/fake
 github.com/sacloud/libsacloud/v2/sacloud/naked
 github.com/sacloud/libsacloud/v2/sacloud/ostype
+github.com/sacloud/libsacloud/v2/sacloud/pointer
 github.com/sacloud/libsacloud/v2/sacloud/profile
 github.com/sacloud/libsacloud/v2/sacloud/search
 github.com/sacloud/libsacloud/v2/sacloud/search/keys


Test patch:
diff --git a/pkg/cmd/disk/list_test.go b/pkg/cmd/disk/list_test.go
new file mode 100644
index 00000000..879c6032
--- /dev/null
+++ b/pkg/cmd/disk/list_test.go
@@ -0,0 +1,51 @@
+// Copyright 2017-2020 The Usacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package disk
+
+import (
+	"testing"
+
+	"github.com/sacloud/libsacloud/v2/helper/service/disk"
+	"github.com/sacloud/libsacloud/v2/pkg/mapconv"
+	"github.com/sacloud/usacloud/pkg/cmd/base"
+	"github.com/stretchr/testify/require"
+)
+
+func TestList_ConvertToServiceRequest(t *testing.T) {
+	in := &ListParameter{
+		ExecContext:     nil,
+		OutputParameter: nil,
+		Zone:            "is1a",
+		Names:           []string{"name1", "name2"},
+		Tags:            []string{"tag1", "tag2"},
+		FindParameter: &base.FindParameter{
+			Count: 1,
+			From:  2,
+		},
+	}
+
+	out := &disk.FindRequest{}
+	if err := mapconv.ConvertTo(in, out); err != nil {
+		t.Fatal(err)
+	}
+
+	require.EqualValues(t, &disk.FindRequest{
+		Zone:  "is1a",
+		Names: []string{"name1", "name2"},
+		Tags:  []string{"tag1", "tag2"},
+		Count: 1,
+		From:  2,
+	}, out)
+}
diff --git a/pkg/cmd/disk/update_test.go b/pkg/cmd/disk/update_test.go
new file mode 100644
index 00000000..2952910b
--- /dev/null
+++ b/pkg/cmd/disk/update_test.go
@@ -0,0 +1,109 @@
+// Copyright 2017-2020 The Usacloud Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package disk
+
+import (
+	"testing"
+
+	"github.com/sacloud/libsacloud/v2/sacloud/pointer"
+	"github.com/sacloud/libsacloud/v2/sacloud/types"
+
+	"github.com/sacloud/libsacloud/v2/helper/service/disk"
+	"github.com/sacloud/libsacloud/v2/pkg/mapconv"
+	"github.com/stretchr/testify/require"
+)
+
+func TestUpdate_ConvertToServiceRequest(t *testing.T) {
+	t.Run("full", func(t *testing.T) {
+		in := &UpdateParameter{
+			ExecContext:     nil,
+			OutputParameter: nil,
+			Zone:            "is1a",
+			ID:              types.ID(1),
+			Name:            pointer.NewString("name"),
+			Description:     pointer.NewString("desc"),
+			Tags:            pointer.NewStringSlice([]string{"tag1", "tag2"}),
+			IconID:          pointer.NewID(types.ID(2)),
+			Connection:      pointer.NewString(types.DiskConnections.VirtIO.String()),
+		}
+
+		out := &disk.UpdateRequest{}
+		if err := mapconv.ConvertTo(in, out); err != nil {
+			t.Fatal(err)
+		}
+
+		require.EqualValues(t, &disk.UpdateRequest{
+			Zone:        "is1a",
+			ID:          types.ID(1),
+			Name:        pointer.NewString("name"),
+			Description: pointer.NewString("desc"),
+			Tags:        pointer.NewTags(types.Tags{"tag1", "tag2"}),
+			IconID:      pointer.NewID(types.ID(2)),
+			Connection:  &types.DiskConnections.VirtIO,
+		}, out)
+	})
+
+	t.Run("nil", func(t *testing.T) {
+		in := &UpdateParameter{
+			ExecContext:     nil,
+			OutputParameter: nil,
+			Zone:            "is1a",
+			ID:              types.ID(1),
+			Name:            pointer.NewString("name"),
+		}
+
+		out := &disk.UpdateRequest{}
+		if err := mapconv.ConvertTo(in, out); err != nil {
+			t.Fatal(err)
+		}
+
+		require.EqualValues(t, &disk.UpdateRequest{
+			Zone:        "is1a",
+			ID:          types.ID(1),
+			Name:        pointer.NewString("name"),
+			Description: nil,
+			Tags:        nil,
+			IconID:      nil,
+			Connection:  nil,
+		}, out)
+	})
+	t.Run("empty", func(t *testing.T) {
+		in := &UpdateParameter{
+			ExecContext:     nil,
+			OutputParameter: nil,
+			Zone:            "is1a",
+			ID:              types.ID(1),
+			Name:            pointer.NewString("name"),
+			Description:     pointer.NewString(""),
+			Tags:            pointer.NewStringSlice([]string{}),
+			IconID:          pointer.NewID(types.ID(0)),
+		}
+
+		out := &disk.UpdateRequest{}
+		if err := mapconv.ConvertTo(in, out); err != nil {
+			t.Fatal(err)
+		}
+
+		require.EqualValues(t, &disk.UpdateRequest{
+			Zone:        "is1a",
+			ID:          types.ID(1),
+			Name:        pointer.NewString("name"),
+			Description: pointer.NewString(""),
+			Tags:        pointer.NewTags(types.Tags{}),
+			IconID:      pointer.NewID(0),
+			Connection:  nil,
+		}, out)
+	})
+}
diff --git a/tools/clitag/parser_tag_test.go b/tools/clitag/parser_tag_test.go
index ca7b1a83..6e824f9f 100644
--- a/tools/clitag/parser_tag_test.go
+++ b/tools/clitag/parser_tag_test.go
@@ -33,7 +33,7 @@ func TestParser_parseTag(t *testing.T) {
 		{
 			in: `example`,
 			expect: Tag{
-				Name: "example",
+				FlagName: "example",
 			},
 		},
 		{
diff --git a/tools/clitag/parser_test.go b/tools/clitag/parser_test.go
index 192ad6af..7ba1d9d3 100644
--- a/tools/clitag/parser_test.go
+++ b/tools/clitag/parser_test.go
@@ -87,7 +87,7 @@ func TestParser_Parse(t *testing.T) {
 				{
 					StructField: reflect.TypeOf(simple{}).Field(0),
 					Tag: Tag{
-						Name: "example-field1",
+						FlagName: "example-field1",
 					},
 				},
 			},
@@ -98,7 +98,7 @@ func TestParser_Parse(t *testing.T) {
 				{
 					StructField: reflect.TypeOf(simple{}).Field(0),
 					Tag: Tag{
-						Name: "example-field1",
+						FlagName: "example-field1",
 					},
 				},
 			},
@@ -113,7 +113,7 @@ func TestParser_Parse(t *testing.T) {
 				{
 					StructField: reflect.TypeOf(noTag{}).Field(0),
 					Tag: Tag{
-						Name: "no-tag",
+						FlagName: "no-tag",
 					},
 				},
 			},
@@ -128,13 +128,13 @@ func TestParser_Parse(t *testing.T) {
 				{
 					StructField: reflect.TypeOf(nested1{}).Field(0),
 					Tag: Tag{
-						Name: "nested-1-field-1",
+						FlagName: "nested-1-field-1",
 					},
 				},
 				{
 					StructField: reflect.TypeOf(nested2{}).Field(0),
 					Tag: Tag{
-						Name: "nested-1-field-2-field-1",
+						FlagName: "nested-1-field-2-field-1",
 					},
 				},
 			},
@@ -145,25 +145,25 @@ func TestParser_Parse(t *testing.T) {
 				{
 					StructField: reflect.TypeOf(nested1{}).Field(0),
 					Tag: Tag{
-						Name: "field-1",
+						FlagName: "field-1",
 					},
 				},
 				{
 					StructField: reflect.TypeOf(nested2{}).Field(0),
 					Tag: Tag{
-						Name: "field-2-field-1",
+						FlagName: "field-2-field-1",
 					},
 				},
 				{
 					StructField: reflect.TypeOf(nested1{}).Field(0),
 					Tag: Tag{
-						Name: "nested-2-field-1",
+						FlagName: "nested-2-field-1",
 					},
 				},
 				{
 					StructField: reflect.TypeOf(nested2{}).Field(0),
 					Tag: Tag{
-						Name: "nested-2-field-2-field-1",
+						FlagName: "nested-2-field-2-field-1",
 					},
 				},
 			},
diff --git a/tools/functions_test.go b/tools/utils/functions_test.go
similarity index 99%
rename from tools/functions_test.go
rename to tools/utils/functions_test.go
index 0ff54c0f..7d06dd5e 100644
--- a/tools/functions_test.go
+++ b/tools/utils/functions_test.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package tools
+package utils
 
 import (
 	"testing"