diff --git a/README.md b/README.md index 19d8210..789d039 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ to the command. Current commands supported in a `plan` file: - cloud server create +- cloud server resize - cloud template restore Example: diff --git a/cmd/cloudServerResize.go b/cmd/cloudServerResize.go index d13c1bb..8895264 100644 --- a/cmd/cloudServerResize.go +++ b/cmd/cloudServerResize.go @@ -20,8 +20,7 @@ import ( "github.com/spf13/cobra" - "github.com/liquidweb/liquidweb-cli/types/api" - "github.com/liquidweb/liquidweb-cli/validate" + "github.com/liquidweb/liquidweb-cli/instance" ) var cloudServerResizeCmd = &cobra.Command{ @@ -68,204 +67,22 @@ going to a config with more diskspace, and --skip-fs-resize wasn't passed. During all resizes, the Cloud Server is online as the disk synchronizes. `, Run: func(cmd *cobra.Command, args []string) { - uniqIdFlag, _ := cmd.Flags().GetString("uniq-id") - diskspaceFlag, _ := cmd.Flags().GetInt64("diskspace") - configIdFlag, _ := cmd.Flags().GetInt64("config-id") - memoryFlag, _ := cmd.Flags().GetInt64("memory") - skipFsResizeFlag, _ := cmd.Flags().GetBool("skip-fs-resize") - vcpuFlag, _ := cmd.Flags().GetInt64("vcpu") - privateParentFlag, _ := cmd.Flags().GetString("private-parent") + params := &instance.CloudServerResizeParams{} - validateFields := map[interface{}]interface{}{ - uniqIdFlag: "UniqId", - } - // must validate UniqId now because we call api methods with this uniq_id before below validate - if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(err) - } - - // convert bool to int for api - skipFsResizeInt := 0 - if skipFsResizeFlag { - skipFsResizeInt = 1 - } - - if configIdFlag == -1 && privateParentFlag == "" { - lwCliInst.Die(fmt.Errorf("flag --config-id required when --private-parent is not given")) - } - - resizeArgs := map[string]interface{}{ - "uniq_id": uniqIdFlag, - "skip_fs_resize": skipFsResizeInt, - "newsize": configIdFlag, - } - - // get details of existing configuration - var cloudServerDetails apiTypes.CloudServerDetails - if err := lwCliInst.CallLwApiInto("bleed/storm/server/details", - map[string]interface{}{"uniq_id": uniqIdFlag}, - &cloudServerDetails); err != nil { - lwCliInst.Die(err) - } - - var liveResize bool - var twoRebootResize bool - if privateParentFlag == "" { - // non private parent resize - if memoryFlag != -1 || diskspaceFlag != -1 || vcpuFlag != -1 { - lwCliInst.Die(fmt.Errorf("cannot pass --memory --diskspace or --vcpu when --private-parent is not given")) - } - - // if already on the given config, nothing to do - if cloudServerDetails.ConfigId == configIdFlag { - lwCliInst.Die(fmt.Errorf("already on config-id [%d]; not initiating a resize", configIdFlag)) - } - - validateFields[configIdFlag] = "PositiveInt64" - if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(err) - } + params.UniqId, _ = cmd.Flags().GetString("uniq-id") + params.DiskSpace, _ = cmd.Flags().GetInt64("diskspace") + params.ConfigId, _ = cmd.Flags().GetInt64("config-id") + params.Memory, _ = cmd.Flags().GetInt64("memory") + params.SkipFsResize, _ = cmd.Flags().GetBool("skip-fs-resize") + params.Vcpu, _ = cmd.Flags().GetInt64("vcpu") + params.PrivateParent, _ = cmd.Flags().GetString("private-parent") - // determine reboot expectation. - // resize up full: 2 reboot - // resize up quick (skip-fs-resize) 1 reboot - // resize down: 1 reboot - var configDetails apiTypes.CloudConfigDetails - if err := lwCliInst.CallLwApiInto("bleed/storm/config/details", - map[string]interface{}{"id": configIdFlag}, &configDetails); err != nil { - lwCliInst.Die(err) - } - - if configDetails.Disk >= cloudServerDetails.DiskSpace { - // disk space going up.. - if !skipFsResizeFlag { - // .. and not skipping fs resize, will be 2 reboots. - twoRebootResize = true - } - } - } else { - // private parent resize specific logic - if memoryFlag == -1 && diskspaceFlag == -1 && vcpuFlag == -1 { - lwCliInst.Die(fmt.Errorf( - "resizes on private parents require at least least one of: --memory --diskspace --vcpu flags")) - } - - privateParentUniqId, err := lwCliInst.DerivePrivateParentUniqId(privateParentFlag) - if err != nil { - lwCliInst.Die(err) - } - - var ( - diskspaceChanging bool - vcpuChanging bool - memoryChanging bool - memoryCanLive bool - vcpuCanLive bool - ) - // record what resources are changing - if diskspaceFlag != -1 { - if cloudServerDetails.DiskSpace != diskspaceFlag { - diskspaceChanging = true - } - } - if vcpuFlag != -1 { - if cloudServerDetails.Vcpu != vcpuFlag { - vcpuChanging = true - } - } - if memoryFlag != -1 { - if cloudServerDetails.Memory != memoryFlag { - memoryChanging = true - } - } - // allow resizes to a private parent even if its old non private parent config had exact same specs - if cloudServerDetails.ConfigId == 0 && cloudServerDetails.PrivateParent != privateParentUniqId { - if !diskspaceChanging && !vcpuChanging && !memoryChanging { - lwCliInst.Die(fmt.Errorf( - "private parent resize, but passed diskspace, memory, vcpu values match existing values")) - } - } - - resizeArgs["newsize"] = 0 // 0 indicates private parent resize - resizeArgs["parent"] = privateParentUniqId // uniq_id of the private parent - validateFields[privateParentUniqId] = "UniqId" - // server/resize api method always wants diskspace, vcpu, memory passed for pp resize, even if not changing - // value. So set to current value, then override based on passed flags. - resizeArgs["diskspace"] = cloudServerDetails.DiskSpace - resizeArgs["memory"] = cloudServerDetails.Memory - resizeArgs["vcpu"] = cloudServerDetails.Vcpu - - if diskspaceFlag != -1 { - resizeArgs["diskspace"] = diskspaceFlag // desired diskspace - validateFields[diskspaceFlag] = "PositiveInt64" - } - if memoryFlag != -1 { - resizeArgs["memory"] = memoryFlag // desired memory - validateFields[memoryFlag] = "PositiveInt64" - } - if vcpuFlag != -1 { - resizeArgs["vcpu"] = vcpuFlag // desired vcpus - validateFields[vcpuFlag] = "PositiveInt64" - } - - // determine if this will be a live resize - if _, exists := resizeArgs["memory"]; exists { - if memoryFlag >= cloudServerDetails.Memory { - // asking for more RAM - memoryCanLive = true - } - } - if _, exists := resizeArgs["vcpu"]; exists { - if vcpuFlag >= cloudServerDetails.Vcpu { - // asking for more vcpu - vcpuCanLive = true - } - } - - if memoryFlag != -1 && vcpuFlag != -1 { - if vcpuCanLive && memoryCanLive { - liveResize = true - } - } else if memoryCanLive { - liveResize = true - } else if vcpuCanLive { - liveResize = true - } - - // if diskspace allocation changes its not currently ever done live regardless of memory, vcpu - if diskspaceFlag != -1 { - if resizeArgs["diskspace"] != cloudServerDetails.DiskSpace { - liveResize = false - } - } - } - - if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(err) - } - - _, err := lwCliInst.LwCliApiClient.Call("bleed/server/resize", resizeArgs) + status, err := lwCliInst.CloudServerResize(params) if err != nil { lwCliInst.Die(err) } - fmt.Printf("server resized started! You can check progress with 'cloud server status --uniq-id %s'\n\n", uniqIdFlag) - - if liveResize { - fmt.Printf("\nthis resize will be performed live without downtime.\n") - } else { - rebootExpectation := "one reboot" - if twoRebootResize { - rebootExpectation = "two reboots" - } - fmt.Printf( - "\nexpect %s during this process. Your server will be online as the disk is copied to the destination.\n", - rebootExpectation) - if twoRebootResize { - fmt.Printf( - "\tTIP: Avoid the second reboot by passing --skip-fs-resize. See usage for additional details.\n") - } - } + fmt.Print(status) }, } diff --git a/instance/cloudServerResize.go b/instance/cloudServerResize.go new file mode 100644 index 0000000..1b3d91a --- /dev/null +++ b/instance/cloudServerResize.go @@ -0,0 +1,260 @@ +/* +Copyright © LiquidWeb + +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 instance + +import ( + "bytes" + "errors" + "fmt" + + "github.com/liquidweb/liquidweb-cli/types/api" + "github.com/liquidweb/liquidweb-cli/validate" +) + +type CloudServerResizeParams struct { + UniqId string `yaml:"uniq-id"` + ConfigId int64 `yaml:"config-id"` + SkipFsResize bool `yaml:"skip-fs-resize"` + PrivateParent string `yaml:"private-parent"` + Memory int64 `yaml:"memory"` + Vcpu int64 `yaml:"vcpu"` + DiskSpace int64 `yaml:"disk-space"` +} + +func (self *CloudServerResizeParams) UnmarshalYAML(unmarshal func(interface{}) error) error { + // define defaults + type rawType CloudServerResizeParams + raw := rawType{ + ConfigId: -1, + Memory: -1, + Vcpu: -1, + DiskSpace: -1, + } // Put your defaults here + if err := unmarshal(&raw); err != nil { + return err + } + *self = CloudServerResizeParams(raw) + + return nil +} + +func (self *Client) CloudServerResize(params *CloudServerResizeParams) (result string, err error) { + + validateFields := map[interface{}]interface{}{ + params.UniqId: "UniqId", + } + + // must validate UniqId now because we call api methods with this uniq_id before below validate + if err = validate.Validate(validateFields); err != nil { + return + } + + // convert bool to int for api + skipFsResizeInt := 0 + if params.SkipFsResize { + skipFsResizeInt = 1 + } + + if params.ConfigId == -1 && params.PrivateParent == "" { + err = errors.New("flag --config-id required when --private-parent is not given") + return + } + + resizeArgs := map[string]interface{}{ + "uniq_id": params.UniqId, + "skip_fs_resize": skipFsResizeInt, + "newsize": params.ConfigId, + } + + // get details of existing configuration + var cloudServerDetails apiTypes.CloudServerDetails + if err = self.CallLwApiInto( + "bleed/storm/server/details", + map[string]interface{}{ + "uniq_id": params.UniqId, + }, &cloudServerDetails); err != nil { + return + } + + var ( + liveResize bool + twoRebootResize bool + ) + if params.PrivateParent == "" { + // non private parent resize + if params.Memory != -1 || params.DiskSpace != -1 || params.Vcpu != -1 { + err = errors.New("cannot pass --memory --diskspace or --vcpu when --private-parent is not given") + return + } + + // if already on the given config, nothing to do + if cloudServerDetails.ConfigId == params.ConfigId { + err = fmt.Errorf("already on config-id [%d]; not initiating a resize", params.ConfigId) + return + } + + validateFields[params.ConfigId] = "PositiveInt64" + if err = validate.Validate(validateFields); err != nil { + return + } + + // determine reboot expectation. + // resize up full: 2 reboot + // resize up quick (skip-fs-resize) 1 reboot + // resize down: 1 reboot + var configDetails apiTypes.CloudConfigDetails + if err = self.CallLwApiInto("bleed/storm/config/details", + map[string]interface{}{"id": params.ConfigId}, &configDetails); err != nil { + return + } + + if configDetails.Disk >= cloudServerDetails.DiskSpace { + // disk space going up.. + if !params.SkipFsResize { + // .. and not skipping fs resize, will be 2 reboots. + twoRebootResize = true + } + } + } else { + // private parent resize specific logic + if params.Memory == -1 && params.DiskSpace == -1 && params.Vcpu == -1 { + err = errors.New("resizes on private parents require at least least one of: --memory --diskspace --vcpu flags") + return + } + + var privateParentUniqId string + privateParentUniqId, err = self.DerivePrivateParentUniqId(params.PrivateParent) + if err != nil { + return + } + + var ( + diskspaceChanging bool + vcpuChanging bool + memoryChanging bool + memoryCanLive bool + vcpuCanLive bool + ) + // record what resources are changing + if params.DiskSpace != -1 { + if cloudServerDetails.DiskSpace != params.DiskSpace { + diskspaceChanging = true + } + } + if params.Vcpu != -1 { + if cloudServerDetails.Vcpu != params.Vcpu { + vcpuChanging = true + } + } + if params.Memory != -1 { + if cloudServerDetails.Memory != params.Memory { + memoryChanging = true + } + } + // allow resizes to a private parent even if its old non private parent config had exact same specs + if cloudServerDetails.ConfigId == 0 && cloudServerDetails.PrivateParent != privateParentUniqId { + if !diskspaceChanging && !vcpuChanging && !memoryChanging { + err = errors.New("private parent resize, but passed diskspace, memory, vcpu values match existing values") + return + } + } + + resizeArgs["newsize"] = 0 // 0 indicates private parent resize + resizeArgs["parent"] = privateParentUniqId // uniq_id of the private parent + validateFields[privateParentUniqId] = "UniqId" + // server/resize api method always wants diskspace, vcpu, memory passed for pp resize, even if not changing + // value. So set to current value, then override based on passed flags. + resizeArgs["diskspace"] = cloudServerDetails.DiskSpace + resizeArgs["memory"] = cloudServerDetails.Memory + resizeArgs["vcpu"] = cloudServerDetails.Vcpu + + if params.DiskSpace != -1 { + resizeArgs["diskspace"] = params.DiskSpace // desired diskspace + validateFields[params.DiskSpace] = "PositiveInt64" + } + if params.Memory != -1 { + resizeArgs["memory"] = params.Memory // desired memory + validateFields[params.Memory] = "PositiveInt64" + } + if params.Vcpu != -1 { + resizeArgs["vcpu"] = params.Vcpu // desired vcpus + validateFields[params.Vcpu] = "PositiveInt64" + } + + // determine if this will be a live resize + if _, exists := resizeArgs["memory"]; exists { + if params.Memory >= cloudServerDetails.Memory { + // asking for more RAM + memoryCanLive = true + } + } + if _, exists := resizeArgs["vcpu"]; exists { + if params.Vcpu >= cloudServerDetails.Vcpu { + // asking for more vcpu + vcpuCanLive = true + } + } + + if params.Memory != -1 && params.Vcpu != -1 { + if vcpuCanLive && memoryCanLive { + liveResize = true + } + } else if memoryCanLive { + liveResize = true + } else if vcpuCanLive { + liveResize = true + } + + // if diskspace allocation changes its not currently ever done live regardless of memory, vcpu + if params.DiskSpace != -1 { + if resizeArgs["diskspace"] != cloudServerDetails.DiskSpace { + liveResize = false + } + } + } + + if err = validate.Validate(validateFields); err != nil { + return + } + + if _, err = self.LwCliApiClient.Call("bleed/server/resize", resizeArgs); err != nil { + return + } + + var b bytes.Buffer + b.WriteString(fmt.Sprintf("server resized started! You can check progress with 'cloud server status --uniq-id %s'\n\n", params.UniqId)) + + if liveResize { + b.WriteString(fmt.Sprintf("\nthis resize will be performed live without downtime.\n")) + } else { + rebootExpectation := "one reboot" + if twoRebootResize { + rebootExpectation = "two reboots" + } + b.WriteString(fmt.Sprintf( + "\nexpect %s during this process. Your server will be online as the disk is copied to the destination.\n", + rebootExpectation)) + + if twoRebootResize { + b.WriteString(fmt.Sprintf( + "\tTIP: Avoid the second reboot by passing --skip-fs-resize. See usage for additional details.\n")) + } + } + + result = b.String() + + return +} diff --git a/instance/plan.go b/instance/plan.go index f18c5aa..225962c 100644 --- a/instance/plan.go +++ b/instance/plan.go @@ -17,7 +17,6 @@ package instance import ( "fmt" - "os" ) type Plan struct { @@ -31,6 +30,7 @@ type PlanCloud struct { type PlanCloudServer struct { Create []CloudServerCreateParams + Resize []CloudServerResizeParams } type PlanCloudTemplate struct { @@ -74,6 +74,13 @@ func (ci *Client) processPlanCloudServer(server *PlanCloudServer) error { } } } + if server.Resize != nil { + for _, r := range server.Resize { + if err := ci.processPlanCloudServerResize(&r); err != nil { + return err + } + } + } return nil } @@ -82,8 +89,7 @@ func (ci *Client) processPlanCloudServerCreate(params *CloudServerCreateParams) uniqId, err := ci.CloudServerCreate(params) if err != nil { - fmt.Print(err) - os.Exit(1) + return err } fmt.Printf( @@ -92,6 +98,18 @@ func (ci *Client) processPlanCloudServerCreate(params *CloudServerCreateParams) return nil } +func (ci *Client) processPlanCloudServerResize(params *CloudServerResizeParams) error { + + result, err := ci.CloudServerResize(params) + if err != nil { + return err + } + + fmt.Print(result) + + return nil +} + func (ci *Client) processPlanCloudTemplate(template *PlanCloudTemplate) error { if template.Restore != nil {