diff --git a/README.md b/README.md index 856a70f..ed49761 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ SYNOLOGY_ADDRESS=http://aaa.bbb.ccc.dddd:5000 SYNOLOGY_USERNAME=test_user SYNOLO terraform { required_providers { synology = { - version = "0.1" + version = "0.2.0" source = "github.com/arnouthoebreckx/synology" } } @@ -85,7 +85,7 @@ Example: terraform { required_providers { synology = { - version = "0.1" + version = "0.2.0" source = "github.com/arnouthoebreckx/synology" } } diff --git a/client/client.go b/client/client.go index 0d5204c..96d18a3 100644 --- a/client/client.go +++ b/client/client.go @@ -17,6 +17,9 @@ type SynologyClient interface { UpdateGuest(name string, new_name string) error DeleteGuest(name string) error PowerGuest(name string, state bool) error + ReadStorageGuest() (StorageResponse, error) + ReadNetworkGuest() (NetworkResponse, error) + ReadHostGuest() (HostResponse, error) } type synologyClient struct { @@ -114,6 +117,18 @@ func (client synologyClient) PowerGuest(name string, state bool) error { return err } +func (client synologyClient) ReadStorageGuest() (StorageResponse, error) { + return ListStorages(client.apiInfo, client.host, client.sid) +} + +func (client synologyClient) ReadNetworkGuest() (NetworkResponse, error) { + return ListNetworks(client.apiInfo, client.host, client.sid) +} + +func (client synologyClient) ReadHostGuest() (HostResponse, error) { + return ListHosts(client.apiInfo, client.host, client.sid) +} + func NewClient() SynologyClient { return &synologyClient{} } diff --git a/client/virtualmachinemanager.go b/client/virtualmachinemanager.go index 155f310..8c3d91f 100644 --- a/client/virtualmachinemanager.go +++ b/client/virtualmachinemanager.go @@ -3,13 +3,50 @@ package client import ( "encoding/json" "fmt" - "log" "strconv" ) -type readReponse struct { - Data Guest `json:"data"` - Success bool `json:"success"` +type readResponse struct { + Data interface{} `json:"data"` + Success bool `json:"success"` +} + +type NetworkResponse struct { + Networks []Network `json:"networks"` +} + +type Network struct { + NetworkID string `json:"network_id"` + NetworkName string `json:"network_name"` +} + +type StorageResponse struct { + Storages []Storage `json:"storages"` +} + +type Storage struct { + HostID string `json:"host_id"` + HostName string `json:"host_name"` + Size int `json:"size"` + Status string `json:"status"` + StorageID string `json:"storage_id"` + StorageName string `json:"storage_name"` + Used int `json:"used"` + VolumePath string `json:"volume_path"` +} + +type HostResponse struct { + Hosts []Host `json:"hosts"` +} + +type Host struct { + FreeCpuCore int `json:"free_cpu_core"` + FreeRamSize int `json:"free_ram_size"` + HostID string `json:"host_id"` + HostName string `json:"host_name"` + Status string `json:"status"` + TotalCpuCore int `json:"total_cpu_core"` + TotalRamSize int `json:"total_ram_size"` } type Guest struct { @@ -59,7 +96,6 @@ func CreateGuest(apiInfo map[string]InfoData, host string, sid string, name stri apiName := "SYNO.Virtualization.API.Guest" info := apiInfo[apiName] - log.Println(vnics) vnicList := createValidRequestMap(vnics, []string{"mac", "network_id", "network_name"}) vdiskList := createValidRequestMap(vdisks, []string{"create_type", "vdisk_size", "image_id", "image_name"}) @@ -88,8 +124,6 @@ func CreateGuest(apiInfo map[string]InfoData, host string, sid string, name stri return CreateGuestResponse{}, err } - log.Println("Create VMM Guest body" + string(body)) - var CreateGuestResponse CreateGuestResponse json.Unmarshal(body, &CreateGuestResponse) @@ -115,14 +149,29 @@ func ReadGuest(apiInfo map[string]InfoData, host string, sid string, name string return Guest{}, err } - response := readReponse{} + response := readResponse{} err = json.Unmarshal(body, &response) if err != nil { - log.Println(err.Error()) return Guest{}, err } - return response.Data, nil + guestData, ok := response.Data.(map[string]interface{}) + if !ok { + return Guest{}, fmt.Errorf("invalid guest data") + } + + guestJson, err := json.Marshal(guestData) + if err != nil { + return Guest{}, err + } + + guest := Guest{} + err = json.Unmarshal(guestJson, &guest) + if err != nil { + return Guest{}, err + } + + return guest, nil } func SetGuest(apiInfo map[string]InfoData, host string, sid string, oldName string, name string, autorun int, description string, vcpuNum int, vramSize int) ([]byte, error) { @@ -226,6 +275,132 @@ func PowerGuest(apiInfo map[string]InfoData, host string, sid string, name strin return statusCode, nil } +func ListNetworks(apiInfo map[string]InfoData, host string, sid string) (NetworkResponse, error) { + apiName := "SYNO.Virtualization.API.Network" + info := apiInfo[apiName] + + queryString := make(map[string]string) + queryString["_sid"] = sid + queryString["api"] = apiName + queryString["version"] = strconv.Itoa(info.MaxVersion) + queryString["method"] = "list" + + wsUrl := host + "/webapi/entry.cgi" + + _, body, err := HttpCall(wsUrl, queryString) + if err != nil { + return NetworkResponse{}, err + } + + response := readResponse{} + err = json.Unmarshal(body, &response) + if err != nil { + return NetworkResponse{}, err + } + + storageData, ok := response.Data.(map[string]interface{}) + if !ok { + return NetworkResponse{}, fmt.Errorf("invalid NetworkResponse data") + } + + storageJson, err := json.Marshal(storageData) + if err != nil { + return NetworkResponse{}, err + } + + networkResponse := NetworkResponse{} + err = json.Unmarshal(storageJson, &networkResponse) + if err != nil { + return NetworkResponse{}, err + } + + return networkResponse, nil +} + +func ListStorages(apiInfo map[string]InfoData, host string, sid string) (StorageResponse, error) { + apiName := "SYNO.Virtualization.API.Storage" + info := apiInfo[apiName] + + queryString := make(map[string]string) + queryString["_sid"] = sid + queryString["api"] = apiName + queryString["version"] = strconv.Itoa(info.MaxVersion) + queryString["method"] = "list" + + wsUrl := host + "/webapi/entry.cgi" + + _, body, err := HttpCall(wsUrl, queryString) + if err != nil { + return StorageResponse{}, err + } + + response := readResponse{} + err = json.Unmarshal(body, &response) + if err != nil { + return StorageResponse{}, err + } + + storageData, ok := response.Data.(map[string]interface{}) + if !ok { + return StorageResponse{}, fmt.Errorf("invalid StorageResponse data") + } + + storageJson, err := json.Marshal(storageData) + if err != nil { + return StorageResponse{}, err + } + + storageResponse := StorageResponse{} + err = json.Unmarshal(storageJson, &storageResponse) + if err != nil { + return StorageResponse{}, err + } + + return storageResponse, nil +} + +func ListHosts(apiInfo map[string]InfoData, host string, sid string) (HostResponse, error) { + apiName := "SYNO.Virtualization.API.Host" + info := apiInfo[apiName] + + queryString := make(map[string]string) + queryString["_sid"] = sid + queryString["api"] = apiName + queryString["version"] = strconv.Itoa(info.MaxVersion) + queryString["method"] = "list" + + wsUrl := host + "/webapi/entry.cgi" + + _, body, err := HttpCall(wsUrl, queryString) + if err != nil { + return HostResponse{}, err + } + + response := readResponse{} + err = json.Unmarshal(body, &response) + if err != nil { + return HostResponse{}, err + } + + storageData, ok := response.Data.(map[string]interface{}) + if !ok { + return HostResponse{}, fmt.Errorf("invalid HostResponse data") + } + + storageJson, err := json.Marshal(storageData) + if err != nil { + return HostResponse{}, err + } + + hostResponse := HostResponse{} + err = json.Unmarshal(storageJson, &hostResponse) + if err != nil { + return HostResponse{}, err + } + + return hostResponse, nil +} + func (g Guest) String() string { str := fmt.Sprintf("Guest:\n\tGuestName: %s\n\tGuestId: %s\n\tAutorun: %d\n\tDescription: %s\n\tStatus: %s\n\tStorageName: %s\n\tStorageId: %s\n\tVcpuNum: %d\n\tVramSize: %d\n\tVdisks: [\n", g.GuestName, g.GuestId, g.Autorun, g.Description, g.Status, g.StorageName, g.StorageId, g.VcpuNum, g.VramSize) for _, vdisk := range g.Vdisks { @@ -247,6 +422,19 @@ func (vdisk VDisk) String() string { return fmt.Sprintf("VDisk:\n\tController: %d\n\tUnmap: %t\n\tVdiskId: %s\n\tVdiskSize: %d", vdisk.Controller, vdisk.Unmap, vdisk.VdiskId, vdisk.VdiskSize) } +func (sr StorageResponse) String() string { + str := "Storages:\n\t" + for _, storage := range sr.Storages { + str += fmt.Sprintf("\t\t%s\n", storage.String()) + } + return str +} + +func (s Storage) String() string { + return fmt.Sprintf("HostID: %s\nHostName: %s\nSize: %d\nStatus: %s\nStorageID: %s\nStorageName: %s\nUsed: %d\nVolumePath: %s\n", + s.HostID, s.HostName, s.Size, s.Status, s.StorageID, s.StorageName, s.Used, s.VolumePath) +} + func createValidRequestMap(input []interface{}, allowedKeys []string) []map[string]interface{} { var output []map[string]interface{} diff --git a/docs/data-sources/vmm_guest.md b/docs/data-sources/vmm_guest.md index e923a82..19992dd 100644 --- a/docs/data-sources/vmm_guest.md +++ b/docs/data-sources/vmm_guest.md @@ -16,7 +16,7 @@ description: |- terraform { required_providers { synology = { - version = "0.1" + version = "0.2.0" source = "github.com/arnouthoebreckx/synology" } } diff --git a/docs/resources/file.md b/docs/resources/file.md index d4aa154..47a6a13 100644 --- a/docs/resources/file.md +++ b/docs/resources/file.md @@ -16,7 +16,7 @@ description: |- terraform { required_providers { synology = { - version = "0.1" + version = "0.2.0" source = "github.com/arnouthoebreckx/synology" } } diff --git a/docs/resources/folder.md b/docs/resources/folder.md index 55e41a2..d59269f 100644 --- a/docs/resources/folder.md +++ b/docs/resources/folder.md @@ -16,7 +16,7 @@ description: |- terraform { required_providers { synology = { - version = "0.1" + version = "0.2.0" source = "github.com/arnouthoebreckx/synology" } } diff --git a/docs/resources/vmm_guest.md b/docs/resources/vmm_guest.md index 0a0e1fc..1ad2847 100644 --- a/docs/resources/vmm_guest.md +++ b/docs/resources/vmm_guest.md @@ -16,7 +16,7 @@ description: |- terraform { required_providers { synology = { - version = "0.1" + version = "0.2.0" source = "github.com/arnouthoebreckx/synology" } } diff --git a/examples/data-sources/synology_vmm_guest/data-source.tf b/examples/data-sources/synology_vmm_guest/data-source.tf index d6207fc..040d934 100644 --- a/examples/data-sources/synology_vmm_guest/data-source.tf +++ b/examples/data-sources/synology_vmm_guest/data-source.tf @@ -1,7 +1,7 @@ terraform { required_providers { synology = { - version = "0.1" + version = "0.2.0" source = "github.com/arnouthoebreckx/synology" } } @@ -15,7 +15,7 @@ provider "synology" { } resource "synology_vmm_guest" "my-guest" { - autorun = 2 + autorun = 2 guest_name = "terraform-guest" description = "Virtual machine setup with terraform" storage_name = "synology - VM Storage 1" diff --git a/examples/data-sources/synology_vmm_guest_host/data-source.tf b/examples/data-sources/synology_vmm_guest_host/data-source.tf new file mode 100644 index 0000000..147b51e --- /dev/null +++ b/examples/data-sources/synology_vmm_guest_host/data-source.tf @@ -0,0 +1,18 @@ +terraform { + required_providers { + synology = { + version = "0.2.0" + source = "github.com/arnouthoebreckx/synology" + } + } +} + +provider "synology" { + url = "" + username = "" + password = "" + # these variables can be set as env vars in SYNOLOGY_ADDRESS SYNOLOGY_USERNAME and SYNOLOGY_PASSWORD +} + +data "synology_vmm_guest_host" "my-guest" { +} diff --git a/examples/data-sources/synology_vmm_guest_network/data-source.tf b/examples/data-sources/synology_vmm_guest_network/data-source.tf new file mode 100644 index 0000000..cb8d120 --- /dev/null +++ b/examples/data-sources/synology_vmm_guest_network/data-source.tf @@ -0,0 +1,18 @@ +terraform { + required_providers { + synology = { + version = "0.2.0" + source = "github.com/arnouthoebreckx/synology" + } + } +} + +provider "synology" { + url = "" + username = "" + password = "" + # these variables can be set as env vars in SYNOLOGY_ADDRESS SYNOLOGY_USERNAME and SYNOLOGY_PASSWORD +} + +data "synology_vmm_guest_network" "my-guest" { +} \ No newline at end of file diff --git a/examples/data-sources/synology_vmm_guest_storage/data-source.tf b/examples/data-sources/synology_vmm_guest_storage/data-source.tf new file mode 100644 index 0000000..0108024 --- /dev/null +++ b/examples/data-sources/synology_vmm_guest_storage/data-source.tf @@ -0,0 +1,18 @@ +terraform { + required_providers { + synology = { + version = "0.2.0" + source = "github.com/arnouthoebreckx/synology" + } + } +} + +provider "synology" { + url = "" + username = "" + password = "" + # these variables can be set as env vars in SYNOLOGY_ADDRESS SYNOLOGY_USERNAME and SYNOLOGY_PASSWORD +} + +data "synology_vmm_guest_storage" "my-guest" { +} \ No newline at end of file diff --git a/examples/resources/synology_file/resource.tf b/examples/resources/synology_file/resource.tf index 2f03918..1154b1d 100644 --- a/examples/resources/synology_file/resource.tf +++ b/examples/resources/synology_file/resource.tf @@ -1,7 +1,7 @@ terraform { required_providers { synology = { - version = "0.1" + version = "0.2.0" source = "github.com/arnouthoebreckx/synology" } } diff --git a/examples/resources/synology_folder/resource.tf b/examples/resources/synology_folder/resource.tf index 04cebd2..40a0d4a 100644 --- a/examples/resources/synology_folder/resource.tf +++ b/examples/resources/synology_folder/resource.tf @@ -1,7 +1,7 @@ terraform { required_providers { synology = { - version = "0.1" + version = "0.2.0" source = "github.com/arnouthoebreckx/synology" } } diff --git a/examples/resources/synology_vmm_guest/resource.tf b/examples/resources/synology_vmm_guest/resource.tf index 31d205f..e0c92b5 100644 --- a/examples/resources/synology_vmm_guest/resource.tf +++ b/examples/resources/synology_vmm_guest/resource.tf @@ -1,7 +1,7 @@ terraform { required_providers { synology = { - version = "0.1" + version = "0.2.0" source = "github.com/arnouthoebreckx/synology" } } @@ -15,7 +15,7 @@ provider "synology" { } resource "synology_vmm_guest" "my-guest" { - autorun = 2 + autorun = 2 poweron = true guest_name = "terraform-guest" description = "Virtual machine setup with terraform" diff --git a/provider/data_source_vmm_host.go b/provider/data_source_vmm_host.go new file mode 100644 index 0000000..ba7501e --- /dev/null +++ b/provider/data_source_vmm_host.go @@ -0,0 +1,89 @@ +package provider + +import ( + "context" + "github.com/arnouthoebreckx/terraform-provider-synology/client" + "strconv" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func datasourceHostItem() *schema.Resource { + return &schema.Resource{ + ReadContext: dataSourceHostItemRead, + Schema: map[string]*schema.Schema{ + "hosts": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "free_cpu_core": { + Type: schema.TypeInt, + Computed: true, + }, + "free_ram_size": { + Type: schema.TypeInt, + Computed: true, + }, + "host_id": { + Type: schema.TypeString, + Computed: true, + }, + "host_name": { + Type: schema.TypeString, + Computed: true, + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, + "total_cpu_core": { + Type: schema.TypeInt, + Computed: true, + }, + "total_ram_size": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func dataSourceHostItemRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + var diags diag.Diagnostics + + client := m.(client.SynologyClient) + service := HostGuestService{synologyClient: client} + + hostResponse, err := service.Read() + if err != nil { + return diag.FromErr(err) + } + + var hosts []map[string]interface{} + for _, h := range hostResponse.Hosts { + m := map[string]interface{}{ + "free_cpu_core": h.FreeCpuCore, + "free_ram_size": h.FreeRamSize, + "host_id": h.HostID, + "host_name": h.HostName, + "status": h.Status, + "total_cpu_core": h.TotalCpuCore, + "total_ram_size": h.TotalRamSize, + } + hosts = append(hosts, m) + } + + if err := d.Set("hosts", hosts); err != nil { + return diag.FromErr(err) + } + + d.SetId(strconv.FormatInt(time.Now().Unix(), 10)) + + return diags +} diff --git a/provider/data_source_vmm_network.go b/provider/data_source_vmm_network.go new file mode 100644 index 0000000..a49fa12 --- /dev/null +++ b/provider/data_source_vmm_network.go @@ -0,0 +1,64 @@ +package provider + +import ( + "context" + "github.com/arnouthoebreckx/terraform-provider-synology/client" + "strconv" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func datasourceNetworkItem() *schema.Resource { + return &schema.Resource{ + ReadContext: dataSourceNetworkItemRead, + Schema: map[string]*schema.Schema{ + "networks": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "network_id": { + Type: schema.TypeString, + Computed: true, + }, + "network_name": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func dataSourceNetworkItemRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + var diags diag.Diagnostics + + client := m.(client.SynologyClient) + service := NetworkGuestService{synologyClient: client} + + networkResponse, err := service.Read() + if err != nil { + return diag.FromErr(err) + } + + var networks []map[string]interface{} + for _, n := range networkResponse.Networks { + network := map[string]interface{}{ + "network_id": n.NetworkID, + "network_name": n.NetworkName, + } + networks = append(networks, network) + } + + if err := d.Set("networks", networks); err != nil { + return diag.FromErr(err) + } + + d.SetId(strconv.FormatInt(time.Now().Unix(), 10)) + + return diags +} diff --git a/provider/data_source_vmm_storage.go b/provider/data_source_vmm_storage.go new file mode 100644 index 0000000..eeec9fe --- /dev/null +++ b/provider/data_source_vmm_storage.go @@ -0,0 +1,94 @@ +package provider + +import ( + "context" + "github.com/arnouthoebreckx/terraform-provider-synology/client" + "strconv" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func datasourceStorageItem() *schema.Resource { + return &schema.Resource{ + ReadContext: dataSourceStorageItemRead, + Schema: map[string]*schema.Schema{ + "storages": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "host_id": { + Type: schema.TypeString, + Computed: true, + }, + "host_name": { + Type: schema.TypeString, + Computed: true, + }, + "size": { + Type: schema.TypeInt, + Computed: true, + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, + "storage_id": { + Type: schema.TypeString, + Computed: true, + }, + "storage_name": { + Type: schema.TypeString, + Computed: true, + }, + "used": { + Type: schema.TypeInt, + Computed: true, + }, + "volume_path": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func dataSourceStorageItemRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + var diags diag.Diagnostics + + client := m.(client.SynologyClient) + service := StorageGuestService{synologyClient: client} + + storageResponse, err := service.Read() + if err != nil { + return diag.FromErr(err) + } + + var storages []map[string]interface{} + for _, s := range storageResponse.Storages { + m := map[string]interface{}{ + "host_id": s.HostID, + "host_name": s.HostName, + "size": s.Size, + "status": s.Status, + "storage_id": s.StorageID, + "storage_name": s.StorageName, + "used": s.Used, + "volume_path": s.VolumePath, + } + storages = append(storages, m) + } + + if err := d.Set("storages", storages); err != nil { + return diag.FromErr(err) + } + + d.SetId(strconv.FormatInt(time.Now().Unix(), 10)) + + return diags +} diff --git a/provider/provider.go b/provider/provider.go index 4c3b976..e7673dd 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -29,7 +29,10 @@ func Provider() *schema.Provider { }, }, DataSourcesMap: map[string]*schema.Resource{ - "synology_vmm_guest": dataSourceGuestItem(), + "synology_vmm_guest": dataSourceGuestItem(), + "synology_vmm_guest_storage": datasourceStorageItem(), + "synology_vmm_guest_network": datasourceNetworkItem(), + "synology_vmm_guest_host": datasourceHostItem(), }, ResourcesMap: map[string]*schema.Resource{ "synology_file": fileItem(), diff --git a/provider/service.go b/provider/service.go index 9b0023f..fe0c21d 100644 --- a/provider/service.go +++ b/provider/service.go @@ -94,3 +94,30 @@ func (service GuestService) Power(name string, state bool) error { err := service.synologyClient.PowerGuest(name, state) return err } + +type StorageGuestService struct { + synologyClient client.SynologyClient +} + +func (service StorageGuestService) Read() (client.StorageResponse, error) { + content, err := service.synologyClient.ReadStorageGuest() + return content, err +} + +type NetworkGuestService struct { + synologyClient client.SynologyClient +} + +func (service NetworkGuestService) Read() (client.NetworkResponse, error) { + content, err := service.synologyClient.ReadNetworkGuest() + return content, err +} + +type HostGuestService struct { + synologyClient client.SynologyClient +} + +func (service HostGuestService) Read() (client.HostResponse, error) { + content, err := service.synologyClient.ReadHostGuest() + return content, err +}