From 67f8b19c9b2f07a54bca02d82295acfe1ea2fdd3 Mon Sep 17 00:00:00 2001 From: David Gageot Date: Wed, 30 Dec 2015 16:11:11 +0100 Subject: [PATCH 1/4] Don't use global variables. Make the Api closeable Signed-off-by: David Gageot --- commands/commands.go | 4 +-- libmachine/drivers/rpc/client_driver.go | 41 ++++++++++++++++++------- libmachine/libmachine.go | 2 -- libmachine/libmachinetest/fake_api.go | 4 +++ libmachine/persist/plugin_store.go | 25 ++++++++++++--- 5 files changed, 55 insertions(+), 21 deletions(-) diff --git a/commands/commands.go b/commands/commands.go index e35d164ce9..619e8b3b27 100644 --- a/commands/commands.go +++ b/commands/commands.go @@ -12,7 +12,6 @@ import ( "github.com/docker/machine/libmachine" "github.com/docker/machine/libmachine/cert" "github.com/docker/machine/libmachine/crashreport" - "github.com/docker/machine/libmachine/drivers/rpc" "github.com/docker/machine/libmachine/host" "github.com/docker/machine/libmachine/log" "github.com/docker/machine/libmachine/mcnutils" @@ -99,6 +98,7 @@ func runAction(actionName string, c CommandLine, api libmachine.API) error { func fatalOnError(command func(commandLine CommandLine, api libmachine.API) error) func(context *cli.Context) { return func(context *cli.Context) { api := libmachine.NewClient(mcndirs.GetBaseDir()) + defer api.Close() if context.GlobalBool("native-ssh") { api.SSHClientType = ssh.Native @@ -117,8 +117,6 @@ func fatalOnError(command func(commandLine CommandLine, api libmachine.API) erro mcnutils.GithubAPIToken = api.GithubAPIToken ssh.SetDefaultClient(api.SSHClientType) - defer rpcdriver.CloseDrivers() - if err := command(&contextCommandLine{context}, api); err != nil { log.Fatal(err) } diff --git a/libmachine/drivers/rpc/client_driver.go b/libmachine/drivers/rpc/client_driver.go index 03db4042b5..9920ac0c4f 100644 --- a/libmachine/drivers/rpc/client_driver.go +++ b/libmachine/drivers/rpc/client_driver.go @@ -6,6 +6,8 @@ import ( "sync" "time" + "io" + "github.com/docker/machine/libmachine/drivers" "github.com/docker/machine/libmachine/drivers/plugin/localbinary" "github.com/docker/machine/libmachine/log" @@ -16,10 +18,25 @@ import ( var ( heartbeatInterval = 5 * time.Second - openedDrivers = []*RPCClientDriver{} - openedDriversLock = &sync.Mutex{} ) +type RPCClientDriverFactory interface { + NewRPCClientDriver(driverName string, rawDriver []byte) (*RPCClientDriver, error) + io.Closer +} + +type DefaultRPCClientDriverFactory struct { + openedDrivers []*RPCClientDriver + openedDriversLock sync.Locker +} + +func NewRPCClientDriverFactory() RPCClientDriverFactory { + return &DefaultRPCClientDriverFactory{ + openedDrivers: []*RPCClientDriver{}, + openedDriversLock: &sync.Mutex{}, + } +} + type RPCClientDriver struct { plugin localbinary.DriverPlugin heartbeatDoneCh chan bool @@ -86,19 +103,21 @@ func NewInternalClient(rpcclient *rpc.Client) *InternalClient { } } -func CloseDrivers() { - openedDriversLock.Lock() - defer openedDriversLock.Unlock() +func (f *DefaultRPCClientDriverFactory) Close() error { + f.openedDriversLock.Lock() + defer f.openedDriversLock.Unlock() - for _, openedDriver := range openedDrivers { + for _, openedDriver := range f.openedDrivers { if err := openedDriver.close(); err != nil { log.Warnf("Error closing a plugin driver: %s", err) } } - openedDrivers = []*RPCClientDriver{} + f.openedDrivers = []*RPCClientDriver{} + + return nil } -func NewRPCClientDriver(driverName string, rawDriver []byte) (*RPCClientDriver, error) { +func (f *DefaultRPCClientDriverFactory) NewRPCClientDriver(driverName string, rawDriver []byte) (*RPCClientDriver, error) { mcnName := "" p, err := localbinary.NewPlugin(driverName) @@ -129,9 +148,9 @@ func NewRPCClientDriver(driverName string, rawDriver []byte) (*RPCClientDriver, heartbeatDoneCh: make(chan bool), } - openedDriversLock.Lock() - openedDrivers = append(openedDrivers, c) - openedDriversLock.Unlock() + f.openedDriversLock.Lock() + f.openedDrivers = append(f.openedDrivers, c) + f.openedDriversLock.Unlock() var serverVersion int if err := c.Client.Call(GetVersionMethod, struct{}{}, &serverVersion); err != nil { diff --git a/libmachine/libmachine.go b/libmachine/libmachine.go index 24bde9448b..e5daee91af 100644 --- a/libmachine/libmachine.go +++ b/libmachine/libmachine.go @@ -108,7 +108,6 @@ func (api *Client) Create(h *host.Host) error { } func (api *Client) performCreate(h *host.Host) error { - if err := h.Driver.Create(); err != nil { return fmt.Errorf("Error in driver during machine creation: %s", err) } @@ -150,7 +149,6 @@ func (api *Client) performCreate(h *host.Host) error { } return nil - } func sendCrashReport(err error, api *Client, host *host.Host) { diff --git a/libmachine/libmachinetest/fake_api.go b/libmachine/libmachinetest/fake_api.go index 9e6963c864..f8f32a3a9b 100644 --- a/libmachine/libmachinetest/fake_api.go +++ b/libmachine/libmachinetest/fake_api.go @@ -16,6 +16,10 @@ func (api *FakeAPI) NewPluginDriver(string, []byte) (drivers.Driver, error) { return nil, nil } +func (api *FakeAPI) Close() error { + return nil +} + func (api *FakeAPI) NewHost(driver drivers.Driver) (*host.Host, error) { return nil, nil } diff --git a/libmachine/persist/plugin_store.go b/libmachine/persist/plugin_store.go index 4a94f253c8..ef0cc19880 100644 --- a/libmachine/persist/plugin_store.go +++ b/libmachine/persist/plugin_store.go @@ -1,6 +1,8 @@ package persist import ( + "io" + "github.com/docker/machine/drivers/errdriver" "github.com/docker/machine/libmachine/drivers" "github.com/docker/machine/libmachine/drivers/plugin/localbinary" @@ -10,17 +12,26 @@ import ( type PluginDriverFactory interface { NewPluginDriver(driverName string, rawDriver []byte) (drivers.Driver, error) + io.Closer } -type RPCPluginDriverFactory struct{} +type RPCPluginDriverFactory struct { + rpcClientDriverFactory rpcdriver.RPCClientDriverFactory +} + +func NewPluginDriverFactory() *RPCPluginDriverFactory { + return &RPCPluginDriverFactory{ + rpcClientDriverFactory: rpcdriver.NewRPCClientDriverFactory(), + } +} type PluginStore struct { *Filestore PluginDriverFactory } -func (factory RPCPluginDriverFactory) NewPluginDriver(driverName string, rawDriver []byte) (drivers.Driver, error) { - d, err := rpcdriver.NewRPCClientDriver(driverName, rawDriver) +func (factory *RPCPluginDriverFactory) NewPluginDriver(driverName string, rawDriver []byte) (drivers.Driver, error) { + d, err := factory.rpcClientDriverFactory.NewRPCClientDriver(driverName, rawDriver) if err != nil { // Not being able to find a driver binary is a "known error" if _, ok := err.(localbinary.ErrPluginBinaryNotFound); ok { @@ -36,14 +47,18 @@ func (factory RPCPluginDriverFactory) NewPluginDriver(driverName string, rawDriv return d, nil } +func (factory *RPCPluginDriverFactory) Close() error { + return factory.rpcClientDriverFactory.Close() +} + func NewPluginStore(path, caCertPath, caPrivateKeyPath string) *PluginStore { return &PluginStore{ Filestore: NewFilestore(path, caCertPath, caPrivateKeyPath), - PluginDriverFactory: RPCPluginDriverFactory{}, + PluginDriverFactory: NewPluginDriverFactory(), } } -func (ps PluginStore) Load(name string) (*host.Host, error) { +func (ps *PluginStore) Load(name string) (*host.Host, error) { h, err := ps.Filestore.Load(name) if err != nil { return nil, err From 00eee7db1d409e14a4d1e8f3dbc64d45e07a8ffd Mon Sep 17 00:00:00 2001 From: David Gageot Date: Wed, 30 Dec 2015 16:42:45 +0100 Subject: [PATCH 2/4] Simplify Api Signed-off-by: David Gageot --- commands/create.go | 24 ++---- libmachine/examples/vbox_create.go | 7 +- libmachine/libmachine.go | 101 ++++++++++++++++++-------- libmachine/libmachinetest/fake_api.go | 2 +- libmachine/persist/plugin_store.go | 75 ------------------- 5 files changed, 77 insertions(+), 132 deletions(-) delete mode 100644 libmachine/persist/plugin_store.go diff --git a/commands/create.go b/commands/create.go index ab53bb5e62..ccb5d4e085 100644 --- a/commands/create.go +++ b/commands/create.go @@ -14,7 +14,6 @@ import ( "github.com/codegangsta/cli" "github.com/docker/machine/commands/mcndirs" - "github.com/docker/machine/drivers/errdriver" "github.com/docker/machine/libmachine" "github.com/docker/machine/libmachine/auth" "github.com/docker/machine/libmachine/drivers" @@ -152,12 +151,7 @@ func cmdCreateInner(c CommandLine, api libmachine.API) error { } driverName := c.String("driver") - driver, err := api.NewPluginDriver(driverName, rawDriver) - if err != nil { - return fmt.Errorf("Error loading driver %q: %s", driverName, err) - } - - h, err := api.NewHost(driver) + h, err := api.NewHost(driverName, rawDriver) if err != nil { return fmt.Errorf("Error getting new host: %s", err) } @@ -211,7 +205,7 @@ func cmdCreateInner(c CommandLine, api libmachine.API) error { // driverOpts is the actual data we send over the wire to set the // driver parameters (an interface fulfilling drivers.DriverOptions, // concrete type rpcdriver.RpcFlags). - mcnFlags := driver.GetCreateFlags() + mcnFlags := h.Driver.GetCreateFlags() driverOpts := getDriverOpts(c, mcnFlags) if err := h.Driver.SetConfigFromFlags(driverOpts); err != nil { @@ -287,13 +281,9 @@ func cmdCreateOuter(c CommandLine, api libmachine.API) error { return fmt.Errorf("Error attempting to marshal bare driver data: %s", err) } - driver, err := api.NewPluginDriver(driverName, rawDriver) + h, err := api.NewHost(driverName, rawDriver) if err != nil { - return fmt.Errorf("Error loading driver %q: %s", driverName, err) - } - - if _, ok := driver.(*errdriver.Driver); ok { - return errdriver.NotLoadable{Name: driverName} + return err } // TODO: So much flag manipulation and voodoo here, it seems to be @@ -301,7 +291,7 @@ func cmdCreateOuter(c CommandLine, api libmachine.API) error { // // mcnFlags is the data we get back over the wire (type mcnflag.Flag) // to indicate which parameters are available. - mcnFlags := driver.GetCreateFlags() + mcnFlags := h.Driver.GetCreateFlags() // This bit will actually make "create" display the correct flags based // on the requested driver. @@ -317,10 +307,6 @@ func cmdCreateOuter(c CommandLine, api libmachine.API) error { } } - if serialDriver, ok := driver.(*drivers.SerialDriver); ok { - driver = serialDriver.Driver - } - return c.Application().Run(os.Args) } diff --git a/libmachine/examples/vbox_create.go b/libmachine/examples/vbox_create.go index 96b67f38b8..70d32348f2 100644 --- a/libmachine/examples/vbox_create.go +++ b/libmachine/examples/vbox_create.go @@ -27,12 +27,7 @@ func main() { log.Fatal(err) } - pluginDriver, err := client.NewPluginDriver("virtualbox", data) - if err != nil { - log.Fatal(err) - } - - h, err := client.NewHost(pluginDriver) + h, err := client.NewHost("virtualbox", data) if err != nil { log.Fatal(err) } diff --git a/libmachine/libmachine.go b/libmachine/libmachine.go index e5daee91af..3c00dc7270 100644 --- a/libmachine/libmachine.go +++ b/libmachine/libmachine.go @@ -4,11 +4,16 @@ import ( "fmt" "path/filepath" + "io" + + "github.com/docker/machine/drivers/errdriver" "github.com/docker/machine/libmachine/auth" "github.com/docker/machine/libmachine/cert" "github.com/docker/machine/libmachine/check" "github.com/docker/machine/libmachine/crashreport" "github.com/docker/machine/libmachine/drivers" + "github.com/docker/machine/libmachine/drivers/plugin/localbinary" + "github.com/docker/machine/libmachine/drivers/rpc" "github.com/docker/machine/libmachine/engine" "github.com/docker/machine/libmachine/host" "github.com/docker/machine/libmachine/log" @@ -22,62 +27,92 @@ import ( ) type API interface { - persist.Store - persist.PluginDriverFactory - NewHost(drivers.Driver) (*host.Host, error) + io.Closer + NewHost(driverName string, rawDriver []byte) (*host.Host, error) Create(h *host.Host) error + persist.Store } type Client struct { - *persist.PluginStore IsDebug bool SSHClientType ssh.ClientType GithubAPIToken string + *persist.Filestore + clientDriverFactory rpcdriver.RPCClientDriverFactory } func NewClient(storePath string) *Client { certsDir := filepath.Join(storePath, ".docker", "machine", "certs") return &Client{ - IsDebug: false, - SSHClientType: ssh.External, - PluginStore: persist.NewPluginStore(storePath, certsDir, certsDir), + IsDebug: false, + SSHClientType: ssh.External, + Filestore: persist.NewFilestore(storePath, certsDir, certsDir), + clientDriverFactory: rpcdriver.NewRPCClientDriverFactory(), } } -func (api *Client) NewHost(driver drivers.Driver) (*host.Host, error) { - certDir := filepath.Join(api.Path, "certs") - - hostOptions := &host.Options{ - AuthOptions: &auth.Options{ - CertDir: certDir, - CaCertPath: filepath.Join(certDir, "ca.pem"), - CaPrivateKeyPath: filepath.Join(certDir, "ca-key.pem"), - ClientCertPath: filepath.Join(certDir, "cert.pem"), - ClientKeyPath: filepath.Join(certDir, "key.pem"), - ServerCertPath: filepath.Join(api.GetMachinesDir(), "server.pem"), - ServerKeyPath: filepath.Join(api.GetMachinesDir(), "server-key.pem"), - }, - EngineOptions: &engine.Options{ - InstallURL: "https://get.docker.com", - StorageDriver: "aufs", - TLSVerify: true, - }, - SwarmOptions: &swarm.Options{ - Host: "tcp://0.0.0.0:3376", - Image: "swarm:latest", - Strategy: "spread", - }, +func (api *Client) NewHost(driverName string, rawDriver []byte) (*host.Host, error) { + driver, err := api.clientDriverFactory.NewRPCClientDriver(driverName, rawDriver) + if err != nil { + return nil, err } + certDir := filepath.Join(api.Path, "certs") + return &host.Host{ ConfigVersion: version.ConfigVersion, Name: driver.GetMachineName(), Driver: driver, DriverName: driver.DriverName(), - HostOptions: hostOptions, + HostOptions: &host.Options{ + AuthOptions: &auth.Options{ + CertDir: certDir, + CaCertPath: filepath.Join(certDir, "ca.pem"), + CaPrivateKeyPath: filepath.Join(certDir, "ca-key.pem"), + ClientCertPath: filepath.Join(certDir, "cert.pem"), + ClientKeyPath: filepath.Join(certDir, "key.pem"), + ServerCertPath: filepath.Join(api.GetMachinesDir(), "server.pem"), + ServerKeyPath: filepath.Join(api.GetMachinesDir(), "server-key.pem"), + }, + EngineOptions: &engine.Options{ + InstallURL: "https://get.docker.com", + StorageDriver: "aufs", + TLSVerify: true, + }, + SwarmOptions: &swarm.Options{ + Host: "tcp://0.0.0.0:3376", + Image: "swarm:latest", + Strategy: "spread", + }, + }, }, nil } +func (api *Client) Load(name string) (*host.Host, error) { + h, err := api.Filestore.Load(name) + if err != nil { + return nil, err + } + + d, err := api.clientDriverFactory.NewRPCClientDriver(h.DriverName, h.RawDriver) + if err != nil { + // Not being able to find a driver binary is a "known error" + if _, ok := err.(localbinary.ErrPluginBinaryNotFound); ok { + h.Driver = errdriver.NewDriver(h.DriverName) + return h, nil + } + return nil, err + } + + if h.DriverName == "virtualbox" { + h.Driver = drivers.NewSerialDriver(d) + } else { + h.Driver = d + } + + return h, nil +} + // Create is the wrapper method which covers all of the boilerplate around // actually creating, provisioning, and persisting an instance in the store. func (api *Client) Create(h *host.Host) error { @@ -159,3 +194,7 @@ func sendCrashReport(err error, api *Client, host *host.Host) { crashreport.Send(err, "api.performCreate", host.DriverName, "Create") } } + +func (api *Client) Close() error { + return api.clientDriverFactory.Close() +} diff --git a/libmachine/libmachinetest/fake_api.go b/libmachine/libmachinetest/fake_api.go index f8f32a3a9b..7a6a47fa6c 100644 --- a/libmachine/libmachinetest/fake_api.go +++ b/libmachine/libmachinetest/fake_api.go @@ -20,7 +20,7 @@ func (api *FakeAPI) Close() error { return nil } -func (api *FakeAPI) NewHost(driver drivers.Driver) (*host.Host, error) { +func (api *FakeAPI) NewHost(driverName string, rawDriver []byte) (*host.Host, error) { return nil, nil } diff --git a/libmachine/persist/plugin_store.go b/libmachine/persist/plugin_store.go deleted file mode 100644 index ef0cc19880..0000000000 --- a/libmachine/persist/plugin_store.go +++ /dev/null @@ -1,75 +0,0 @@ -package persist - -import ( - "io" - - "github.com/docker/machine/drivers/errdriver" - "github.com/docker/machine/libmachine/drivers" - "github.com/docker/machine/libmachine/drivers/plugin/localbinary" - "github.com/docker/machine/libmachine/drivers/rpc" - "github.com/docker/machine/libmachine/host" -) - -type PluginDriverFactory interface { - NewPluginDriver(driverName string, rawDriver []byte) (drivers.Driver, error) - io.Closer -} - -type RPCPluginDriverFactory struct { - rpcClientDriverFactory rpcdriver.RPCClientDriverFactory -} - -func NewPluginDriverFactory() *RPCPluginDriverFactory { - return &RPCPluginDriverFactory{ - rpcClientDriverFactory: rpcdriver.NewRPCClientDriverFactory(), - } -} - -type PluginStore struct { - *Filestore - PluginDriverFactory -} - -func (factory *RPCPluginDriverFactory) NewPluginDriver(driverName string, rawDriver []byte) (drivers.Driver, error) { - d, err := factory.rpcClientDriverFactory.NewRPCClientDriver(driverName, rawDriver) - if err != nil { - // Not being able to find a driver binary is a "known error" - if _, ok := err.(localbinary.ErrPluginBinaryNotFound); ok { - return errdriver.NewDriver(driverName), nil - } - return nil, err - } - - if driverName == "virtualbox" { - return drivers.NewSerialDriver(d), nil - } - - return d, nil -} - -func (factory *RPCPluginDriverFactory) Close() error { - return factory.rpcClientDriverFactory.Close() -} - -func NewPluginStore(path, caCertPath, caPrivateKeyPath string) *PluginStore { - return &PluginStore{ - Filestore: NewFilestore(path, caCertPath, caPrivateKeyPath), - PluginDriverFactory: NewPluginDriverFactory(), - } -} - -func (ps *PluginStore) Load(name string) (*host.Host, error) { - h, err := ps.Filestore.Load(name) - if err != nil { - return nil, err - } - - d, err := ps.NewPluginDriver(h.DriverName, h.RawDriver) - if err != nil { - return nil, err - } - - h.Driver = d - - return h, nil -} From 263f8c5cddaef930427a1b1b1a5829b842a08620 Mon Sep 17 00:00:00 2001 From: David Gageot Date: Wed, 30 Dec 2015 17:19:11 +0100 Subject: [PATCH 3/4] Remove duplication Signed-off-by: David Gageot --- commands/commands.go | 2 +- libmachine/examples/vbox_create.go | 2 +- libmachine/libmachine.go | 17 ++++++++--------- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/commands/commands.go b/commands/commands.go index 619e8b3b27..0a625ac377 100644 --- a/commands/commands.go +++ b/commands/commands.go @@ -97,7 +97,7 @@ func runAction(actionName string, c CommandLine, api libmachine.API) error { func fatalOnError(command func(commandLine CommandLine, api libmachine.API) error) func(context *cli.Context) { return func(context *cli.Context) { - api := libmachine.NewClient(mcndirs.GetBaseDir()) + api := libmachine.NewClient(mcndirs.GetBaseDir(), mcndirs.GetMachineCertDir()) defer api.Close() if context.GlobalBool("native-ssh") { diff --git a/libmachine/examples/vbox_create.go b/libmachine/examples/vbox_create.go index 70d32348f2..9ae9f4b7de 100644 --- a/libmachine/examples/vbox_create.go +++ b/libmachine/examples/vbox_create.go @@ -13,7 +13,7 @@ import ( func main() { log.SetDebug(true) - client := libmachine.NewClient("/tmp/automatic") + client := libmachine.NewClient("/tmp/automatic", "/tmp/automatic/certs") hostName := "myfunhost" diff --git a/libmachine/libmachine.go b/libmachine/libmachine.go index 3c00dc7270..bd291f177d 100644 --- a/libmachine/libmachine.go +++ b/libmachine/libmachine.go @@ -34,6 +34,7 @@ type API interface { } type Client struct { + certsDir string IsDebug bool SSHClientType ssh.ClientType GithubAPIToken string @@ -41,9 +42,9 @@ type Client struct { clientDriverFactory rpcdriver.RPCClientDriverFactory } -func NewClient(storePath string) *Client { - certsDir := filepath.Join(storePath, ".docker", "machine", "certs") +func NewClient(storePath, certsDir string) *Client { return &Client{ + certsDir: certsDir, IsDebug: false, SSHClientType: ssh.External, Filestore: persist.NewFilestore(storePath, certsDir, certsDir), @@ -57,8 +58,6 @@ func (api *Client) NewHost(driverName string, rawDriver []byte) (*host.Host, err return nil, err } - certDir := filepath.Join(api.Path, "certs") - return &host.Host{ ConfigVersion: version.ConfigVersion, Name: driver.GetMachineName(), @@ -66,11 +65,11 @@ func (api *Client) NewHost(driverName string, rawDriver []byte) (*host.Host, err DriverName: driver.DriverName(), HostOptions: &host.Options{ AuthOptions: &auth.Options{ - CertDir: certDir, - CaCertPath: filepath.Join(certDir, "ca.pem"), - CaPrivateKeyPath: filepath.Join(certDir, "ca-key.pem"), - ClientCertPath: filepath.Join(certDir, "cert.pem"), - ClientKeyPath: filepath.Join(certDir, "key.pem"), + CertDir: api.certsDir, + CaCertPath: filepath.Join(api.certsDir, "ca.pem"), + CaPrivateKeyPath: filepath.Join(api.certsDir, "ca-key.pem"), + ClientCertPath: filepath.Join(api.certsDir, "cert.pem"), + ClientKeyPath: filepath.Join(api.certsDir, "key.pem"), ServerCertPath: filepath.Join(api.GetMachinesDir(), "server.pem"), ServerKeyPath: filepath.Join(api.GetMachinesDir(), "server-key.pem"), }, From 85405468de1d92ef2c5e38d34741a702ec8909fe Mon Sep 17 00:00:00 2001 From: David Gageot Date: Mon, 4 Jan 2016 14:58:28 +0100 Subject: [PATCH 4/4] Fix the vbox sample code Signed-off-by: David Gageot --- libmachine/examples/vbox_create.go | 1 + 1 file changed, 1 insertion(+) diff --git a/libmachine/examples/vbox_create.go b/libmachine/examples/vbox_create.go index 9ae9f4b7de..d84aefe369 100644 --- a/libmachine/examples/vbox_create.go +++ b/libmachine/examples/vbox_create.go @@ -14,6 +14,7 @@ func main() { log.SetDebug(true) client := libmachine.NewClient("/tmp/automatic", "/tmp/automatic/certs") + defer client.Close() hostName := "myfunhost"