terraform/builtin/providers/cobbler/resource_cobbler_system.go

852 lines
23 KiB
Go

package cobbler
import (
"bytes"
"fmt"
"log"
"sync"
"github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/schema"
cobbler "github.com/jtopjian/cobblerclient"
)
var systemSyncLock sync.Mutex
func resourceSystem() *schema.Resource {
return &schema.Resource{
Create: resourceSystemCreate,
Read: resourceSystemRead,
Update: resourceSystemUpdate,
Delete: resourceSystemDelete,
Schema: map[string]*schema.Schema{
"boot_files": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"comment": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"enable_gpxe": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Computed: true,
},
"fetchable_files": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"gateway": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"hostname": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"image": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"interface": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"cnames": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"dhcp_tag": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"dns_name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"bonding_opts": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"bridge_opts": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"gateway": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"interface_type": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"interface_master": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"ip_address": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"ipv6_address": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"ipv6_secondaries": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"ipv6_mtu": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"ipv6_static_routes": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"ipv6_default_gateway": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"mac_address": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"management": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Computed: true,
},
"netmask": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"static": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Computed: true,
},
"static_routes": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"virt_bridge": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
},
},
Set: resourceSystemInterfaceHash,
},
"ipv6_default_device": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"kernel_options": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"kernel_options_post": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"kickstart": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"ks_meta": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"ldap_enabled": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Computed: true,
},
"ldap_type": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"mgmt_classes": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"mgmt_parameters": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"monit_enabled": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Computed: true,
},
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"name_servers_search": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"name_servers": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"netboot_enabled": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Computed: true,
},
"owners": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"power_address": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"power_id": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"power_pass": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"power_type": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"power_user": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"profile": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"proxy": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"redhat_management_key": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"redhat_management_server": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"status": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"template_files": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"template_remote_kickstarts": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
Computed: true,
},
"virt_auto_boot": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"virt_file_size": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"virt_cpus": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"virt_type": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"virt_path": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"virt_pxe_boot": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
Computed: true,
},
"virt_ram": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"virt_disk_driver": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
},
}
}
func resourceSystemCreate(d *schema.ResourceData, meta interface{}) error {
systemSyncLock.Lock()
defer systemSyncLock.Unlock()
config := meta.(*Config)
// Create a cobblerclient.System struct
system := buildSystem(d)
// Attempt to create the System
log.Printf("[DEBUG] Cobbler System: Create Options: %#v", system)
newSystem, err := config.cobblerClient.CreateSystem(system)
if err != nil {
return fmt.Errorf("Cobbler System: Error Creating: %s", err)
}
// Build cobblerclient.Interface structs
interfaces := buildSystemInterfaces(d.Get("interface").(*schema.Set))
// Add each interface to the system
for interfaceName, interfaceInfo := range interfaces {
log.Printf("[DEBUG] Cobbler System Interface %#v: %#v", interfaceName, interfaceInfo)
if err := newSystem.CreateInterface(interfaceName, interfaceInfo); err != nil {
return fmt.Errorf("Cobbler System: Error adding Interface %s to %s: %s", interfaceName, newSystem.Name, err)
}
}
log.Printf("[DEBUG] Cobbler System: Created System: %#v", newSystem)
d.SetId(newSystem.Name)
log.Printf("[DEBUG] Cobbler System: syncing system")
if err := config.cobblerClient.Sync(); err != nil {
return fmt.Errorf("Cobbler System: Error syncing system: %s", err)
}
return resourceSystemRead(d, meta)
}
func resourceSystemRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
// Retrieve the system entry from Cobbler
system, err := config.cobblerClient.GetSystem(d.Id())
if err != nil {
return fmt.Errorf("Cobbler System: Error Reading (%s): %s", d.Id(), err)
}
// Set all fields
d.Set("boot_files", system.BootFiles)
d.Set("comment", system.Comment)
d.Set("enable_gpxe", system.EnableGPXE)
d.Set("fetchable_files", system.FetchableFiles)
d.Set("gateway", system.Gateway)
d.Set("hostname", system.Hostname)
d.Set("image", system.Image)
d.Set("ipv6_default_device", system.IPv6DefaultDevice)
d.Set("kernel_options", system.KernelOptions)
d.Set("kernel_options_post", system.KernelOptionsPost)
d.Set("kickstart", system.Kickstart)
d.Set("ks_meta", system.KSMeta)
d.Set("ldap_enabled", system.LDAPEnabled)
d.Set("ldap_type", system.LDAPType)
d.Set("mgmt_classes", system.MGMTClasses)
d.Set("mgmt_parameters", system.MGMTParameters)
d.Set("monit_enabled", system.MonitEnabled)
d.Set("name_servers_search", system.NameServersSearch)
d.Set("name_servers", system.NameServers)
d.Set("netboot_enabled", system.NetbootEnabled)
d.Set("owners", system.Owners)
d.Set("power_address", system.PowerAddress)
d.Set("power_id", system.PowerID)
d.Set("power_pass", system.PowerPass)
d.Set("power_type", system.PowerType)
d.Set("power_user", system.PowerUser)
d.Set("profile", system.Profile)
d.Set("proxy", system.Proxy)
d.Set("redhat_management_key", system.RedHatManagementKey)
d.Set("redhat_management_server", system.RedHatManagementServer)
d.Set("status", system.Status)
d.Set("template_files", system.TemplateFiles)
d.Set("template_remote_kickstarts", system.TemplateRemoteKickstarts)
d.Set("virt_auto_boot", system.VirtAutoBoot)
d.Set("virt_file_size", system.VirtFileSize)
d.Set("virt_cpus", system.VirtCPUs)
d.Set("virt_type", system.VirtType)
d.Set("virt_path", system.VirtPath)
d.Set("virt_pxe_boot", system.VirtPXEBoot)
d.Set("virt_ram", system.VirtRam)
d.Set("virt_disk_driver", system.VirtDiskDriver)
// Get all interfaces that the System has
allInterfaces, err := system.GetInterfaces()
if err != nil {
return fmt.Errorf("Cobbler System %s: Error getting interfaces: %s", system.Name, err)
}
// Build a generic map array with the interface attributes
var systemInterfaces []map[string]interface{}
for interfaceName, interfaceInfo := range allInterfaces {
iface := make(map[string]interface{})
iface["name"] = interfaceName
iface["cnames"] = interfaceInfo.CNAMEs
iface["dhcp_tag"] = interfaceInfo.DHCPTag
iface["dns_name"] = interfaceInfo.DNSName
iface["bonding_opts"] = interfaceInfo.BondingOpts
iface["bridge_opts"] = interfaceInfo.BridgeOpts
iface["gateway"] = interfaceInfo.Gateway
iface["interface_type"] = interfaceInfo.InterfaceType
iface["interface_master"] = interfaceInfo.InterfaceMaster
iface["ip_address"] = interfaceInfo.IPAddress
iface["ipv6_address"] = interfaceInfo.IPv6Address
iface["ipv6_secondaries"] = interfaceInfo.IPv6Secondaries
iface["ipv6_mtu"] = interfaceInfo.IPv6MTU
iface["ipv6_static_routes"] = interfaceInfo.IPv6StaticRoutes
iface["ipv6_default_gateway"] = interfaceInfo.IPv6DefaultGateway
iface["mac_address"] = interfaceInfo.MACAddress
iface["management"] = interfaceInfo.Management
iface["netmask"] = interfaceInfo.Netmask
iface["static"] = interfaceInfo.Static
iface["static_Routes"] = interfaceInfo.StaticRoutes
iface["virt_bridge"] = interfaceInfo.VirtBridge
systemInterfaces = append(systemInterfaces, iface)
}
d.Set("interface", systemInterfaces)
return nil
}
func resourceSystemUpdate(d *schema.ResourceData, meta interface{}) error {
systemSyncLock.Lock()
defer systemSyncLock.Unlock()
config := meta.(*Config)
// Retrieve the existing system entry from Cobbler
system, err := config.cobblerClient.GetSystem(d.Id())
if err != nil {
return fmt.Errorf("Cobbler System: Error Reading (%s): %s", d.Id(), err)
}
// Get a list of the old interfaces
currentInterfaces, err := system.GetInterfaces()
if err != nil {
return fmt.Errorf("Error getting interfaces: %s", err)
}
log.Printf("[DEBUG] Cobbler System Interfaces: %#v", currentInterfaces)
// Create a new cobblerclient.System struct with the new information
newSystem := buildSystem(d)
// Attempt to update the system with new information
log.Printf("[DEBUG] Cobbler System: Updating System (%s) with options: %+v", d.Id(), system)
err = config.cobblerClient.UpdateSystem(&newSystem)
if err != nil {
return fmt.Errorf("Cobbler System: Error Updating (%s): %s", d.Id(), err)
}
if d.HasChange("interface") {
oldInterfaces, newInterfaces := d.GetChange("interface")
oldInterfacesSet := oldInterfaces.(*schema.Set)
newInterfacesSet := newInterfaces.(*schema.Set)
interfacesToRemove := oldInterfacesSet.Difference(newInterfacesSet)
oldIfaces := buildSystemInterfaces(interfacesToRemove)
newIfaces := buildSystemInterfaces(newInterfacesSet)
for interfaceName, interfaceInfo := range oldIfaces {
if _, ok := newIfaces[interfaceName]; !ok {
// Interface does not exist in the new set,
// so it has been removed from terraform.
log.Printf("[DEBUG] Cobbler System: Deleting Interface %#v: %#v", interfaceName, interfaceInfo)
if err := system.DeleteInterface(interfaceName); err != nil {
return fmt.Errorf("Cobbler System: Error deleting Interface %s to %s: %s", interfaceName, system.Name, err)
}
}
}
// Modify interfaces that have changed
for interfaceName, interfaceInfo := range newIfaces {
log.Printf("[DEBUG] Cobbler System: New Interface %#v: %#v", interfaceName, interfaceInfo)
if err := system.CreateInterface(interfaceName, interfaceInfo); err != nil {
return fmt.Errorf("Cobbler System: Error adding Interface %s to %s: %s", interfaceName, system.Name, err)
}
}
}
log.Printf("[DEBUG] Cobbler System: syncing system")
if err := config.cobblerClient.Sync(); err != nil {
return fmt.Errorf("Cobbler System: Error syncing system: %s", err)
}
return resourceSystemRead(d, meta)
}
func resourceSystemDelete(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
// Attempt to delete the system
if err := config.cobblerClient.DeleteSystem(d.Id()); err != nil {
return fmt.Errorf("Cobbler System: Error Deleting (%s): %s", d.Id(), err)
}
return nil
}
// buildSystem builds a cobblerclient.System out of the Terraform attributes
func buildSystem(d *schema.ResourceData) cobbler.System {
mgmtClasses := []string{}
for _, i := range d.Get("mgmt_classes").([]interface{}) {
mgmtClasses = append(mgmtClasses, i.(string))
}
nameServersSearch := []string{}
for _, i := range d.Get("name_servers_search").([]interface{}) {
nameServersSearch = append(nameServersSearch, i.(string))
}
nameServers := []string{}
for _, i := range d.Get("name_servers").([]interface{}) {
nameServers = append(nameServers, i.(string))
}
owners := []string{}
for _, i := range d.Get("owners").([]interface{}) {
owners = append(owners, i.(string))
}
system := cobbler.System{
BootFiles: d.Get("boot_files").(string),
Comment: d.Get("comment").(string),
EnableGPXE: d.Get("enable_gpxe").(bool),
FetchableFiles: d.Get("fetchable_files").(string),
Gateway: d.Get("gateway").(string),
Hostname: d.Get("hostname").(string),
Image: d.Get("image").(string),
IPv6DefaultDevice: d.Get("ipv6_default_device").(string),
KernelOptions: d.Get("kernel_options").(string),
KernelOptionsPost: d.Get("kernel_options_post").(string),
Kickstart: d.Get("kickstart").(string),
KSMeta: d.Get("ks_meta").(string),
LDAPEnabled: d.Get("ldap_enabled").(bool),
LDAPType: d.Get("ldap_type").(string),
MGMTClasses: mgmtClasses,
MGMTParameters: d.Get("mgmt_parameters").(string),
MonitEnabled: d.Get("monit_enabled").(bool),
Name: d.Get("name").(string),
NameServersSearch: nameServersSearch,
NameServers: nameServers,
NetbootEnabled: d.Get("netboot_enabled").(bool),
Owners: owners,
PowerAddress: d.Get("power_address").(string),
PowerID: d.Get("power_id").(string),
PowerPass: d.Get("power_pass").(string),
PowerType: d.Get("power_type").(string),
PowerUser: d.Get("power_user").(string),
Profile: d.Get("profile").(string),
Proxy: d.Get("proxy").(string),
RedHatManagementKey: d.Get("redhat_management_key").(string),
RedHatManagementServer: d.Get("redhat_management_server").(string),
Status: d.Get("status").(string),
TemplateFiles: d.Get("template_files").(string),
TemplateRemoteKickstarts: d.Get("template_remote_kickstarts").(int),
VirtAutoBoot: d.Get("virt_auto_boot").(string),
VirtFileSize: d.Get("virt_file_size").(string),
VirtCPUs: d.Get("virt_cpus").(string),
VirtType: d.Get("virt_type").(string),
VirtPath: d.Get("virt_path").(string),
VirtPXEBoot: d.Get("virt_pxe_boot").(int),
VirtRam: d.Get("virt_ram").(string),
VirtDiskDriver: d.Get("virt_disk_driver").(string),
}
return system
}
// buildSystemInterface builds a cobblerclient.Interface out of the Terraform attributes
func buildSystemInterfaces(systemInterfaces *schema.Set) cobbler.Interfaces {
interfaces := make(cobbler.Interfaces)
rawInterfaces := systemInterfaces.List()
for _, rawInterface := range rawInterfaces {
rawInterfaceMap := rawInterface.(map[string]interface{})
cnames := []string{}
for _, i := range rawInterfaceMap["cnames"].([]interface{}) {
cnames = append(cnames, i.(string))
}
ipv6Secondaries := []string{}
for _, i := range rawInterfaceMap["ipv6_secondaries"].([]interface{}) {
ipv6Secondaries = append(ipv6Secondaries, i.(string))
}
ipv6StaticRoutes := []string{}
for _, i := range rawInterfaceMap["ipv6_static_routes"].([]interface{}) {
ipv6StaticRoutes = append(ipv6StaticRoutes, i.(string))
}
staticRoutes := []string{}
for _, i := range rawInterfaceMap["static_routes"].([]interface{}) {
staticRoutes = append(staticRoutes, i.(string))
}
interfaceName := rawInterfaceMap["name"].(string)
interfaces[interfaceName] = cobbler.Interface{
CNAMEs: cnames,
DHCPTag: rawInterfaceMap["dhcp_tag"].(string),
DNSName: rawInterfaceMap["dns_name"].(string),
BondingOpts: rawInterfaceMap["bonding_opts"].(string),
BridgeOpts: rawInterfaceMap["bridge_opts"].(string),
Gateway: rawInterfaceMap["gateway"].(string),
InterfaceType: rawInterfaceMap["interface_type"].(string),
InterfaceMaster: rawInterfaceMap["interface_master"].(string),
IPAddress: rawInterfaceMap["ip_address"].(string),
IPv6Address: rawInterfaceMap["ipv6_address"].(string),
IPv6Secondaries: ipv6Secondaries,
IPv6MTU: rawInterfaceMap["ipv6_mtu"].(string),
IPv6StaticRoutes: ipv6StaticRoutes,
IPv6DefaultGateway: rawInterfaceMap["ipv6_default_gateway"].(string),
MACAddress: rawInterfaceMap["mac_address"].(string),
Management: rawInterfaceMap["management"].(bool),
Netmask: rawInterfaceMap["netmask"].(string),
Static: rawInterfaceMap["static"].(bool),
StaticRoutes: staticRoutes,
VirtBridge: rawInterfaceMap["virt_bridge"].(string),
}
}
return interfaces
}
func resourceSystemInterfaceHash(v interface{}) int {
var buf bytes.Buffer
m := v.(map[string]interface{})
buf.WriteString(fmt.Sprintf("%s", m["name"].(string)))
if v, ok := m["cnames"]; ok {
for _, x := range v.([]interface{}) {
buf.WriteString(fmt.Sprintf("%v-", x.(string)))
}
}
if v, ok := m["dhcp_tag"]; ok {
buf.WriteString(fmt.Sprintf("%v-", v.(string)))
}
if v, ok := m["dns_name"]; ok {
buf.WriteString(fmt.Sprintf("%v-", v.(string)))
}
if v, ok := m["bonding_opts"]; ok {
buf.WriteString(fmt.Sprintf("%v-", v.(string)))
}
if v, ok := m["bridge_opts"]; ok {
buf.WriteString(fmt.Sprintf("%v-", v.(string)))
}
if v, ok := m["gateway"]; ok {
buf.WriteString(fmt.Sprintf("%v-", v.(string)))
}
if v, ok := m["interface_type"]; ok {
buf.WriteString(fmt.Sprintf("%v-", v.(string)))
}
if v, ok := m["interface_master"]; ok {
buf.WriteString(fmt.Sprintf("%v-", v.(string)))
}
if v, ok := m["ip_address"]; ok {
buf.WriteString(fmt.Sprintf("%v-", v.(string)))
}
if v, ok := m["ipv6_address"]; ok {
buf.WriteString(fmt.Sprintf("%v-", v.(string)))
}
if v, ok := m["ipv6_secondaries"]; ok {
for _, x := range v.([]interface{}) {
buf.WriteString(fmt.Sprintf("%v-", x.(string)))
}
}
if v, ok := m["ipv6_mtu"]; ok {
buf.WriteString(fmt.Sprintf("%v-", v.(string)))
}
if v, ok := m["ipv6_static_routes"]; ok {
for _, x := range v.([]interface{}) {
buf.WriteString(fmt.Sprintf("%v-", x.(string)))
}
}
if v, ok := m["ipv6_default_gateway"]; ok {
buf.WriteString(fmt.Sprintf("%v-", v.(string)))
}
if v, ok := m["mac_address"]; ok {
buf.WriteString(fmt.Sprintf("%v-", v.(string)))
}
if v, ok := m["management"]; ok {
buf.WriteString(fmt.Sprintf("%v-", v.(bool)))
}
if v, ok := m["netmask"]; ok {
buf.WriteString(fmt.Sprintf("%v-", v.(string)))
}
if v, ok := m["static"]; ok {
buf.WriteString(fmt.Sprintf("%v-", v.(bool)))
}
if v, ok := m["static_Routes"]; ok {
for _, x := range v.([]interface{}) {
buf.WriteString(fmt.Sprintf("%v-", x.(string)))
}
}
if v, ok := m["virt_bridge"]; ok {
buf.WriteString(fmt.Sprintf("%v-", v.(string)))
}
return hashcode.String(buf.String())
}