From f2bcfffa305122c8f603635e66f2b5c29e7ed67e Mon Sep 17 00:00:00 2001 From: Yannick Struyf Date: Fri, 7 Aug 2020 12:54:46 +0200 Subject: [PATCH] fix for issue 159: Changing a disk image is now propagated to the VMs --- go.mod | 2 + go.sum | 4 + nutanix/resource_nutanix_virtual_machine.go | 139 +++++-- .../resource_nutanix_virtual_machine_test.go | 185 ++++++++++ nutanix/structure.go | 41 ++- utils/utils.go | 9 + vendor/github.com/jinzhu/copier/Guardfile | 3 + vendor/github.com/jinzhu/copier/License | 20 + vendor/github.com/jinzhu/copier/README.md | 100 +++++ vendor/github.com/jinzhu/copier/copier.go | 189 ++++++++++ .../jinzhu/copier/copier_benchmark_test.go | 45 +++ .../copier/copier_different_type_test.go | 143 ++++++++ .../github.com/jinzhu/copier/copier_test.go | 342 ++++++++++++++++++ vendor/github.com/jinzhu/copier/wercker.yml | 23 ++ 14 files changed, 1220 insertions(+), 25 deletions(-) create mode 100644 vendor/github.com/jinzhu/copier/Guardfile create mode 100644 vendor/github.com/jinzhu/copier/License create mode 100644 vendor/github.com/jinzhu/copier/README.md create mode 100644 vendor/github.com/jinzhu/copier/copier.go create mode 100644 vendor/github.com/jinzhu/copier/copier_benchmark_test.go create mode 100644 vendor/github.com/jinzhu/copier/copier_different_type_test.go create mode 100644 vendor/github.com/jinzhu/copier/copier_test.go create mode 100644 vendor/github.com/jinzhu/copier/wercker.yml diff --git a/go.mod b/go.mod index c2b69a6c3..9b25247e1 100644 --- a/go.mod +++ b/go.mod @@ -2,8 +2,10 @@ module github.com/terraform-providers/terraform-provider-nutanix require ( github.com/client9/misspell v0.3.4 + github.com/getlantern/deepcopy v0.0.0-20160317154340-7f45deb8130a github.com/golangci/golangci-lint v1.25.0 github.com/hashicorp/terraform v0.12.28 + github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a github.com/mitchellh/gox v1.0.1 github.com/spf13/afero v1.2.2 // indirect github.com/spf13/cast v1.3.1 diff --git a/go.sum b/go.sum index 0c4877cc8..4416b8e9e 100644 --- a/go.sum +++ b/go.sum @@ -136,6 +136,8 @@ github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/getlantern/deepcopy v0.0.0-20160317154340-7f45deb8130a h1:yU/FENpkHYISWsQrbr3pcZOBj0EuRjPzNc1+dTCLu44= +github.com/getlantern/deepcopy v0.0.0-20160317154340-7f45deb8130a/go.mod h1:AEugkNu3BjBxyz958nJ5holD9PRjta6iprcoUauDbU4= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-critic/go-critic v0.4.1 h1:4DTQfT1wWwLg/hzxwD9bkdhDQrdJtxe6DUTadPlrIeE= github.com/go-critic/go-critic v0.4.1/go.mod h1:7/14rZGnZbY6E38VEGk2kVhoq6itzc1E68facVDK23g= @@ -338,6 +340,8 @@ github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZ github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a h1:GmsqmapfzSJkm28dhRoHz2tLRbJmqhU86IPgBtN3mmk= github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a/go.mod h1:xRskid8CManxVta/ALEhJha/pweKBaVG6fWgc0yH25s= +github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt70eK/BSch+gFAGrNzecsoENgu2o= +github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s= github.com/jirfag/go-printf-func-name v0.0.0-20191110105641-45db9963cdd3 h1:jNYPNLe3d8smommaoQlK7LOA5ESyUJJ+Wf79ZtA7Vp4= github.com/jirfag/go-printf-func-name v0.0.0-20191110105641-45db9963cdd3/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= diff --git a/nutanix/resource_nutanix_virtual_machine.go b/nutanix/resource_nutanix_virtual_machine.go index 9687a7f41..c551d01f2 100644 --- a/nutanix/resource_nutanix_virtual_machine.go +++ b/nutanix/resource_nutanix_virtual_machine.go @@ -16,6 +16,7 @@ import ( "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" + "github.com/jinzhu/copier" ) var ( @@ -23,6 +24,7 @@ var ( vmDelay = 3 * time.Second vmMinTimeout = 3 * time.Second IDE = "IDE" + SCSI = "SCSI" useHotAdd = true ) @@ -1087,7 +1089,7 @@ func resourceNutanixVirtualMachineUpdate(d *schema.ResourceData, meta interface{ preFillGTUpdateRequest(guestTool, response) preFillGUpdateRequest(guest, response) preFillPWUpdateRequest(pw, response) - + toUpdateresourceList := []*v3.VMResources{res} if err != nil { if strings.Contains(fmt.Sprint(err), "ENTITY_NOT_FOUND") { d.SetId("") @@ -1266,6 +1268,15 @@ func resourceNutanixVirtualMachineUpdate(d *schema.ResourceData, meta interface{ } res.DiskList = expandDiskListUpdate(d, response) + imageMismatch := false + toUpdateresourceList, imageMismatch, err = ParseDiskImageChange(response, d, res.DiskList, toUpdateresourceList) + if err != nil { + return err + } + + if imageMismatch { + hotPlugChange = false + } postCdromCount, err := CountDiskListCdrom(res.DiskList) if err != nil { @@ -1338,33 +1349,44 @@ func resourceNutanixVirtualMachineUpdate(d *schema.ResourceData, meta interface{ mySpec += 2 metadata.SpecVersion = &mySpec } + toUpdateresourceListlength := len(toUpdateresourceList) + for i := 0; i < toUpdateresourceListlength; i++ { + spec.Resources = toUpdateresourceList[i] + request.Metadata = metadata + request.Spec = spec - spec.Resources = res - request.Metadata = metadata - request.Spec = spec + log.Printf("[DEBUG] Updating Virtual Machine: %s, %s", d.Get("name").(string), d.Id()) - log.Printf("[DEBUG] Updating Virtual Machine: %s, %s", d.Get("name").(string), d.Id()) + resp, err2 := conn.V3.UpdateVM(d.Id(), request) + if err2 != nil { + return fmt.Errorf("error updating Virtual Machine UUID(%s): %s", d.Id(), err2) + } - resp, err2 := conn.V3.UpdateVM(d.Id(), request) - if err2 != nil { - return fmt.Errorf("error updating Virtual Machine UUID(%s): %s", d.Id(), err2) - } + stateConf := &resource.StateChangeConf{ + Pending: []string{"QUEUED", "RUNNING"}, + Target: []string{"SUCCEEDED"}, + Refresh: taskStateRefreshFunc(conn, resp.Status.ExecutionContext.TaskUUID.(string)), + Timeout: vmTimeout, + Delay: vmDelay, + MinTimeout: vmMinTimeout, + } - stateConf := &resource.StateChangeConf{ - Pending: []string{"QUEUED", "RUNNING"}, - Target: []string{"SUCCEEDED"}, - Refresh: taskStateRefreshFunc(conn, resp.Status.ExecutionContext.TaskUUID.(string)), - Timeout: vmTimeout, - Delay: vmDelay, - MinTimeout: vmMinTimeout, - } + if _, err := stateConf.WaitForState(); err != nil { + return fmt.Errorf( + "error waiting for vm (%s) to update: %s", d.Id(), err) + } - if _, err := stateConf.WaitForState(); err != nil { - return fmt.Errorf( - "error waiting for vm (%s) to update: %s", d.Id(), err) + // Do not do a new get in final iteration + if i < toUpdateresourceListlength-1 { + responseAfterUpdate, err := conn.V3.GetVM(d.Id()) + if err != nil { + return err + } + metadata.SpecVersion = responseAfterUpdate.Metadata.SpecVersion + } } - //Tehn, Turn On the VM. + //Then, Turn On the VM. if err := changePowerState(conn, d.Id(), "ON"); err != nil { return fmt.Errorf("internal error: cannot turn ON the VM with UUID(%s): %s", d.Id(), err) } @@ -2067,6 +2089,81 @@ func setVMTimeout(meta interface{}) { } } +func hasChangedDiskImage(orgDiskUUID string, orgImageUUID string, newDiskListInt interface{}) bool { + reflectedNew := reflect.ValueOf(newDiskListInt) + for i := 0; i < reflectedNew.Len(); i++ { + ndisk := reflectedNew.Index(i).Interface().(map[string]interface{}) + if orgDiskUUID == ndisk["uuid"].(string) { + if datasourcereference, ok := ndisk["data_source_reference"]; ok { + if imageUUID, ok := datasourcereference.(map[string]interface{})["uuid"]; ok { + if orgImageUUID != imageUUID { + return true + } + } + } + } + } + return false +} + +func GetChangedImageDiskUUIDs(d *schema.ResourceData) []string { + old, new := d.GetChange("disk_list") + tmpDiskUUIDList := make([]string, 0) + odiskUUID := "" + reflectedOld := reflect.ValueOf(old) + for i := 0; i < reflectedOld.Len(); i++ { + odisk := reflectedOld.Index(i).Interface().(map[string]interface{}) + if odiskUUIDInt, ok := odisk["uuid"]; ok { + odiskUUID = odiskUUIDInt.(string) + if datasourcereference, ok := odisk["data_source_reference"]; ok { + if imageUUID, ok := datasourcereference.(map[string]interface{})["uuid"]; ok { + if hasChangedDiskImage(odiskUUID, imageUUID.(string), new) { + tmpDiskUUIDList = append(tmpDiskUUIDList, odiskUUID) + } + } + } + } + } + return tmpDiskUUIDList +} + +func ParseDiskImageChange(vmOutput *v3.VMIntentResponse, d *schema.ResourceData, expandedDiskList []*v3.VMDisk, toUpdateresourceList []*v3.VMResources) ([]*v3.VMResources, bool, error) { + // Check if there is an image mismatch + changedImageDiskListUUIDs := GetChangedImageDiskUUIDs(d) + imageMismatch := len(changedImageDiskListUUIDs) > 0 + //if there is no mismatch, just return + if !imageMismatch { + return toUpdateresourceList, imageMismatch, nil + } + // if there is a change, create a new vmResource object without the image disks (need to be removed from the vm) + tmpDiskList := make([]*v3.VMDisk, 0) + previousResourceDefinition := toUpdateresourceList[0] + utils.PrintToJSON(expandedDiskList, "expandedDiskList: ") + for _, edisk := range expandedDiskList { + // if disk has no uuid, it is new and must be added + if *edisk.UUID == "" || utils.FindIndexOfValueInStringSlice(changedImageDiskListUUIDs, *edisk.UUID) == -1 { + tmpDiskList = append(tmpDiskList, edisk) + } + } + //Need to force OFF powerstate, otherwise this will poweron the vm between changes + powerState := "OFF" + previousResourceDefinition.PowerState = &powerState + for _, rdisk := range previousResourceDefinition.DiskList { + if *rdisk.UUID != "" && utils.FindIndexOfValueInStringSlice(changedImageDiskListUUIDs, *rdisk.UUID) > -1 { + rdisk.UUID = nil + } + } + + // Clone the resource + resourceClone := v3.VMResources{} + copier.Copy(&resourceClone, &previousResourceDefinition) + resourceClone.DiskList = tmpDiskList + //Add resource to the list of resources to be updated + toUpdateresourceList = append([]*v3.VMResources{&resourceClone}, toUpdateresourceList...) + + return toUpdateresourceList, imageMismatch, nil +} + func resourceNutanixVirtualMachineInstanceResourceV0() *schema.Resource { return &schema.Resource{ Schema: map[string]*schema.Schema{ diff --git a/nutanix/resource_nutanix_virtual_machine_test.go b/nutanix/resource_nutanix_virtual_machine_test.go index 3f83104cf..f306d3c3f 100644 --- a/nutanix/resource_nutanix_virtual_machine_test.go +++ b/nutanix/resource_nutanix_virtual_machine_test.go @@ -429,6 +429,84 @@ func TestAccNutanixVirtualMachine_resizeDiskClone(t *testing.T) { }) } +func TestAccNutanixVirtualMachine_changeVMImage(t *testing.T) { + resourceName := "nutanix_virtual_machine.vm" + imageURL1 := "http://download.cirros-cloud.net/0.4.0/cirros-0.4.0-x86_64-disk.img" + imageName1 := "cirros-040" + imageURL2 := "http://download.cirros-cloud.net/0.5.0/cirros-0.5.0-x86_64-disk.img" + imageName2 := "cirros-050" + vmName := acctest.RandomWithPrefix("test-dou-VM") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckNutanixVirtualMachineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccNutanixVMChangeImage(vmName, imageName1, imageURL1), + Check: resource.ComposeTestCheckFunc( + testAccCheckNutanixVirtualMachineExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", vmName), + resource.TestCheckResourceAttr(resourceName, "power_state", "ON"), + resource.TestCheckResourceAttr(resourceName, "memory_size_mib", "186"), + resource.TestCheckResourceAttr(resourceName, "num_sockets", "1"), + resource.TestCheckResourceAttr(resourceName, "num_vcpus_per_socket", "1"), + ), + }, + { + Config: testAccNutanixVMChangeImage(vmName, imageName2, imageURL2), + Check: resource.ComposeTestCheckFunc( + testAccCheckNutanixVirtualMachineExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", vmName), + resource.TestCheckResourceAttr(resourceName, "power_state", "ON"), + resource.TestCheckResourceAttr(resourceName, "memory_size_mib", "186"), + resource.TestCheckResourceAttr(resourceName, "num_sockets", "1"), + resource.TestCheckResourceAttr(resourceName, "num_vcpus_per_socket", "1"), + ), + }, + }, + }) +} + +func TestAccNutanixVirtualMachine_changeMultipleVMImages(t *testing.T) { + resourceName := "nutanix_virtual_machine.vm" + imageURL1 := "http://download.cirros-cloud.net/0.4.0/cirros-0.4.0-x86_64-disk.img" + imageName1 := "cirros-040" + imageURL2 := "http://download.cirros-cloud.net/0.5.0/cirros-0.5.0-x86_64-disk.img" + imageName2 := "cirros-050" + vmName := acctest.RandomWithPrefix("test-dou-VM") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckNutanixVirtualMachineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccNutanixVMChangeMultipleVMImages(vmName, imageName1, imageURL1), + Check: resource.ComposeTestCheckFunc( + testAccCheckNutanixVirtualMachineExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", vmName), + resource.TestCheckResourceAttr(resourceName, "power_state", "ON"), + resource.TestCheckResourceAttr(resourceName, "memory_size_mib", "186"), + resource.TestCheckResourceAttr(resourceName, "num_sockets", "1"), + resource.TestCheckResourceAttr(resourceName, "num_vcpus_per_socket", "1"), + ), + }, + { + Config: testAccNutanixVMChangeMultipleVMImages(vmName, imageName2, imageURL2), + Check: resource.ComposeTestCheckFunc( + testAccCheckNutanixVirtualMachineExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", vmName), + resource.TestCheckResourceAttr(resourceName, "power_state", "ON"), + resource.TestCheckResourceAttr(resourceName, "memory_size_mib", "186"), + resource.TestCheckResourceAttr(resourceName, "num_sockets", "1"), + resource.TestCheckResourceAttr(resourceName, "num_vcpus_per_socket", "1"), + ), + }, + }, + }) +} + func testAccCheckNutanixVirtualMachineExists(n string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -1060,3 +1138,110 @@ func testAccNutanixVMConfigResizeDiskClone(imgName, vmName string, diskSize int) } `, imgName, vmName, diskSize) } + +func testAccNutanixVMChangeImage(vmName string, imgName string, imgURL string) string { + return fmt.Sprintf(` + data "nutanix_clusters" "clusters" {} + + locals { + cluster1 = [ + for cluster in data.nutanix_clusters.clusters.entities : + cluster.metadata.uuid if cluster.service_list[0] != "PRISM_CENTRAL" + ][0] + } + + resource "nutanix_image" "%[2]s" { + name = "%[2]s" + source_uri = "%[3]s" + description = "heres a tiny linux image, not an iso, but a real disk!" + } + + resource "nutanix_virtual_machine" "vm" { + name = "%[1]s" + cluster_uuid = local.cluster1 + num_vcpus_per_socket = 1 + num_sockets = 1 + memory_size_mib = 186 + + disk_list { + data_source_reference = { + kind = "image" + uuid = nutanix_image.%[2]s.id + } + device_properties { + disk_address = { + device_index = 0 + adapter_type = "SCSI" + } + device_type = "DISK" + } + } + disk_list { + disk_size_mib = 100 + } + disk_list { + disk_size_mib = 200 + } + } + `, vmName, imgName, imgURL) +} + +func testAccNutanixVMChangeMultipleVMImages(vmName string, imgName string, imgURL string) string { + return fmt.Sprintf(` + data "nutanix_clusters" "clusters" {} + + locals { + cluster1 = [ + for cluster in data.nutanix_clusters.clusters.entities : + cluster.metadata.uuid if cluster.service_list[0] != "PRISM_CENTRAL" + ][0] + } + + resource "nutanix_image" "%[2]s" { + name = "%[2]s" + source_uri = "%[3]s" + description = "heres a tiny linux image, not an iso, but a real disk!" + } + + resource "nutanix_virtual_machine" "vm" { + name = "%[1]s" + cluster_uuid = local.cluster1 + num_vcpus_per_socket = 1 + num_sockets = 1 + memory_size_mib = 186 + + disk_list { + data_source_reference = { + kind = "image" + uuid = nutanix_image.%[2]s.id + } + device_properties { + disk_address = { + device_index = 0 + adapter_type = "SCSI" + } + device_type = "DISK" + } + } + disk_list { + data_source_reference = { + kind = "image" + uuid = nutanix_image.%[2]s.id + } + device_properties { + disk_address = { + device_index = 1 + adapter_type = "SCSI" + } + device_type = "DISK" + } + } + disk_list { + disk_size_mib = 100 + } + disk_list { + disk_size_mib = 200 + } + } + `, vmName, imgName, imgURL) +} diff --git a/nutanix/structure.go b/nutanix/structure.go index 8e2058a23..72162eb29 100644 --- a/nutanix/structure.go +++ b/nutanix/structure.go @@ -104,7 +104,10 @@ func flattenNicList(nics []*v3.VMNic) []map[string]interface{} { } func flattenDiskList(disks []*v3.VMDisk) []map[string]interface{} { - diskList := make([]map[string]interface{}, 0) + utils.PrintToJSON(disks, "flattenDiskList disks: ") + disklistLength := len(disks) + sortedSCSI := make([]map[string]interface{}, disklistLength) + sortedIDE := make([]map[string]interface{}, disklistLength) for _, v := range disks { var deviceProps []map[string]interface{} var storageConfig []map[string]interface{} @@ -140,8 +143,7 @@ func flattenDiskList(disks []*v3.VMDisk) []map[string]interface{} { }, }) } - - diskList = append(diskList, map[string]interface{}{ + diskMap := map[string]interface{}{ "uuid": utils.StringValue(v.UUID), "disk_size_bytes": utils.Int64Value(v.DiskSizeBytes), "disk_size_mib": utils.Int64Value(v.DiskSizeMib), @@ -149,11 +151,42 @@ func flattenDiskList(disks []*v3.VMDisk) []map[string]interface{} { "storage_config": storageConfig, "data_source_reference": flattenReferenceValues(v.DataSourceReference), "volume_group_reference": flattenReferenceValues(v.VolumeGroupReference), - }) + } + + listIndex, _ := strconv.Atoi(deviceProps[0]["disk_address"].(map[string]interface{})["device_index"].(string)) + listType := deviceProps[0]["disk_address"].(map[string]interface{})["adapter_type"] + + if listType == IDE { + sortedIDE[listIndex] = diskMap + } else { + sortedSCSI[listIndex] = diskMap + } + + // diskList = append(diskList, map[string]interface{}{ + // "uuid": utils.StringValue(v.UUID), + // "disk_size_bytes": utils.Int64Value(v.DiskSizeBytes), + // "disk_size_mib": utils.Int64Value(v.DiskSizeMib), + // "device_properties": deviceProps, + // "storage_config": storageConfig, + // "data_source_reference": flattenReferenceValues(v.DataSourceReference), + // "volume_group_reference": flattenReferenceValues(v.VolumeGroupReference), + // }) } + diskList := append(deleteEmptyDiskListValues(sortedSCSI), deleteEmptyDiskListValues(sortedIDE)...) + utils.PrintToJSON(diskList, "sorted disklist: ") return diskList } +func deleteEmptyDiskListValues(s []map[string]interface{}) []map[string]interface{} { + var r []map[string]interface{} + for _, el := range s { + if el != nil { + r = append(r, el) + } + } + return r +} + func flattenSerialPortList(serialPorts []*v3.VMSerialPort) []map[string]interface{} { serialPortList := make([]map[string]interface{}, 0) if serialPorts != nil { diff --git a/utils/utils.go b/utils/utils.go index d7c643f65..af26c71fa 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -42,3 +42,12 @@ func ConvertMapString(o map[string]interface{}) map[string]string { } return converted } + +func FindIndexOfValueInStringSlice(slice []string, val string) int { + for i, item := range slice { + if item == val { + return i + } + } + return -1 +} diff --git a/vendor/github.com/jinzhu/copier/Guardfile b/vendor/github.com/jinzhu/copier/Guardfile new file mode 100644 index 000000000..0b860b065 --- /dev/null +++ b/vendor/github.com/jinzhu/copier/Guardfile @@ -0,0 +1,3 @@ +guard 'gotest' do + watch(%r{\.go$}) +end diff --git a/vendor/github.com/jinzhu/copier/License b/vendor/github.com/jinzhu/copier/License new file mode 100644 index 000000000..e2dc5381e --- /dev/null +++ b/vendor/github.com/jinzhu/copier/License @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2015 Jinzhu + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/jinzhu/copier/README.md b/vendor/github.com/jinzhu/copier/README.md new file mode 100644 index 000000000..f929b4679 --- /dev/null +++ b/vendor/github.com/jinzhu/copier/README.md @@ -0,0 +1,100 @@ +# Copier + + I am a copier, I copy everything from one to another + +[![wercker status](https://app.wercker.com/status/9d44ad2d4e6253929c8fb71359effc0b/s/master "wercker status")](https://app.wercker.com/project/byKey/9d44ad2d4e6253929c8fb71359effc0b) + +## Features + +* Copy from field to field with same name +* Copy from method to field with same name +* Copy from field to method with same name +* Copy from slice to slice +* Copy from struct to slice + +## Usage + +```go +package main + +import ( + "fmt" + "github.com/jinzhu/copier" +) + +type User struct { + Name string + Role string + Age int32 +} + +func (user *User) DoubleAge() int32 { + return 2 * user.Age +} + +type Employee struct { + Name string + Age int32 + DoubleAge int32 + EmployeId int64 + SuperRule string +} + +func (employee *Employee) Role(role string) { + employee.SuperRule = "Super " + role +} + +func main() { + var ( + user = User{Name: "Jinzhu", Age: 18, Role: "Admin"} + users = []User{{Name: "Jinzhu", Age: 18, Role: "Admin"}, {Name: "jinzhu 2", Age: 30, Role: "Dev"}} + employee = Employee{} + employees = []Employee{} + ) + + copier.Copy(&employee, &user) + + fmt.Printf("%#v \n", employee) + // Employee{ + // Name: "Jinzhu", // Copy from field + // Age: 18, // Copy from field + // DoubleAge: 36, // Copy from method + // EmployeeId: 0, // Ignored + // SuperRule: "Super Admin", // Copy to method + // } + + // Copy struct to slice + copier.Copy(&employees, &user) + + fmt.Printf("%#v \n", employees) + // []Employee{ + // {Name: "Jinzhu", Age: 18, DoubleAge: 36, EmployeId: 0, SuperRule: "Super Admin"} + // } + + // Copy slice to slice + employees = []Employee{} + copier.Copy(&employees, &users) + + fmt.Printf("%#v \n", employees) + // []Employee{ + // {Name: "Jinzhu", Age: 18, DoubleAge: 36, EmployeId: 0, SuperRule: "Super Admin"}, + // {Name: "jinzhu 2", Age: 30, DoubleAge: 60, EmployeId: 0, SuperRule: "Super Dev"}, + // } +} +``` + +## Contributing + +You can help to make the project better, check out [http://gorm.io/contribute.html](http://gorm.io/contribute.html) for things you can do. + +# Author + +**jinzhu** + +* +* +* + +## License + +Released under the [MIT License](https://github.com/jinzhu/copier/blob/master/License). diff --git a/vendor/github.com/jinzhu/copier/copier.go b/vendor/github.com/jinzhu/copier/copier.go new file mode 100644 index 000000000..3f339becc --- /dev/null +++ b/vendor/github.com/jinzhu/copier/copier.go @@ -0,0 +1,189 @@ +package copier + +import ( + "database/sql" + "errors" + "reflect" +) + +// Copy copy things +func Copy(toValue interface{}, fromValue interface{}) (err error) { + var ( + isSlice bool + amount = 1 + from = indirect(reflect.ValueOf(fromValue)) + to = indirect(reflect.ValueOf(toValue)) + ) + + if !to.CanAddr() { + return errors.New("copy to value is unaddressable") + } + + // Return is from value is invalid + if !from.IsValid() { + return + } + + fromType := indirectType(from.Type()) + toType := indirectType(to.Type()) + + // Just set it if possible to assign + // And need to do copy anyway if the type is struct + if fromType.Kind() != reflect.Struct && from.Type().AssignableTo(to.Type()) { + to.Set(from) + return + } + + if fromType.Kind() != reflect.Struct || toType.Kind() != reflect.Struct { + return + } + + if to.Kind() == reflect.Slice { + isSlice = true + if from.Kind() == reflect.Slice { + amount = from.Len() + } + } + + for i := 0; i < amount; i++ { + var dest, source reflect.Value + + if isSlice { + // source + if from.Kind() == reflect.Slice { + source = indirect(from.Index(i)) + } else { + source = indirect(from) + } + // dest + dest = indirect(reflect.New(toType).Elem()) + } else { + source = indirect(from) + dest = indirect(to) + } + + // check source + if source.IsValid() { + fromTypeFields := deepFields(fromType) + //fmt.Printf("%#v", fromTypeFields) + // Copy from field to field or method + for _, field := range fromTypeFields { + name := field.Name + + if fromField := source.FieldByName(name); fromField.IsValid() { + // has field + if toField := dest.FieldByName(name); toField.IsValid() { + if toField.CanSet() { + if !set(toField, fromField) { + if err := Copy(toField.Addr().Interface(), fromField.Interface()); err != nil { + return err + } + } + } + } else { + // try to set to method + var toMethod reflect.Value + if dest.CanAddr() { + toMethod = dest.Addr().MethodByName(name) + } else { + toMethod = dest.MethodByName(name) + } + + if toMethod.IsValid() && toMethod.Type().NumIn() == 1 && fromField.Type().AssignableTo(toMethod.Type().In(0)) { + toMethod.Call([]reflect.Value{fromField}) + } + } + } + } + + // Copy from method to field + for _, field := range deepFields(toType) { + name := field.Name + + var fromMethod reflect.Value + if source.CanAddr() { + fromMethod = source.Addr().MethodByName(name) + } else { + fromMethod = source.MethodByName(name) + } + + if fromMethod.IsValid() && fromMethod.Type().NumIn() == 0 && fromMethod.Type().NumOut() == 1 { + if toField := dest.FieldByName(name); toField.IsValid() && toField.CanSet() { + values := fromMethod.Call([]reflect.Value{}) + if len(values) >= 1 { + set(toField, values[0]) + } + } + } + } + } + if isSlice { + if dest.Addr().Type().AssignableTo(to.Type().Elem()) { + to.Set(reflect.Append(to, dest.Addr())) + } else if dest.Type().AssignableTo(to.Type().Elem()) { + to.Set(reflect.Append(to, dest)) + } + } + } + return +} + +func deepFields(reflectType reflect.Type) []reflect.StructField { + var fields []reflect.StructField + + if reflectType = indirectType(reflectType); reflectType.Kind() == reflect.Struct { + for i := 0; i < reflectType.NumField(); i++ { + v := reflectType.Field(i) + if v.Anonymous { + fields = append(fields, deepFields(v.Type)...) + } else { + fields = append(fields, v) + } + } + } + + return fields +} + +func indirect(reflectValue reflect.Value) reflect.Value { + for reflectValue.Kind() == reflect.Ptr { + reflectValue = reflectValue.Elem() + } + return reflectValue +} + +func indirectType(reflectType reflect.Type) reflect.Type { + for reflectType.Kind() == reflect.Ptr || reflectType.Kind() == reflect.Slice { + reflectType = reflectType.Elem() + } + return reflectType +} + +func set(to, from reflect.Value) bool { + if from.IsValid() { + if to.Kind() == reflect.Ptr { + //set `to` to nil if from is nil + if from.Kind() == reflect.Ptr && from.IsNil() { + to.Set(reflect.Zero(to.Type())) + return true + } else if to.IsNil() { + to.Set(reflect.New(to.Type().Elem())) + } + to = to.Elem() + } + + if from.Type().ConvertibleTo(to.Type()) { + to.Set(from.Convert(to.Type())) + } else if scanner, ok := to.Addr().Interface().(sql.Scanner); ok { + err := scanner.Scan(from.Interface()) + if err != nil { + return false + } + } else if from.Kind() == reflect.Ptr { + return set(to, from.Elem()) + } else { + return false + } + } + return true +} diff --git a/vendor/github.com/jinzhu/copier/copier_benchmark_test.go b/vendor/github.com/jinzhu/copier/copier_benchmark_test.go new file mode 100644 index 000000000..9471fa4df --- /dev/null +++ b/vendor/github.com/jinzhu/copier/copier_benchmark_test.go @@ -0,0 +1,45 @@ +package copier_test + +import ( + "encoding/json" + "testing" + + "github.com/jinzhu/copier" +) + +func BenchmarkCopyStruct(b *testing.B) { + var fakeAge int32 = 12 + user := User{Name: "Jinzhu", Nickname: "jinzhu", Age: 18, FakeAge: &fakeAge, Role: "Admin", Notes: []string{"hello world", "welcome"}, flags: []byte{'x'}} + for x := 0; x < b.N; x++ { + copier.Copy(&Employee{}, &user) + } +} + +func BenchmarkNamaCopy(b *testing.B) { + var fakeAge int32 = 12 + user := User{Name: "Jinzhu", Nickname: "jinzhu", Age: 18, FakeAge: &fakeAge, Role: "Admin", Notes: []string{"hello world", "welcome"}, flags: []byte{'x'}} + for x := 0; x < b.N; x++ { + employee := &Employee{ + Name: user.Name, + Nickname: &user.Nickname, + Age: int64(user.Age), + FakeAge: int(*user.FakeAge), + DoubleAge: user.DoubleAge(), + Notes: user.Notes, + } + employee.Role(user.Role) + } +} + +func BenchmarkJsonMarshalCopy(b *testing.B) { + var fakeAge int32 = 12 + user := User{Name: "Jinzhu", Nickname: "jinzhu", Age: 18, FakeAge: &fakeAge, Role: "Admin", Notes: []string{"hello world", "welcome"}, flags: []byte{'x'}} + for x := 0; x < b.N; x++ { + data, _ := json.Marshal(user) + var employee Employee + json.Unmarshal(data, &employee) + + employee.DoubleAge = user.DoubleAge() + employee.Role(user.Role) + } +} diff --git a/vendor/github.com/jinzhu/copier/copier_different_type_test.go b/vendor/github.com/jinzhu/copier/copier_different_type_test.go new file mode 100644 index 000000000..c1903f877 --- /dev/null +++ b/vendor/github.com/jinzhu/copier/copier_different_type_test.go @@ -0,0 +1,143 @@ +package copier_test + +import ( + "testing" + + "github.com/jinzhu/copier" +) + +type TypeStruct1 struct { + Field1 string + Field2 string + Field3 TypeStruct2 + Field4 *TypeStruct2 + Field5 []*TypeStruct2 + Field6 []TypeStruct2 + Field7 []*TypeStruct2 + Field8 []TypeStruct2 +} + +type TypeStruct2 struct { + Field1 int + Field2 string +} + +type TypeStruct3 struct { + Field1 interface{} + Field2 string + Field3 TypeStruct4 + Field4 *TypeStruct4 + Field5 []*TypeStruct4 + Field6 []*TypeStruct4 + Field7 []TypeStruct4 + Field8 []TypeStruct4 +} + +type TypeStruct4 struct { + field1 int + Field2 string +} + +func (t *TypeStruct4) Field1(i int) { + t.field1 = i +} + +func TestCopyDifferentFieldType(t *testing.T) { + ts := &TypeStruct1{ + Field1: "str1", + Field2: "str2", + } + ts2 := &TypeStruct2{} + + copier.Copy(ts2, ts) + + if ts2.Field2 != ts.Field2 || ts2.Field1 != 0 { + t.Errorf("Should be able to copy from ts to ts2") + } +} + +func TestCopyDifferentTypeMethod(t *testing.T) { + ts := &TypeStruct1{ + Field1: "str1", + Field2: "str2", + } + ts4 := &TypeStruct4{} + + copier.Copy(ts4, ts) + + if ts4.Field2 != ts.Field2 || ts4.field1 != 0 { + t.Errorf("Should be able to copy from ts to ts4") + } +} + +func TestAssignableType(t *testing.T) { + ts := &TypeStruct1{ + Field1: "str1", + Field2: "str2", + Field3: TypeStruct2{ + Field1: 666, + Field2: "str2", + }, + Field4: &TypeStruct2{ + Field1: 666, + Field2: "str2", + }, + Field5: []*TypeStruct2{ + { + Field1: 666, + Field2: "str2", + }, + }, + Field6: []TypeStruct2{ + { + Field1: 666, + Field2: "str2", + }, + }, + Field7: []*TypeStruct2{ + { + Field1: 666, + Field2: "str2", + }, + }, + } + + ts3 := &TypeStruct3{} + + copier.Copy(&ts3, &ts) + + if v, ok := ts3.Field1.(string); !ok { + t.Error("Assign to interface{} type did not succeed") + } else if v != "str1" { + t.Error("String haven't been copied correctly") + } + + if ts3.Field2 != ts.Field2 { + t.Errorf("Field2 should be copied") + } + + checkType2WithType4(ts.Field3, ts3.Field3, t, "Field3") + checkType2WithType4(*ts.Field4, *ts3.Field4, t, "Field4") + + for idx, f := range ts.Field5 { + checkType2WithType4(*f, *(ts3.Field5[idx]), t, "Field5") + } + + for idx, f := range ts.Field6 { + checkType2WithType4(f, *(ts3.Field6[idx]), t, "Field6") + } + + for idx, f := range ts.Field7 { + checkType2WithType4(*f, ts3.Field7[idx], t, "Field7") + } + + for idx, f := range ts.Field8 { + checkType2WithType4(f, ts3.Field8[idx], t, "Field8") + } +} + +func checkType2WithType4(t2 TypeStruct2, t4 TypeStruct4, t *testing.T, testCase string) { + if t2.Field1 != t4.field1 || t2.Field2 != t4.Field2 { + t.Errorf("%v: type struct 4 and type struct 2 is not equal", testCase) + } +} diff --git a/vendor/github.com/jinzhu/copier/copier_test.go b/vendor/github.com/jinzhu/copier/copier_test.go new file mode 100644 index 000000000..788902413 --- /dev/null +++ b/vendor/github.com/jinzhu/copier/copier_test.go @@ -0,0 +1,342 @@ +package copier_test + +import ( + "errors" + "reflect" + "testing" + "time" + + "github.com/jinzhu/copier" +) + +type User struct { + Name string + Birthday *time.Time + Nickname string + Role string + Age int32 + FakeAge *int32 + Notes []string + flags []byte +} + +func (user User) DoubleAge() int32 { + return 2 * user.Age +} + +type Employee struct { + Name string + Birthday *time.Time + Nickname *string + Age int64 + FakeAge int + EmployeID int64 + DoubleAge int32 + SuperRule string + Notes []string + flags []byte +} + +func (employee *Employee) Role(role string) { + employee.SuperRule = "Super " + role +} + +func checkEmployee(employee Employee, user User, t *testing.T, testCase string) { + if employee.Name != user.Name { + t.Errorf("%v: Name haven't been copied correctly.", testCase) + } + if employee.Nickname == nil || *employee.Nickname != user.Nickname { + t.Errorf("%v: NickName haven't been copied correctly.", testCase) + } + if employee.Birthday == nil && user.Birthday != nil { + t.Errorf("%v: Birthday haven't been copied correctly.", testCase) + } + if employee.Birthday != nil && user.Birthday == nil { + t.Errorf("%v: Birthday haven't been copied correctly.", testCase) + } + if employee.Birthday != nil && user.Birthday != nil && + !employee.Birthday.Equal(*(user.Birthday)) { + t.Errorf("%v: Birthday haven't been copied correctly.", testCase) + } + if employee.Age != int64(user.Age) { + t.Errorf("%v: Age haven't been copied correctly.", testCase) + } + if user.FakeAge != nil && employee.FakeAge != int(*user.FakeAge) { + t.Errorf("%v: FakeAge haven't been copied correctly.", testCase) + } + if employee.DoubleAge != user.DoubleAge() { + t.Errorf("%v: Copy from method doesn't work", testCase) + } + if employee.SuperRule != "Super "+user.Role { + t.Errorf("%v: Copy to method doesn't work", testCase) + } + if !reflect.DeepEqual(employee.Notes, user.Notes) { + t.Errorf("%v: Copy from slice doen't work", testCase) + } +} + +func TestCopySameStructWithPointerField(t *testing.T) { + var fakeAge int32 = 12 + var currentTime time.Time = time.Now() + user := &User{Birthday: ¤tTime, Name: "Jinzhu", Nickname: "jinzhu", Age: 18, FakeAge: &fakeAge, Role: "Admin", Notes: []string{"hello world", "welcome"}, flags: []byte{'x'}} + newUser := &User{} + copier.Copy(newUser, user) + if user.Birthday == newUser.Birthday { + t.Errorf("TestCopySameStructWithPointerField: copy Birthday failed since they need to have different address") + } + + if user.FakeAge == newUser.FakeAge { + t.Errorf("TestCopySameStructWithPointerField: copy FakeAge failed since they need to have different address") + } +} + +func checkEmployee2(employee Employee, user *User, t *testing.T, testCase string) { + if user == nil { + if employee.Name != "" || employee.Nickname != nil || employee.Birthday != nil || employee.Age != 0 || + employee.DoubleAge != 0 || employee.FakeAge != 0 || employee.SuperRule != "" || employee.Notes != nil { + t.Errorf("%v : employee should be empty", testCase) + } + return + } + + checkEmployee(employee, *user, t, testCase) +} + +func TestCopyStruct(t *testing.T) { + var fakeAge int32 = 12 + user := User{Name: "Jinzhu", Nickname: "jinzhu", Age: 18, FakeAge: &fakeAge, Role: "Admin", Notes: []string{"hello world", "welcome"}, flags: []byte{'x'}} + employee := Employee{} + + if err := copier.Copy(employee, &user); err == nil { + t.Errorf("Copy to unaddressable value should get error") + } + + copier.Copy(&employee, &user) + checkEmployee(employee, user, t, "Copy From Ptr To Ptr") + + employee2 := Employee{} + copier.Copy(&employee2, user) + checkEmployee(employee2, user, t, "Copy From Struct To Ptr") + + employee3 := Employee{} + ptrToUser := &user + copier.Copy(&employee3, &ptrToUser) + checkEmployee(employee3, user, t, "Copy From Double Ptr To Ptr") + + employee4 := &Employee{} + copier.Copy(&employee4, user) + checkEmployee(*employee4, user, t, "Copy From Ptr To Double Ptr") +} + +func TestCopyFromStructToSlice(t *testing.T) { + user := User{Name: "Jinzhu", Age: 18, Role: "Admin", Notes: []string{"hello world"}} + employees := []Employee{} + + if err := copier.Copy(employees, &user); err != nil && len(employees) != 0 { + t.Errorf("Copy to unaddressable value should get error") + } + + if copier.Copy(&employees, &user); len(employees) != 1 { + t.Errorf("Should only have one elem when copy struct to slice") + } else { + checkEmployee(employees[0], user, t, "Copy From Struct To Slice Ptr") + } + + employees2 := &[]Employee{} + if copier.Copy(&employees2, user); len(*employees2) != 1 { + t.Errorf("Should only have one elem when copy struct to slice") + } else { + checkEmployee((*employees2)[0], user, t, "Copy From Struct To Double Slice Ptr") + } + + employees3 := []*Employee{} + if copier.Copy(&employees3, user); len(employees3) != 1 { + t.Errorf("Should only have one elem when copy struct to slice") + } else { + checkEmployee(*(employees3[0]), user, t, "Copy From Struct To Ptr Slice Ptr") + } + + employees4 := &[]*Employee{} + if copier.Copy(&employees4, user); len(*employees4) != 1 { + t.Errorf("Should only have one elem when copy struct to slice") + } else { + checkEmployee(*((*employees4)[0]), user, t, "Copy From Struct To Double Ptr Slice Ptr") + } +} + +func TestCopyFromSliceToSlice(t *testing.T) { + users := []User{User{Name: "Jinzhu", Age: 18, Role: "Admin", Notes: []string{"hello world"}}, User{Name: "Jinzhu2", Age: 22, Role: "Dev", Notes: []string{"hello world", "hello"}}} + employees := []Employee{} + + if copier.Copy(&employees, users); len(employees) != 2 { + t.Errorf("Should have two elems when copy slice to slice") + } else { + checkEmployee(employees[0], users[0], t, "Copy From Slice To Slice Ptr @ 1") + checkEmployee(employees[1], users[1], t, "Copy From Slice To Slice Ptr @ 2") + } + + employees2 := &[]Employee{} + if copier.Copy(&employees2, &users); len(*employees2) != 2 { + t.Errorf("Should have two elems when copy slice to slice") + } else { + checkEmployee((*employees2)[0], users[0], t, "Copy From Slice Ptr To Double Slice Ptr @ 1") + checkEmployee((*employees2)[1], users[1], t, "Copy From Slice Ptr To Double Slice Ptr @ 2") + } + + employees3 := []*Employee{} + if copier.Copy(&employees3, users); len(employees3) != 2 { + t.Errorf("Should have two elems when copy slice to slice") + } else { + checkEmployee(*(employees3[0]), users[0], t, "Copy From Slice To Ptr Slice Ptr @ 1") + checkEmployee(*(employees3[1]), users[1], t, "Copy From Slice To Ptr Slice Ptr @ 2") + } + + employees4 := &[]*Employee{} + if copier.Copy(&employees4, users); len(*employees4) != 2 { + t.Errorf("Should have two elems when copy slice to slice") + } else { + checkEmployee(*((*employees4)[0]), users[0], t, "Copy From Slice Ptr To Double Ptr Slice Ptr @ 1") + checkEmployee(*((*employees4)[1]), users[1], t, "Copy From Slice Ptr To Double Ptr Slice Ptr @ 2") + } +} + +func TestCopyFromSliceToSlice2(t *testing.T) { + users := []*User{{Name: "Jinzhu", Age: 18, Role: "Admin", Notes: []string{"hello world"}}, nil} + employees := []Employee{} + + if copier.Copy(&employees, users); len(employees) != 2 { + t.Errorf("Should have two elems when copy slice to slice") + } else { + checkEmployee2(employees[0], users[0], t, "Copy From Slice To Slice Ptr @ 1") + checkEmployee2(employees[1], users[1], t, "Copy From Slice To Slice Ptr @ 2") + } + + employees2 := &[]Employee{} + if copier.Copy(&employees2, &users); len(*employees2) != 2 { + t.Errorf("Should have two elems when copy slice to slice") + } else { + checkEmployee2((*employees2)[0], users[0], t, "Copy From Slice Ptr To Double Slice Ptr @ 1") + checkEmployee2((*employees2)[1], users[1], t, "Copy From Slice Ptr To Double Slice Ptr @ 2") + } + + employees3 := []*Employee{} + if copier.Copy(&employees3, users); len(employees3) != 2 { + t.Errorf("Should have two elems when copy slice to slice") + } else { + checkEmployee2(*(employees3[0]), users[0], t, "Copy From Slice To Ptr Slice Ptr @ 1") + checkEmployee2(*(employees3[1]), users[1], t, "Copy From Slice To Ptr Slice Ptr @ 2") + } + + employees4 := &[]*Employee{} + if copier.Copy(&employees4, users); len(*employees4) != 2 { + t.Errorf("Should have two elems when copy slice to slice") + } else { + checkEmployee2(*((*employees4)[0]), users[0], t, "Copy From Slice Ptr To Double Ptr Slice Ptr @ 1") + checkEmployee2(*((*employees4)[1]), users[1], t, "Copy From Slice Ptr To Double Ptr Slice Ptr @ 2") + } +} + +func TestEmbeddedAndBase(t *testing.T) { + type Base struct { + BaseField1 int + BaseField2 int + User *User + } + + type Embed struct { + EmbedField1 int + EmbedField2 int + Base + } + + base := Base{} + embeded := Embed{} + embeded.BaseField1 = 1 + embeded.BaseField2 = 2 + embeded.EmbedField1 = 3 + embeded.EmbedField2 = 4 + + user:=User{ + Name:"testName", + } + embeded.User=&user + + copier.Copy(&base, &embeded) + + if base.BaseField1 != 1 || base.User.Name!="testName"{ + t.Error("Embedded fields not copied") + } + + base.BaseField1=11 + base.BaseField2=12 + user1:=User{ + Name:"testName1", + } + base.User=&user1 + + copier.Copy(&embeded,&base) + + if embeded.BaseField1 != 11 || embeded.User.Name!="testName1" { + t.Error("base fields not copied") + } +} + +type structSameName1 struct { + A string + B int64 + C time.Time +} + +type structSameName2 struct { + A string + B time.Time + C int64 +} + +func TestCopyFieldsWithSameNameButDifferentTypes(t *testing.T) { + obj1 := structSameName1{A: "123", B: 2, C: time.Now()} + obj2 := &structSameName2{} + err := copier.Copy(obj2, &obj1) + if err != nil { + t.Error("Should not raise error") + } + + if obj2.A != obj1.A { + t.Errorf("Field A should be copied") + } +} + +type ScannerValue struct { + V int +} + +func (s *ScannerValue) Scan(src interface{}) error { + return errors.New("I failed") +} + +type ScannerStruct struct { + V *ScannerValue +} + +type ScannerStructTo struct { + V *ScannerValue +} + +func TestScanner(t *testing.T) { + s := &ScannerStruct{ + V: &ScannerValue{ + V: 12, + }, + } + + s2 := &ScannerStructTo{} + + err := copier.Copy(s2, s) + if err != nil { + t.Error("Should not raise error") + } + + if s.V.V != s2.V.V { + t.Errorf("Field V should be copied") + } +} diff --git a/vendor/github.com/jinzhu/copier/wercker.yml b/vendor/github.com/jinzhu/copier/wercker.yml new file mode 100644 index 000000000..5e6ce981d --- /dev/null +++ b/vendor/github.com/jinzhu/copier/wercker.yml @@ -0,0 +1,23 @@ +box: golang + +build: + steps: + - setup-go-workspace + + # Gets the dependencies + - script: + name: go get + code: | + go get + + # Build the project + - script: + name: go build + code: | + go build ./... + + # Test the project + - script: + name: go test + code: | + go test ./...