package civogo import ( "bytes" "encoding/json" "fmt" "strings" "time" "github.com/civo/civogo/utils" ) // Instance represents a virtual server within Civo's infrastructure type Instance struct { ID string `json:"id,omitempty"` OpenstackServerID string `json:"openstack_server_id,omitempty"` Hostname string `json:"hostname,omitempty"` ReverseDNS string `json:"reverse_dns,omitempty"` Size string `json:"size,omitempty"` Region string `json:"region,omitempty"` NetworkID string `json:"network_id,omitempty"` PrivateIP string `json:"private_ip,omitempty"` PublicIP string `json:"public_ip,omitempty"` IPv6 string `json:"ipv6,omitempty"` PseudoIP string `json:"pseudo_ip,omitempty"` TemplateID string `json:"template_id,omitempty"` SourceType string `json:"source_type,omitempty"` SourceID string `json:"source_id,omitempty"` SnapshotID string `json:"snapshot_id,omitempty"` InitialUser string `json:"initial_user,omitempty"` InitialPassword string `json:"initial_password,omitempty"` SSHKey string `json:"ssh_key,omitempty"` SSHKeyID string `json:"ssh_key_id,omitempty"` Status string `json:"status,omitempty"` Notes string `json:"notes,omitempty"` FirewallID string `json:"firewall_id,omitempty"` Tags []string `json:"tags,omitempty"` CivostatsdToken string `json:"civostatsd_token,omitempty"` CivostatsdStats string `json:"civostatsd_stats,omitempty"` CivostatsdStatsPerMinute []string `json:"civostatsd_stats_per_minute,omitempty"` CivostatsdStatsPerHour []string `json:"civostatsd_stats_per_hour,omitempty"` OpenstackImageID string `json:"openstack_image_id,omitempty"` RescuePassword string `json:"rescue_password,omitempty"` VolumeBacked bool `json:"volume_backed,omitempty"` CPUCores int `json:"cpu_cores,omitempty"` RAMMegabytes int `json:"ram_mb,omitempty"` DiskGigabytes int `json:"disk_gb,omitempty"` Script string `json:"script,omitempty"` CreatedAt time.Time `json:"created_at,omitempty"` ReservedIPID string `json:"reserved_ip_id,omitempty"` ReservedIPName string `json:"reserved_ip_name,omitempty"` ReservedIP string `json:"reserved_ip,omitempty"` Subnets []Subnet `json:"subnets,omitempty"` } //"cpu_cores":1,"ram_mb":2048,"disk_gb":25 // InstanceConsole represents a link to a webconsole for an instances type InstanceConsole struct { URL string `json:"url"` } // PaginatedInstanceList returns a paginated list of Instance object type PaginatedInstanceList struct { Page int `json:"page"` PerPage int `json:"per_page"` Pages int `json:"pages"` Items []Instance `json:"items"` } // InstanceConfig describes the parameters for a new instance // none of the fields are mandatory and will be automatically // set with default values type InstanceConfig struct { Count int `json:"count"` Hostname string `json:"hostname"` ReverseDNS string `json:"reverse_dns"` Size string `json:"size"` Region string `json:"region"` PublicIPRequired string `json:"public_ip"` NetworkID string `json:"network_id"` TemplateID string `json:"template_id"` SourceType string `json:"source_type"` SourceID string `json:"source_id"` SnapshotID string `json:"snapshot_id"` Subnets []string `json:"subnets,omitempty"` InitialUser string `json:"initial_user"` SSHKeyID string `json:"ssh_key_id"` Script string `json:"script"` Tags []string `json:"-"` TagsList string `json:"tags"` FirewallID string `json:"firewall_id"` } // ListInstances returns a page of Instances owned by the calling API account func (c *Client) ListInstances(page int, perPage int) (*PaginatedInstanceList, error) { url := "/v2/instances" if page != 0 && perPage != 0 { url = url + fmt.Sprintf("?page=%d&per_page=%d", page, perPage) } resp, err := c.SendGetRequest(url) if err != nil { return nil, decodeError(err) } PaginatedInstances := PaginatedInstanceList{} err = json.NewDecoder(bytes.NewReader(resp)).Decode(&PaginatedInstances) return &PaginatedInstances, err } // ListAllInstances returns all (well, upto 99,999,999 instances) Instances owned by the calling API account func (c *Client) ListAllInstances() ([]Instance, error) { instances, err := c.ListInstances(1, 99999999) if err != nil { return []Instance{}, decodeError(err) } return instances.Items, nil } // FindInstance finds a instance by either part of the ID or part of the hostname func (c *Client) FindInstance(search string) (*Instance, error) { instances, err := c.ListAllInstances() if err != nil { return nil, decodeError(err) } exactMatch := false partialMatchesCount := 0 result := Instance{} for _, value := range instances { if value.Hostname == search || value.ID == search { exactMatch = true result = value } else if strings.Contains(value.Hostname, search) || strings.Contains(value.ID, search) { if !exactMatch { result = value partialMatchesCount++ } } } if exactMatch || partialMatchesCount == 1 { return &result, nil } else if partialMatchesCount > 1 { err := fmt.Errorf("unable to find %s because there were multiple matches", search) return nil, MultipleMatchesError.wrap(err) } else { err := fmt.Errorf("unable to find %s, zero matches", search) return nil, ZeroMatchesError.wrap(err) } } // GetInstance returns a single Instance by its full ID func (c *Client) GetInstance(id string) (*Instance, error) { resp, err := c.SendGetRequest("/v2/instances/" + id) if err != nil { return nil, decodeError(err) } instance := Instance{} err = json.NewDecoder(bytes.NewReader(resp)).Decode(&instance) return &instance, err } // NewInstanceConfig returns an initialized config for a new instance func (c *Client) NewInstanceConfig() (*InstanceConfig, error) { network, err := c.GetDefaultNetwork() if err != nil { return nil, decodeError(err) } diskimage, err := c.GetDiskImageByName("ubuntu-focal") if err != nil { return nil, decodeError(err) } return &InstanceConfig{ Count: 1, Hostname: utils.RandomName(), ReverseDNS: "", Size: "g3.medium", Region: c.Region, PublicIPRequired: "true", NetworkID: network.ID, TemplateID: diskimage.ID, SnapshotID: "", InitialUser: "civo", SSHKeyID: "", Script: "", Tags: []string{""}, FirewallID: "", }, nil } // CreateInstance creates a new instance in the account func (c *Client) CreateInstance(config *InstanceConfig) (*Instance, error) { config.TagsList = strings.Join(config.Tags, " ") body, err := c.SendPostRequest("/v2/instances", config) if err != nil { return nil, decodeError(err) } var instance Instance if err := json.NewDecoder(bytes.NewReader(body)).Decode(&instance); err != nil { return nil, err } return &instance, nil } // SetInstanceTags sets the tags for the specified instance func (c *Client) SetInstanceTags(i *Instance, tags string) (*SimpleResponse, error) { resp, err := c.SendPutRequest(fmt.Sprintf("/v2/instances/%s/tags", i.ID), map[string]string{ "tags": tags, "region": c.Region, }) if err != nil { return nil, decodeError(err) } response, err := c.DecodeSimpleResponse(resp) return response, err } // UpdateInstance updates an Instance's hostname, reverse DNS or notes func (c *Client) UpdateInstance(i *Instance) (*SimpleResponse, error) { params := map[string]interface{}{ "hostname": i.Hostname, "reverse_dns": i.ReverseDNS, "notes": i.Notes, "region": c.Region, "public_ip": i.PublicIP, "subnets": i.Subnets, } if i.Notes == "" { params["notes_delete"] = "true" } resp, err := c.SendPutRequest(fmt.Sprintf("/v2/instances/%s", i.ID), params) if err != nil { return nil, decodeError(err) } response, err := c.DecodeSimpleResponse(resp) return response, err } // DeleteInstance deletes an instance and frees its resources func (c *Client) DeleteInstance(id string) (*SimpleResponse, error) { resp, err := c.SendDeleteRequest("/v2/instances/" + id) if err != nil { return nil, decodeError(err) } response, err := c.DecodeSimpleResponse(resp) return response, err } // RebootInstance reboots an instance (short version of HardRebootInstance) func (c *Client) RebootInstance(id string) (*SimpleResponse, error) { return c.HardRebootInstance(id) } // HardRebootInstance harshly reboots an instance (like shutting the power off and booting it again) func (c *Client) HardRebootInstance(id string) (*SimpleResponse, error) { resp, err := c.SendPostRequest(fmt.Sprintf("/v2/instances/%s/hard_reboots", id), map[string]string{ "region": c.Region, }) if err != nil { return nil, decodeError(err) } response, err := c.DecodeSimpleResponse(resp) return response, err } // SoftRebootInstance requests the VM to shut down nicely func (c *Client) SoftRebootInstance(id string) (*SimpleResponse, error) { resp, err := c.SendPostRequest(fmt.Sprintf("/v2/instances/%s/soft_reboots", id), map[string]string{ "region": c.Region, }) if err != nil { return nil, decodeError(err) } response, err := c.DecodeSimpleResponse(resp) return response, err } // StopInstance shuts the power down to the instance func (c *Client) StopInstance(id string) (*SimpleResponse, error) { resp, err := c.SendPutRequest(fmt.Sprintf("/v2/instances/%s/stop", id), map[string]string{ "region": c.Region, }) if err != nil { return nil, decodeError(err) } response, err := c.DecodeSimpleResponse(resp) return response, err } // StartInstance starts the instance booting from the shutdown state func (c *Client) StartInstance(id string) (*SimpleResponse, error) { resp, err := c.SendPutRequest(fmt.Sprintf("/v2/instances/%s/start", id), map[string]string{ "region": c.Region, }) if err != nil { return nil, decodeError(err) } response, err := c.DecodeSimpleResponse(resp) return response, err } // GetInstanceConsoleURL gets the web URL for an instance's console func (c *Client) GetInstanceConsoleURL(id string) (string, error) { resp, err := c.SendGetRequest(fmt.Sprintf("/v2/instances/%s/console", id)) if err != nil { return "", decodeError(err) } console := InstanceConsole{} err = json.NewDecoder(bytes.NewReader(resp)).Decode(&console) return console.URL, err } // UpgradeInstance resizes the instance up to the new specification // it's not possible to resize the instance to a smaller size func (c *Client) UpgradeInstance(id, newSize string) (*SimpleResponse, error) { resp, err := c.SendPutRequest(fmt.Sprintf("/v2/instances/%s/resize", id), map[string]string{ "size": newSize, "region": c.Region, }) if err != nil { return nil, decodeError(err) } response, err := c.DecodeSimpleResponse(resp) return response, err } // MovePublicIPToInstance moves a public IP to the specified instance func (c *Client) MovePublicIPToInstance(id, ipAddress string) (*SimpleResponse, error) { resp, err := c.SendPutRequest(fmt.Sprintf("/v2/instances/%s/ip/%s", id, ipAddress), "") if err != nil { return nil, decodeError(err) } response, err := c.DecodeSimpleResponse(resp) return response, err } // SetInstanceFirewall changes the current firewall for an instance func (c *Client) SetInstanceFirewall(id, firewallID string) (*SimpleResponse, error) { resp, err := c.SendPutRequest(fmt.Sprintf("/v2/instances/%s/firewall", id), map[string]string{ "firewall_id": firewallID, "region": c.Region, }) if err != nil { return nil, decodeError(err) } response, err := c.DecodeSimpleResponse(resp) return response, err }