diff --git a/docs/resources/vm.md b/docs/resources/vm.md index eb42003..1d1816f 100644 --- a/docs/resources/vm.md +++ b/docs/resources/vm.md @@ -33,8 +33,10 @@ data "crunchloop_vmi" "ubuntu" { name = "Ubuntu 24.04 (noble)" } -resource "crunchloop_vm" "default" { - name = "terraform-vm" +# You can be assign a host directly to a VM +# +resource "crunchloop_vm" "with_host" { + name = "terraform-with-host" vmi_id = data.crunchloop_vmi.ubuntu.id host_id = data.crunchloop_host.test-1.id cores = 1 @@ -42,6 +44,17 @@ resource "crunchloop_vm" "default" { root_volume_size_gigabytes = 10 user_data = base64encode("echo 'Hello, World!'") } + +# Or the can let the system allocate the host for you +# +resource "crunchloop_vm" "without_host" { + name = "terraform-without-host" + vmi_id = data.crunchloop_vmi.ubuntu.id + cores = 1 + memory_megabytes = 1024 + root_volume_size_gigabytes = 10 + user_data = base64encode("echo 'Hello, World!'") +} ``` @@ -50,7 +63,6 @@ resource "crunchloop_vm" "default" { ### Required - `cores` (Number) Virtual CPU cores -- `host_id` (Number) Identifier of the Host where the Vm will be created - `memory_megabytes` (Number) Memory (MiB) - `name` (String) Name of the Vm - `root_volume_size_gigabytes` (Number) Root volume size (GiB) @@ -58,6 +70,7 @@ resource "crunchloop_vm" "default" { ### Optional +- `host_id` (Number) Identifier of the Host where the Vm will be created - `ssh_key` (String) Ssh public key to authenticate with the Vm - `user_data` (String) Cloud init user data shell script, base64 encoded diff --git a/examples/resources/crunchloop_vm/resource.tf b/examples/resources/crunchloop_vm/resource.tf index e3b0f04..aacfa7f 100644 --- a/examples/resources/crunchloop_vm/resource.tf +++ b/examples/resources/crunchloop_vm/resource.tf @@ -18,8 +18,10 @@ data "crunchloop_vmi" "ubuntu" { name = "Ubuntu 24.04 (noble)" } -resource "crunchloop_vm" "default" { - name = "terraform-vm" +# You can be assign a host directly to a VM +# +resource "crunchloop_vm" "with_host" { + name = "terraform-with-host" vmi_id = data.crunchloop_vmi.ubuntu.id host_id = data.crunchloop_host.test-1.id cores = 1 @@ -27,3 +29,14 @@ resource "crunchloop_vm" "default" { root_volume_size_gigabytes = 10 user_data = base64encode("echo 'Hello, World!'") } + +# Or the can let the system allocate the host for you +# +resource "crunchloop_vm" "without_host" { + name = "terraform-without-host" + vmi_id = data.crunchloop_vmi.ubuntu.id + cores = 1 + memory_megabytes = 1024 + root_volume_size_gigabytes = 10 + user_data = base64encode("echo 'Hello, World!'") +} diff --git a/internal/client/client.gen.go b/internal/client/client.gen.go index 7177f34..74982c1 100644 --- a/internal/client/client.gen.go +++ b/internal/client/client.gen.go @@ -57,7 +57,7 @@ type ErrorCode string // Host defines model for Host. type Host struct { - Id *int `json:"id,omitempty"` + Id *int32 `json:"id,omitempty"` Name *string `json:"name,omitempty"` Object *string `json:"object,omitempty"` Status *HostStatus `json:"status,omitempty"` @@ -76,7 +76,7 @@ type HostCollection struct { // NetworkInterface defines model for NetworkInterface. type NetworkInterface struct { Dhcp *bool `json:"dhcp,omitempty"` - Id *int `json:"id,omitempty"` + Id *int32 `json:"id,omitempty"` IpAddress *string `json:"ip_address,omitempty"` Object *string `json:"object,omitempty"` } @@ -85,7 +85,7 @@ type NetworkInterface struct { type VirtualMachine struct { Cores *int32 `json:"cores,omitempty"` Host *Host `json:"host,omitempty"` - Id *int `json:"id,omitempty"` + Id *int32 `json:"id,omitempty"` MemoryBytes *int64 `json:"memory_bytes,omitempty"` Name *string `json:"name,omitempty"` Nic *NetworkInterface `json:"nic,omitempty"` @@ -100,7 +100,7 @@ type VirtualMachineStatus string // VirtualMachineImage defines model for VirtualMachineImage. type VirtualMachineImage struct { - Id *int `json:"id,omitempty"` + Id *int32 `json:"id,omitempty"` Name *string `json:"name,omitempty"` Object *string `json:"object,omitempty"` } @@ -114,7 +114,7 @@ type VirtualMachineImageCollection struct { // Volume defines model for Volume. type Volume struct { - Id *int `json:"id,omitempty"` + Id *int32 `json:"id,omitempty"` Name *string `json:"name,omitempty"` Object *string `json:"object,omitempty"` SizeBytes *int64 `json:"size_bytes,omitempty"` @@ -127,13 +127,13 @@ type VolumeStatus string // CreateVmJSONBody defines parameters for CreateVm. type CreateVmJSONBody struct { Cores int32 `json:"cores"` - HostId int `json:"host_id"` + HostId *int32 `json:"host_id,omitempty"` MemoryMegabytes int32 `json:"memory_megabytes"` Name string `json:"name"` RootVolumeSizeGigabytes int32 `json:"root_volume_size_gigabytes"` SshKey *string `json:"ssh_key,omitempty"` UserData *string `json:"user_data,omitempty"` - VmiId int `json:"vmi_id"` + VmiId int32 `json:"vmi_id"` } // UpdateVmJSONBody defines parameters for UpdateVm. @@ -233,21 +233,24 @@ type ClientInterface interface { CreateVm(ctx context.Context, body CreateVmJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // DeleteVm request - DeleteVm(ctx context.Context, id int, reqEditors ...RequestEditorFn) (*http.Response, error) + DeleteVm(ctx context.Context, id int32, reqEditors ...RequestEditorFn) (*http.Response, error) // GetVm request - GetVm(ctx context.Context, id int, reqEditors ...RequestEditorFn) (*http.Response, error) + GetVm(ctx context.Context, id int32, reqEditors ...RequestEditorFn) (*http.Response, error) // UpdateVmWithBody request with any body - UpdateVmWithBody(ctx context.Context, id int, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + UpdateVmWithBody(ctx context.Context, id int32, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - UpdateVm(ctx context.Context, id int, body UpdateVmJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + UpdateVm(ctx context.Context, id int32, body UpdateVmJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // RebootVm request + RebootVm(ctx context.Context, id int32, reqEditors ...RequestEditorFn) (*http.Response, error) // StartVm request - StartVm(ctx context.Context, id int, reqEditors ...RequestEditorFn) (*http.Response, error) + StartVm(ctx context.Context, id int32, reqEditors ...RequestEditorFn) (*http.Response, error) // StopVm request - StopVm(ctx context.Context, id int, reqEditors ...RequestEditorFn) (*http.Response, error) + StopVm(ctx context.Context, id int32, reqEditors ...RequestEditorFn) (*http.Response, error) } func (c *Client) ListHosts(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { @@ -298,7 +301,7 @@ func (c *Client) CreateVm(ctx context.Context, body CreateVmJSONRequestBody, req return c.Client.Do(req) } -func (c *Client) DeleteVm(ctx context.Context, id int, reqEditors ...RequestEditorFn) (*http.Response, error) { +func (c *Client) DeleteVm(ctx context.Context, id int32, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewDeleteVmRequest(c.Server, id) if err != nil { return nil, err @@ -310,7 +313,7 @@ func (c *Client) DeleteVm(ctx context.Context, id int, reqEditors ...RequestEdit return c.Client.Do(req) } -func (c *Client) GetVm(ctx context.Context, id int, reqEditors ...RequestEditorFn) (*http.Response, error) { +func (c *Client) GetVm(ctx context.Context, id int32, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewGetVmRequest(c.Server, id) if err != nil { return nil, err @@ -322,7 +325,7 @@ func (c *Client) GetVm(ctx context.Context, id int, reqEditors ...RequestEditorF return c.Client.Do(req) } -func (c *Client) UpdateVmWithBody(ctx context.Context, id int, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { +func (c *Client) UpdateVmWithBody(ctx context.Context, id int32, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewUpdateVmRequestWithBody(c.Server, id, contentType, body) if err != nil { return nil, err @@ -334,7 +337,7 @@ func (c *Client) UpdateVmWithBody(ctx context.Context, id int, contentType strin return c.Client.Do(req) } -func (c *Client) UpdateVm(ctx context.Context, id int, body UpdateVmJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { +func (c *Client) UpdateVm(ctx context.Context, id int32, body UpdateVmJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewUpdateVmRequest(c.Server, id, body) if err != nil { return nil, err @@ -346,7 +349,19 @@ func (c *Client) UpdateVm(ctx context.Context, id int, body UpdateVmJSONRequestB return c.Client.Do(req) } -func (c *Client) StartVm(ctx context.Context, id int, reqEditors ...RequestEditorFn) (*http.Response, error) { +func (c *Client) RebootVm(ctx context.Context, id int32, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewRebootVmRequest(c.Server, id) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) StartVm(ctx context.Context, id int32, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewStartVmRequest(c.Server, id) if err != nil { return nil, err @@ -358,7 +373,7 @@ func (c *Client) StartVm(ctx context.Context, id int, reqEditors ...RequestEdito return c.Client.Do(req) } -func (c *Client) StopVm(ctx context.Context, id int, reqEditors ...RequestEditorFn) (*http.Response, error) { +func (c *Client) StopVm(ctx context.Context, id int32, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewStopVmRequest(c.Server, id) if err != nil { return nil, err @@ -465,7 +480,7 @@ func NewCreateVmRequestWithBody(server string, contentType string, body io.Reade } // NewDeleteVmRequest generates requests for DeleteVm -func NewDeleteVmRequest(server string, id int) (*http.Request, error) { +func NewDeleteVmRequest(server string, id int32) (*http.Request, error) { var err error var pathParam0 string @@ -499,7 +514,7 @@ func NewDeleteVmRequest(server string, id int) (*http.Request, error) { } // NewGetVmRequest generates requests for GetVm -func NewGetVmRequest(server string, id int) (*http.Request, error) { +func NewGetVmRequest(server string, id int32) (*http.Request, error) { var err error var pathParam0 string @@ -533,7 +548,7 @@ func NewGetVmRequest(server string, id int) (*http.Request, error) { } // NewUpdateVmRequest calls the generic UpdateVm builder with application/json body -func NewUpdateVmRequest(server string, id int, body UpdateVmJSONRequestBody) (*http.Request, error) { +func NewUpdateVmRequest(server string, id int32, body UpdateVmJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { @@ -544,7 +559,7 @@ func NewUpdateVmRequest(server string, id int, body UpdateVmJSONRequestBody) (*h } // NewUpdateVmRequestWithBody generates requests for UpdateVm with any type of body -func NewUpdateVmRequestWithBody(server string, id int, contentType string, body io.Reader) (*http.Request, error) { +func NewUpdateVmRequestWithBody(server string, id int32, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -579,8 +594,42 @@ func NewUpdateVmRequestWithBody(server string, id int, contentType string, body return req, nil } +// NewRebootVmRequest generates requests for RebootVm +func NewRebootVmRequest(server string, id int32) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "id", runtime.ParamLocationPath, id) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("api/v1/vms/%s/reboot", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewStartVmRequest generates requests for StartVm -func NewStartVmRequest(server string, id int) (*http.Request, error) { +func NewStartVmRequest(server string, id int32) (*http.Request, error) { var err error var pathParam0 string @@ -614,7 +663,7 @@ func NewStartVmRequest(server string, id int) (*http.Request, error) { } // NewStopVmRequest generates requests for StopVm -func NewStopVmRequest(server string, id int) (*http.Request, error) { +func NewStopVmRequest(server string, id int32) (*http.Request, error) { var err error var pathParam0 string @@ -702,21 +751,24 @@ type ClientWithResponsesInterface interface { CreateVmWithResponse(ctx context.Context, body CreateVmJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateVmResponse, error) // DeleteVmWithResponse request - DeleteVmWithResponse(ctx context.Context, id int, reqEditors ...RequestEditorFn) (*DeleteVmResponse, error) + DeleteVmWithResponse(ctx context.Context, id int32, reqEditors ...RequestEditorFn) (*DeleteVmResponse, error) // GetVmWithResponse request - GetVmWithResponse(ctx context.Context, id int, reqEditors ...RequestEditorFn) (*GetVmResponse, error) + GetVmWithResponse(ctx context.Context, id int32, reqEditors ...RequestEditorFn) (*GetVmResponse, error) // UpdateVmWithBodyWithResponse request with any body - UpdateVmWithBodyWithResponse(ctx context.Context, id int, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateVmResponse, error) + UpdateVmWithBodyWithResponse(ctx context.Context, id int32, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateVmResponse, error) - UpdateVmWithResponse(ctx context.Context, id int, body UpdateVmJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateVmResponse, error) + UpdateVmWithResponse(ctx context.Context, id int32, body UpdateVmJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateVmResponse, error) + + // RebootVmWithResponse request + RebootVmWithResponse(ctx context.Context, id int32, reqEditors ...RequestEditorFn) (*RebootVmResponse, error) // StartVmWithResponse request - StartVmWithResponse(ctx context.Context, id int, reqEditors ...RequestEditorFn) (*StartVmResponse, error) + StartVmWithResponse(ctx context.Context, id int32, reqEditors ...RequestEditorFn) (*StartVmResponse, error) // StopVmWithResponse request - StopVmWithResponse(ctx context.Context, id int, reqEditors ...RequestEditorFn) (*StopVmResponse, error) + StopVmWithResponse(ctx context.Context, id int32, reqEditors ...RequestEditorFn) (*StopVmResponse, error) } type ListHostsResponse struct { @@ -856,6 +908,30 @@ func (r UpdateVmResponse) StatusCode() int { return 0 } +type RebootVmResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *VirtualMachine + JSON400 *Error + JSON404 *Error +} + +// Status returns HTTPResponse.Status +func (r RebootVmResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r RebootVmResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type StartVmResponse struct { Body []byte HTTPResponse *http.Response @@ -940,7 +1016,7 @@ func (c *ClientWithResponses) CreateVmWithResponse(ctx context.Context, body Cre } // DeleteVmWithResponse request returning *DeleteVmResponse -func (c *ClientWithResponses) DeleteVmWithResponse(ctx context.Context, id int, reqEditors ...RequestEditorFn) (*DeleteVmResponse, error) { +func (c *ClientWithResponses) DeleteVmWithResponse(ctx context.Context, id int32, reqEditors ...RequestEditorFn) (*DeleteVmResponse, error) { rsp, err := c.DeleteVm(ctx, id, reqEditors...) if err != nil { return nil, err @@ -949,7 +1025,7 @@ func (c *ClientWithResponses) DeleteVmWithResponse(ctx context.Context, id int, } // GetVmWithResponse request returning *GetVmResponse -func (c *ClientWithResponses) GetVmWithResponse(ctx context.Context, id int, reqEditors ...RequestEditorFn) (*GetVmResponse, error) { +func (c *ClientWithResponses) GetVmWithResponse(ctx context.Context, id int32, reqEditors ...RequestEditorFn) (*GetVmResponse, error) { rsp, err := c.GetVm(ctx, id, reqEditors...) if err != nil { return nil, err @@ -958,7 +1034,7 @@ func (c *ClientWithResponses) GetVmWithResponse(ctx context.Context, id int, req } // UpdateVmWithBodyWithResponse request with arbitrary body returning *UpdateVmResponse -func (c *ClientWithResponses) UpdateVmWithBodyWithResponse(ctx context.Context, id int, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateVmResponse, error) { +func (c *ClientWithResponses) UpdateVmWithBodyWithResponse(ctx context.Context, id int32, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateVmResponse, error) { rsp, err := c.UpdateVmWithBody(ctx, id, contentType, body, reqEditors...) if err != nil { return nil, err @@ -966,7 +1042,7 @@ func (c *ClientWithResponses) UpdateVmWithBodyWithResponse(ctx context.Context, return ParseUpdateVmResponse(rsp) } -func (c *ClientWithResponses) UpdateVmWithResponse(ctx context.Context, id int, body UpdateVmJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateVmResponse, error) { +func (c *ClientWithResponses) UpdateVmWithResponse(ctx context.Context, id int32, body UpdateVmJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateVmResponse, error) { rsp, err := c.UpdateVm(ctx, id, body, reqEditors...) if err != nil { return nil, err @@ -974,8 +1050,17 @@ func (c *ClientWithResponses) UpdateVmWithResponse(ctx context.Context, id int, return ParseUpdateVmResponse(rsp) } +// RebootVmWithResponse request returning *RebootVmResponse +func (c *ClientWithResponses) RebootVmWithResponse(ctx context.Context, id int32, reqEditors ...RequestEditorFn) (*RebootVmResponse, error) { + rsp, err := c.RebootVm(ctx, id, reqEditors...) + if err != nil { + return nil, err + } + return ParseRebootVmResponse(rsp) +} + // StartVmWithResponse request returning *StartVmResponse -func (c *ClientWithResponses) StartVmWithResponse(ctx context.Context, id int, reqEditors ...RequestEditorFn) (*StartVmResponse, error) { +func (c *ClientWithResponses) StartVmWithResponse(ctx context.Context, id int32, reqEditors ...RequestEditorFn) (*StartVmResponse, error) { rsp, err := c.StartVm(ctx, id, reqEditors...) if err != nil { return nil, err @@ -984,7 +1069,7 @@ func (c *ClientWithResponses) StartVmWithResponse(ctx context.Context, id int, r } // StopVmWithResponse request returning *StopVmResponse -func (c *ClientWithResponses) StopVmWithResponse(ctx context.Context, id int, reqEditors ...RequestEditorFn) (*StopVmResponse, error) { +func (c *ClientWithResponses) StopVmWithResponse(ctx context.Context, id int32, reqEditors ...RequestEditorFn) (*StopVmResponse, error) { rsp, err := c.StopVm(ctx, id, reqEditors...) if err != nil { return nil, err @@ -1183,6 +1268,46 @@ func ParseUpdateVmResponse(rsp *http.Response) (*UpdateVmResponse, error) { return response, nil } +// ParseRebootVmResponse parses an HTTP response from a RebootVmWithResponse call +func ParseRebootVmResponse(rsp *http.Response) (*RebootVmResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &RebootVmResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest VirtualMachine + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + } + + return response, nil +} + // ParseStartVmResponse parses an HTTP response from a StartVmWithResponse call func ParseStartVmResponse(rsp *http.Response) (*StartVmResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) diff --git a/internal/client/openapi.yaml b/internal/client/openapi.yaml index 3d19760..4f5ac15 100644 --- a/internal/client/openapi.yaml +++ b/internal/client/openapi.yaml @@ -22,9 +22,11 @@ paths: example: swagger-vm host_id: type: integer + format: int32 example: 10 vmi_id: type: integer + format: int32 example: 20 cores: type: integer @@ -51,7 +53,6 @@ paths: type: string required: - name - - host_id - vmi_id - cores - memory_megabytes @@ -70,7 +71,6 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' - api/v1/vms/{id}: get: summary: Get virtual machine @@ -81,6 +81,7 @@ paths: required: true schema: type: integer + format: int32 example: 1 responses: '200': @@ -96,7 +97,6 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' - put: summary: Update virtual machine operationId: updateVm @@ -106,6 +106,7 @@ paths: required: true schema: type: integer + format: int32 example: 1 requestBody: required: true @@ -156,6 +157,7 @@ paths: required: true schema: type: integer + format: int32 responses: '204': description: No Content @@ -181,6 +183,7 @@ paths: required: true schema: type: integer + format: int32 responses: '200': description: Ok @@ -201,7 +204,6 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' - api/v1/vms/{id}/stop: post: summary: Stop a virtual machine @@ -212,6 +214,37 @@ paths: required: true schema: type: integer + format: int32 + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/VirtualMachine' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + api/v1/vms/{id}/reboot: + post: + summary: Reboot a virtual machine + operationId: rebootVm + parameters: + - name: id + in: path + required: true + schema: + type: integer + format: int32 responses: '200': description: Ok @@ -260,6 +293,7 @@ components: properties: id: type: integer + format: int32 example: 1 object: type: string @@ -298,6 +332,7 @@ components: properties: id: type: integer + format: int32 example: 1 object: type: string @@ -316,6 +351,7 @@ components: properties: id: type: integer + format: int32 example: 1 object: type: string @@ -328,6 +364,7 @@ components: properties: id: type: integer + format: int32 example: 1 object: type: string @@ -352,6 +389,7 @@ components: properties: id: type: integer + format: int32 example: 1 object: type: string diff --git a/internal/provider/host_data_source.go b/internal/provider/host_data_source.go index 14e8252..ec16485 100644 --- a/internal/provider/host_data_source.go +++ b/internal/provider/host_data_source.go @@ -89,7 +89,7 @@ func (d *HostDataSource) Read(ctx context.Context, req datasource.ReadRequest, r for _, host := range *response.JSON200.Data { if *host.Name == data.Name.ValueString() { - data.Id = types.StringValue(strconv.Itoa(*host.Id)) + data.Id = types.StringValue(strconv.Itoa(int(*host.Id))) data.Name = types.StringValue(*host.Name) tflog.Trace(ctx, "read host data source") diff --git a/internal/provider/vm_resource.go b/internal/provider/vm_resource.go index 34d1d94..ec20d37 100644 --- a/internal/provider/vm_resource.go +++ b/internal/provider/vm_resource.go @@ -4,18 +4,17 @@ import ( "context" "fmt" "strconv" - "time" "github.com/crunchloop/terraform-provider-crunchloop/internal/client" + "github.com/crunchloop/terraform-provider-crunchloop/internal/services" + "github.com/crunchloop/terraform-provider-crunchloop/internal/utils" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/int32planmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-log/tflog" ) // Ensure provider defined types fully satisfy framework interfaces. @@ -28,7 +27,7 @@ func NewVmResource() resource.Resource { // VmResource defines the resource implementation. type VmResource struct { - client *client.ClientWithResponses + service *services.VmService } // VmResourceModel describes the resource data model. @@ -37,8 +36,8 @@ type VmResourceModel struct { Name types.String `tfsdk:"name"` MemoryMegabytes types.Int32 `tfsdk:"memory_megabytes"` Cores types.Int32 `tfsdk:"cores"` - VmiId types.Int64 `tfsdk:"vmi_id"` - HostId types.Int64 `tfsdk:"host_id"` + VmiId types.Int32 `tfsdk:"vmi_id"` + HostId types.Int32 `tfsdk:"host_id"` RootVolumeSizeGigabytes types.Int32 `tfsdk:"root_volume_size_gigabytes"` UserData types.String `tfsdk:"user_data"` SshKey types.String `tfsdk:"ssh_key"` @@ -76,18 +75,19 @@ func (r *VmResource) Schema(ctx context.Context, req resource.SchemaRequest, res Required: true, MarkdownDescription: "Virtual CPU cores", }, - "vmi_id": schema.Int64Attribute{ + "vmi_id": schema.Int32Attribute{ Required: true, MarkdownDescription: "Identifier of the VMI to use for the Vm", - PlanModifiers: []planmodifier.Int64{ - int64planmodifier.RequiresReplace(), + PlanModifiers: []planmodifier.Int32{ + int32planmodifier.RequiresReplace(), }, }, - "host_id": schema.Int64Attribute{ - Required: true, + "host_id": schema.Int32Attribute{ + Optional: true, + Computed: true, MarkdownDescription: "Identifier of the Host where the Vm will be created", - PlanModifiers: []planmodifier.Int64{ - int64planmodifier.RequiresReplace(), + PlanModifiers: []planmodifier.Int32{ + int32planmodifier.UseStateForUnknown(), }, }, "root_volume_size_gigabytes": schema.Int32Attribute{ @@ -126,21 +126,19 @@ func (r *VmResource) Configure(ctx context.Context, req resource.ConfigureReques if !ok { resp.Diagnostics.AddError( "Unexpected Resource Configure Type", - fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + fmt.Sprintf("Expected *client.ClientWithResponses, got: %T. Please report this issue to the provider developers.", req.ProviderData), ) return } - r.client = client + r.service = services.NewVmService(client) } func (r *VmResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { var data VmResourceModel - // Read Terraform plan data into the model resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) - if resp.Diagnostics.HasError() { return } @@ -149,11 +147,14 @@ func (r *VmResource) Create(ctx context.Context, req resource.CreateRequest, res Name: data.Name.ValueString(), MemoryMegabytes: data.MemoryMegabytes.ValueInt32(), Cores: data.Cores.ValueInt32(), - VmiId: int(data.VmiId.ValueInt64()), - HostId: int(data.HostId.ValueInt64()), + VmiId: data.VmiId.ValueInt32(), RootVolumeSizeGigabytes: data.RootVolumeSizeGigabytes.ValueInt32(), } + if data.HostId.ValueInt32() != 0 { + createOptions.HostId = data.HostId.ValueInt32Pointer() + } + if data.UserData.ValueString() != "" { createOptions.UserData = data.UserData.ValueStringPointer() } @@ -162,84 +163,39 @@ func (r *VmResource) Create(ctx context.Context, req resource.CreateRequest, res createOptions.SshKey = data.SshKey.ValueStringPointer() } - vmResponse, err := r.client.CreateVmWithResponse(ctx, createOptions) - if err != nil { - resp.Diagnostics.AddError( - "API Error", - fmt.Sprintf("Failed to create vm: %s", err), - ) - return - } - - if vmResponse.StatusCode() != 201 { - resp.Diagnostics.AddError( - "API Error", - fmt.Sprintf("Failed to create vm. Response body: %s", vmResponse.Body), - ) - return - } - - // User expects the VM to be running, so wait for it to be running - err = waitForVmStatus(ctx, r.client, *vmResponse.JSON201.Id, "running") + vm, err := r.service.CreateVm(ctx, createOptions) if err != nil { - resp.Diagnostics.AddError( - "API Error", - fmt.Sprintf("Failed while waiting for vm to be running: %s", err), - ) + resp.Diagnostics.AddError("API Error", err.Error()) return } - data.vmModelToStateResource(vmResponse.JSON201) - - // Write logs using the tflog package - // Documentation: https://terraform.io/plugin/log - tflog.Trace(ctx, "created a resource") - - // Save data into Terraform state + data.vmModelToStateResource(vm) resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } func (r *VmResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { var data VmResourceModel - // Read Terraform prior state data into the model resp.Diagnostics.Append(req.State.Get(ctx, &data)...) if resp.Diagnostics.HasError() { return } - // Parse the vm id id, _ := strconv.Atoi(data.Id.ValueString()) - - vm, err := r.client.GetVmWithResponse(ctx, id) + vm, err := r.service.GetVm(ctx, int32(id)) if err != nil { - resp.Diagnostics.AddError( - "API Error", - fmt.Sprintf("Failed to get vm: %s", err), - ) - return - } - - if vm.StatusCode() != 200 { - resp.Diagnostics.AddError( - "API Error", - fmt.Sprintf("Failed to get vm. Response body: %s", vm.Body), - ) + resp.Diagnostics.AddError("API Error", err.Error()) return } - data.vmModelToStateResource(vm.JSON200) - - // Save updated data into Terraform state + data.vmModelToStateResource(vm) resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } func (r *VmResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { var data VmResourceModel - // Read Terraform plan data into the model resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) - if resp.Diagnostics.HasError() { return } @@ -251,90 +207,29 @@ func (r *VmResource) Update(ctx context.Context, req resource.UpdateRequest, res // Parse the vm id id, _ := strconv.Atoi(data.Id.ValueString()) - - vmUpdateResponse, err := r.client.UpdateVmWithResponse(ctx, id, updateOptions) + vm, err := r.service.UpdateVm(ctx, int32(id), updateOptions) if err != nil { - resp.Diagnostics.AddError( - "API Error", - fmt.Sprintf("Failed to update vm: %s", err), - ) + resp.Diagnostics.AddError("API Error", err.Error()) return } - if vmUpdateResponse.StatusCode() != 200 { - resp.Diagnostics.AddError( - "API Error", - fmt.Sprintf("Failed to update vm. Response body: %s", vmUpdateResponse.Body), - ) - return - } - - // Updating the VM can take some time, so wait for it to be running - // before updating the state. - err = waitForVmStatus(ctx, r.client, *vmUpdateResponse.JSON200.Id, "running") - if err != nil { - resp.Diagnostics.AddError( - "API Error", - fmt.Sprintf("Failed while waiting for vm to update: %s", err), - ) - return - } - - vmResponse, err := r.client.GetVmWithResponse(ctx, id) - if err != nil { - resp.Diagnostics.AddError( - "API Error", - fmt.Sprintf("Failed to get vm: %s", err), - ) - return - } - - if vmResponse.StatusCode() != 200 { - resp.Diagnostics.AddError( - "API Error", - fmt.Sprintf("Failed to get vm. Response body: %s", vmResponse.Body), - ) - return - } - - data.vmModelToStateResource(vmResponse.JSON200) - - // Write logs using the tflog package - // Documentation: https://terraform.io/plugin/log - tflog.Trace(ctx, "updated a resource") - - // Save updated data into Terraform state + data.vmModelToStateResource(vm) resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } func (r *VmResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { var data VmResourceModel - // Read Terraform prior state data into the model resp.Diagnostics.Append(req.State.Get(ctx, &data)...) - if resp.Diagnostics.HasError() { return } // Parse the vm id id, _ := strconv.Atoi(data.Id.ValueString()) - - _, err := r.client.DeleteVmWithResponse(ctx, id) + err := r.service.DeleteVm(ctx, int32(id)) if err != nil { - resp.Diagnostics.AddError( - "API Error", - fmt.Sprintf("Failed to delete vm: %s", err), - ) - return - } - - err = waitForVmDeletion(ctx, r.client, id) - if err != nil { - resp.Diagnostics.AddError( - "API Error", - fmt.Sprintf("Failed while waiting for vm to get deleted: %s", err), - ) + resp.Diagnostics.AddError("API Error", err.Error()) return } } @@ -344,43 +239,11 @@ func (r *VmResource) ImportState(ctx context.Context, req resource.ImportStateRe } func (d *VmResourceModel) vmModelToStateResource(vm *client.VirtualMachine) { - d.Id = types.StringValue(strconv.Itoa(*vm.Id)) + d.Id = types.StringValue(strconv.Itoa(int(*vm.Id))) d.Name = types.StringValue(*vm.Name) - d.VmiId = types.Int64Value(int64(*vm.Vmi.Id)) - d.HostId = types.Int64Value(int64(*vm.Host.Id)) - d.MemoryMegabytes = types.Int32Value(bytesToMegabytes(*vm.MemoryBytes)) + d.VmiId = types.Int32Value(*vm.Vmi.Id) + d.HostId = types.Int32Value(*vm.Host.Id) d.Cores = types.Int32Value(*vm.Cores) - d.RootVolumeSizeGigabytes = types.Int32Value(bytesToGigabytes(*vm.RootVolume.SizeBytes)) -} - -func bytesToMegabytes(bytes int64) int32 { - return int32(bytes / 1024 / 1024) -} - -func bytesToGigabytes(bytes int64) int32 { - return int32(bytes / 1024 / 1024 / 1024) -} - -func waitForVmDeletion(ctx context.Context, client *client.ClientWithResponses, id int) error { - timeout := time.After(5 * time.Minute) // 5 minutes timeout - ticker := time.NewTicker(5 * time.Second) // Check every 10 seconds - defer ticker.Stop() - - for { - select { - case <-ctx.Done(): - return ctx.Err() - case <-timeout: - return fmt.Errorf("timeout waiting for VM status to get deleted") - case <-ticker.C: - vmResponse, err := client.GetVmWithResponse(ctx, id) - if err != nil { - return err - } - - if vmResponse.StatusCode() == 404 { - return nil - } - } - } + d.MemoryMegabytes = types.Int32Value(utils.BytesToMegabytes(*vm.MemoryBytes)) + d.RootVolumeSizeGigabytes = types.Int32Value(utils.BytesToGigabytes(*vm.RootVolume.SizeBytes)) } diff --git a/internal/provider/vm_state_resource.go b/internal/provider/vm_state_resource.go index 69044a1..42ee652 100644 --- a/internal/provider/vm_state_resource.go +++ b/internal/provider/vm_state_resource.go @@ -4,9 +4,9 @@ import ( "context" "fmt" "strconv" - "time" "github.com/crunchloop/terraform-provider-crunchloop/internal/client" + "github.com/crunchloop/terraform-provider-crunchloop/internal/services" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" @@ -26,7 +26,7 @@ func NewVmStateResource() resource.Resource { // VmStateResource defines the resource implementation. type VmStateResource struct { - client *client.ClientWithResponses + service *services.VmService } // VmStateResourceModel describes the resource data model. @@ -71,34 +71,30 @@ func (r *VmStateResource) Configure(ctx context.Context, req resource.ConfigureR if !ok { resp.Diagnostics.AddError( "Unexpected Resource Configure Type", - fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + fmt.Sprintf("Expected *client.ClientWithResponses, got: %T. Please report this issue to the provider developers.", req.ProviderData), ) return } - r.client = client + r.service = services.NewVmService(client) } func (r *VmStateResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { var data VmStateResourceModel - // Read Terraform plan data into the model resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) - if resp.Diagnostics.HasError() { return } - // For the purposes of this example code, hardcoding a response value to - // save into the Terraform state. - // data.Id = types.StringValue("example-id") - - // Write logs using the tflog package - // Documentation: https://terraform.io/plugin/log - tflog.Trace(ctx, "created a resource") + vm, err := r.updateVmStatus(ctx, &data) + if err != nil { + resp.Diagnostics.AddError("API Error", err.Error()) + return + } - // Save data into Terraform state + data.vmModelToStateResource(vm) resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } @@ -111,97 +107,39 @@ func (r *VmStateResource) Read(ctx context.Context, req resource.ReadRequest, re return } - // Parse the vm id id, _ := strconv.Atoi(data.VmId.ValueString()) - - vmResponse, err := r.client.GetVmWithResponse(ctx, id) + vm, err := r.service.GetVm(ctx, int32(id)) if err != nil { - resp.Diagnostics.AddError( - "API Error", fmt.Sprintf("Failed to get vm: %s", err), - ) + resp.Diagnostics.AddError("API Error", err.Error()) return } - data.VmId = types.StringValue(strconv.Itoa(*vmResponse.JSON200.Id)) - data.Status = types.StringValue(string(*vmResponse.JSON200.Status)) - - // Save updated data into Terraform state + data.vmModelToStateResource(vm) resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } func (r *VmStateResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { var data VmStateResourceModel - // Read Terraform plan data into the model resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) - if resp.Diagnostics.HasError() { return } - // Parse the vm id - id, _ := strconv.Atoi(data.VmId.ValueString()) - - // Get current status of vm - vmResponse, err := r.client.GetVmWithResponse(ctx, id) + vm, err := r.updateVmStatus(ctx, &data) if err != nil { - resp.Diagnostics.AddError( - "API Error", - fmt.Sprintf("Failed to get vm: %s", err), - ) + resp.Diagnostics.AddError("API Error", err.Error()) return } - if data.Status.String() == string(client.VirtualMachineStatusRunning) && *vmResponse.JSON200.Status == client.VirtualMachineStatusRunning { - tflog.Debug(ctx, fmt.Sprintf("Vm is already running: %s", data.VmId.String())) - return - } - - if data.Status.String() == string(client.VirtualMachineStatusStopped) && *vmResponse.JSON200.Status == client.VirtualMachineStatusStopped { - tflog.Debug(ctx, fmt.Sprintf("Vm is already stopped: %s", data.VmId.String())) - return - } - - switch data.Status.ValueString() { - case "stopped": - _, err = r.client.StopVmWithResponse(ctx, id) - if err != nil { - resp.Diagnostics.AddError( - "API Error", - fmt.Sprintf("Failed to stop vm: %s", err), - ) - return - } - case "running": - _, err = r.client.StartVmWithResponse(ctx, id) - if err != nil { - resp.Diagnostics.AddError( - "API Error", - fmt.Sprintf("Failed to start vm: %s", err), - ) - return - } - } - - err = waitForVmStatus(ctx, r.client, id, client.VirtualMachineStatus(data.Status.ValueString())) - if err != nil { - resp.Diagnostics.AddError( - "API Error", - fmt.Sprintf("Failed while waiting for vm to be in status %s: %s", data.Status.ValueString(), err), - ) - return - } - - // Save updated data into Terraform state + data.vmModelToStateResource(vm) resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } func (r *VmStateResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { var data VmStateResourceModel - // Read Terraform prior state data into the model resp.Diagnostics.Append(req.State.Get(ctx, &data)...) - tflog.Debug(ctx, fmt.Sprintf("Deleting a crunchloop_vm_state resource only stops managing instance state, The vm is left in its current state.: %s", data.VmId.String())) } @@ -209,25 +147,34 @@ func (r *VmStateResource) ImportState(ctx context.Context, req resource.ImportSt resource.ImportStatePassthroughID(ctx, path.Root("vm_id"), req, resp) } -func waitForVmStatus(ctx context.Context, client *client.ClientWithResponses, id int, status client.VirtualMachineStatus) error { - timeout := time.After(5 * time.Minute) // 5 minutes timeout - ticker := time.NewTicker(5 * time.Second) // Check every 10 seconds - defer ticker.Stop() - - for { - select { - case <-ctx.Done(): - return ctx.Err() - case <-timeout: - return fmt.Errorf("timeout waiting for VM status to become '%s'", status) - case <-ticker.C: - vm, err := client.GetVmWithResponse(ctx, id) - if err != nil { - return err - } - if *vm.JSON200.Status == status { - return nil - } - } +func (d *VmStateResourceModel) vmModelToStateResource(vm *client.VirtualMachine) { + d.VmId = types.StringValue(strconv.Itoa(int(*vm.Id))) + d.Status = types.StringValue(string(*vm.Status)) +} + +func (r *VmStateResource) updateVmStatus(ctx context.Context, data *VmStateResourceModel) (*client.VirtualMachine, error) { + id, _ := strconv.Atoi(data.VmId.ValueString()) + vm, err := r.service.GetVm(ctx, int32(id)) + if err != nil { + return nil, err } + + if data.Status.String() == string(client.VirtualMachineStatusRunning) && *vm.Status == client.VirtualMachineStatusRunning { + tflog.Debug(ctx, fmt.Sprintf("Vm is already running: %s", data.VmId.String())) + return vm, nil + } + + if data.Status.String() == string(client.VirtualMachineStatusStopped) && *vm.Status == client.VirtualMachineStatusStopped { + tflog.Debug(ctx, fmt.Sprintf("Vm is already stopped: %s", data.VmId.String())) + return vm, nil + } + + switch data.Status.ValueString() { + case "stopped": + vm, err = r.service.StopVm(ctx, int32(id)) + case "running": + vm, err = r.service.StartVm(ctx, int32(id)) + } + + return vm, err } diff --git a/internal/provider/vmi_data_source.go b/internal/provider/vmi_data_source.go index b994efc..242cb2a 100644 --- a/internal/provider/vmi_data_source.go +++ b/internal/provider/vmi_data_source.go @@ -9,7 +9,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-log/tflog" ) // Ensure provider defined types fully satisfy framework interfaces. @@ -89,14 +88,9 @@ func (d *VmiDataSource) Read(ctx context.Context, req datasource.ReadRequest, re for _, vmi := range *response.JSON200.Data { if *vmi.Name == data.Name.ValueString() { - data.Id = types.StringValue(strconv.Itoa(*vmi.Id)) + data.Id = types.StringValue(strconv.Itoa(int(*vmi.Id))) data.Name = types.StringValue(*vmi.Name) - // Write logs using the tflog package - // Documentation: https://terraform.io/plugin/log - tflog.Trace(ctx, "read Vmi data source") - - // Save data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) return diff --git a/internal/services/vm_service.go b/internal/services/vm_service.go new file mode 100644 index 0000000..5fd80f9 --- /dev/null +++ b/internal/services/vm_service.go @@ -0,0 +1,167 @@ +package services + +import ( + "context" + "fmt" + + "github.com/crunchloop/terraform-provider-crunchloop/internal/client" + "github.com/crunchloop/terraform-provider-crunchloop/internal/utils" +) + +type VmService struct { + client *client.ClientWithResponses +} + +func NewVmService(client *client.ClientWithResponses) *VmService { + return &VmService{ + client: client, + } +} + +func (s *VmService) CreateVm(ctx context.Context, options client.CreateVmJSONRequestBody) (*client.VirtualMachine, error) { + createResponse, err := s.client.CreateVmWithResponse(ctx, options) + if err != nil { + return nil, fmt.Errorf("failed to create. Error: %s", err) + } + + if createResponse.StatusCode() != 201 { + return nil, fmt.Errorf("failed to create vm. Response body: %s", createResponse.Body) + } + + err = utils.WaitForVmStatus(ctx, s.client, *createResponse.JSON201.Id, "running") + if err != nil { + return nil, err + } + + vm, err := s.GetVm(ctx, *createResponse.JSON201.Id) + if err != nil { + return nil, err + } + + return vm, nil +} + +func (s *VmService) GetVm(ctx context.Context, id int32) (*client.VirtualMachine, error) { + response, err := s.client.GetVmWithResponse(ctx, id) + if err != nil { + return nil, fmt.Errorf("failed to create. Error: %s", err) + } + + if response.StatusCode() != 200 { + return nil, fmt.Errorf("failed to get vm. Response body: %s", response.Body) + } + + return response.JSON200, nil +} + +func (s *VmService) DeleteVm(ctx context.Context, id int32) error { + response, err := s.client.DeleteVmWithResponse(ctx, id) + if err != nil { + return fmt.Errorf("failed to delete. Error: %s", err) + } + + if response.StatusCode() != 204 { + return fmt.Errorf("failed to delete vm. Response body: %s", response.Body) + } + + return nil +} + +func (s *VmService) UpdateVm(ctx context.Context, id int32, options client.UpdateVmJSONRequestBody) (*client.VirtualMachine, error) { + vm, err := s.GetVm(ctx, id) + if err != nil { + return nil, err + } + + var wasRunning = *vm.Status == client.VirtualMachineStatusRunning + + // We need to stop the vm if it's running to update it. + if wasRunning { + _, err = s.client.StopVm(ctx, id) + if err != nil { + return nil, fmt.Errorf("failed to stop vm before updating. Error: %s", err) + } + + err = utils.WaitForVmStatus(ctx, s.client, *vm.Id, "stopped") + if err != nil { + return nil, err + } + } + + // We are ready to update the vm now. + updateResponse, err := s.client.UpdateVmWithResponse(ctx, id, options) + if err != nil { + return nil, fmt.Errorf("failed to update. Error: %s", err) + } + + if updateResponse.StatusCode() != 200 { + return nil, fmt.Errorf("failed to update vm. Response body: %s", updateResponse.Body) + } + + // After we issue an update, the vm is going to transition to `updating` state + // and eventually will be back to `stopped` state, we need to wait for that + // state before moving forward. + err = utils.WaitForVmStatus(ctx, s.client, *vm.Id, "stopped") + if err != nil { + return nil, err + } + + // If the vm was running we should turn it on again. + // + if wasRunning { + _, err = s.client.StartVm(ctx, id) + if err != nil { + return nil, fmt.Errorf("failed to start vm after updating. Error: %s", err) + } + + err = utils.WaitForVmStatus(ctx, s.client, *vm.Id, "running") + if err != nil { + return nil, err + } + } + + vm, err = s.GetVm(ctx, id) + if err != nil { + return nil, err + } + + return vm, nil +} + +func (s *VmService) StopVm(ctx context.Context, id int32) (*client.VirtualMachine, error) { + _, err := s.client.StopVmWithResponse(ctx, id) + if err != nil { + return nil, fmt.Errorf("failed to stop vm: %s", err) + } + + err = utils.WaitForVmStatus(ctx, s.client, id, "stopped") + if err != nil { + return nil, fmt.Errorf("failed while waiting for vm to be stopped: %s", err) + } + + vm, err := s.GetVm(ctx, id) + if err != nil { + return nil, err + } + + return vm, nil +} + +func (s *VmService) StartVm(ctx context.Context, id int32) (*client.VirtualMachine, error) { + _, err := s.client.StartVmWithResponse(ctx, id) + if err != nil { + return nil, fmt.Errorf("failed to start vm: %s", err) + } + + err = utils.WaitForVmStatus(ctx, s.client, id, "running") + if err != nil { + return nil, fmt.Errorf("failed while waiting for vm to be running: %s", err) + } + + vm, err := s.GetVm(ctx, id) + if err != nil { + return nil, err + } + + return vm, nil +} diff --git a/internal/utils/bytes.go b/internal/utils/bytes.go new file mode 100644 index 0000000..8cac0a9 --- /dev/null +++ b/internal/utils/bytes.go @@ -0,0 +1,9 @@ +package utils + +func BytesToMegabytes(bytes int64) int32 { + return int32(bytes / 1024 / 1024) +} + +func BytesToGigabytes(bytes int64) int32 { + return int32(bytes / 1024 / 1024 / 1024) +} diff --git a/internal/utils/wait.go b/internal/utils/wait.go new file mode 100644 index 0000000..a6e4599 --- /dev/null +++ b/internal/utils/wait.go @@ -0,0 +1,56 @@ +package utils + +import ( + "context" + "fmt" + "time" + + "github.com/crunchloop/terraform-provider-crunchloop/internal/client" +) + +func WaitForVmStatus(ctx context.Context, client *client.ClientWithResponses, id int32, status client.VirtualMachineStatus) error { + timeout := time.After(5 * time.Minute) // 5 minutes timeout + ticker := time.NewTicker(5 * time.Second) // Check every 10 seconds + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return ctx.Err() + case <-timeout: + return fmt.Errorf("timeout waiting for VM status to become '%s'", status) + case <-ticker.C: + vm, err := client.GetVmWithResponse(ctx, id) + if err != nil { + return err + } + if *vm.JSON200.Status == status { + return nil + } + } + } +} + +func WaitForVmDeletion(ctx context.Context, client *client.ClientWithResponses, id int32) error { + timeout := time.After(5 * time.Minute) // 5 minutes timeout + ticker := time.NewTicker(5 * time.Second) // Check every 10 seconds + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return ctx.Err() + case <-timeout: + return fmt.Errorf("timeout waiting for VM status to get deleted") + case <-ticker.C: + vmResponse, err := client.GetVmWithResponse(ctx, id) + if err != nil { + return err + } + + if vmResponse.StatusCode() == 404 { + return nil + } + } + } +}