updated google driver; added StartDocker and StopDocker to driver

Signed-off-by: Evan Hazlett <ejhazlett@gmail.com>
This commit is contained in:
Evan Hazlett 2015-01-09 18:32:08 -08:00
parent 4534944f6a
commit 4a47ce01fe
13 changed files with 392 additions and 87 deletions

View File

@ -25,6 +25,9 @@ import (
_ "github.com/docker/machine/drivers/vmwarefusion" _ "github.com/docker/machine/drivers/vmwarefusion"
_ "github.com/docker/machine/drivers/vmwarevcloudair" _ "github.com/docker/machine/drivers/vmwarevcloudair"
_ "github.com/docker/machine/drivers/vmwarevsphere" _ "github.com/docker/machine/drivers/vmwarevsphere"
//_ "github.com/docker/machine/drivers/vmwarevcloudair"
//_ "github.com/docker/machine/drivers/vmwarevsphere"
"github.com/docker/machine/state" "github.com/docker/machine/state"
) )

View File

@ -26,6 +26,7 @@ const (
defaultInstanceType = "t2.micro" defaultInstanceType = "t2.micro"
defaultRootSize = 16 defaultRootSize = 16
ipRange = "0.0.0.0/0" ipRange = "0.0.0.0/0"
dockerConfigDir = "/etc/docker"
) )
type Driver struct { type Driver struct {
@ -380,6 +381,38 @@ func (d *Driver) Kill() error {
return nil return nil
} }
func (d *Driver) StartDocker() error {
log.Debug("Starting Docker...")
cmd, err := d.GetSSHCommand("sudo service docker start")
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
return nil
}
func (d *Driver) StopDocker() error {
log.Debug("Stopping Docker...")
cmd, err := d.GetSSHCommand("sudo service docker stop")
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
return nil
}
func (d *Driver) GetDockerConfigDir() string {
return dockerConfigDir
}
func (d *Driver) Upgrade() error { func (d *Driver) Upgrade() error {
sshCmd, err := d.GetSSHCommand("apt-get update && apt-get install -y lxc-docker") sshCmd, err := d.GetSSHCommand("apt-get update && apt-get install -y lxc-docker")
if err != nil { if err != nil {

View File

@ -20,6 +20,10 @@ import (
"github.com/docker/machine/state" "github.com/docker/machine/state"
) )
const (
dockerConfigDir = "/etc/docker"
)
type Driver struct { type Driver struct {
MachineName string MachineName string
SubscriptionID string SubscriptionID string
@ -508,6 +512,38 @@ func (driver *Driver) Kill() error {
return nil return nil
} }
func (d *Driver) StartDocker() error {
log.Debug("Starting Docker...")
cmd, err := d.GetSSHCommand("sudo service docker start")
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
return nil
}
func (d *Driver) StopDocker() error {
log.Debug("Stopping Docker...")
cmd, err := d.GetSSHCommand("sudo service docker stop")
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
return nil
}
func (d *Driver) GetDockerConfigDir() string {
return dockerConfigDir
}
func (driver *Driver) GetSSHCommand(args ...string) (*exec.Cmd, error) { func (driver *Driver) GetSSHCommand(args ...string) (*exec.Cmd, error) {
err := driver.setUserSubscription() err := driver.setUserSubscription()
if err != nil { if err != nil {

View File

@ -18,6 +18,10 @@ import (
"github.com/docker/machine/state" "github.com/docker/machine/state"
) )
const (
dockerConfigDir = "/etc/docker"
)
type Driver struct { type Driver struct {
AccessToken string AccessToken string
DropletID int DropletID int
@ -260,6 +264,38 @@ func (d *Driver) Kill() error {
return err return err
} }
func (d *Driver) StartDocker() error {
log.Debug("Starting Docker...")
cmd, err := d.GetSSHCommand("sudo service docker start")
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
return nil
}
func (d *Driver) StopDocker() error {
log.Debug("Stopping Docker...")
cmd, err := d.GetSSHCommand("sudo service docker stop")
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
return nil
}
func (d *Driver) GetDockerConfigDir() string {
return dockerConfigDir
}
func (d *Driver) Upgrade() error { func (d *Driver) Upgrade() error {
sshCmd, err := d.GetSSHCommand("apt-get update && apt-get install lxc-docker") sshCmd, err := d.GetSSHCommand("apt-get update && apt-get install lxc-docker")
if err != nil { if err != nil {

View File

@ -51,9 +51,18 @@ type Driver interface {
// Kill stops a host forcefully // Kill stops a host forcefully
Kill() error Kill() error
// RestartDocker restarts a Docker daemon on the machine
StartDocker() error
// RestartDocker restarts a Docker daemon on the machine
StopDocker() error
// Upgrade the version of Docker on the host to the latest version // Upgrade the version of Docker on the host to the latest version
Upgrade() error Upgrade() error
// GetDockerConfigDir returns the config directory for storing daemon configs
GetDockerConfigDir() string
// GetSSHCommand returns a command for SSH pointing at the correct user, host // GetSSHCommand returns a command for SSH pointing at the correct user, host
// and keys for the host with args appended. If no args are passed, it will // and keys for the host with args appended. If no args are passed, it will
// initiate an interactive SSH session as if SSH were passed no args. // initiate an interactive SSH session as if SSH were passed no args.

View File

@ -41,8 +41,7 @@ var (
`sudo mkdir -p /.docker/authorized-keys.d/ `sudo mkdir -p /.docker/authorized-keys.d/
sudo chown -R {{ .UserName }} /.docker sudo chown -R {{ .UserName }} /.docker
while [ -e /var/run/docker.pid ]; do sleep 1; done while [ -e /var/run/docker.pid ]; do sleep 1; done
sudo sed -i 's@DOCKER_OPTS=.*@DOCKER_OPTS=\"--auth=identity -H unix://var/run/docker.sock -H 0.0.0.0:2376\"@g' /etc/default/docker sudo curl -L -o /usr/bin/docker https://get.docker.com/builds/Linux/x86_64/docker-latest
sudo wget https://bfirsh.s3.amazonaws.com/docker/docker-1.3.1-dev-identity-auth -O /usr/bin/docker && sudo chmod +x /usr/bin/docker
`)) `))
) )

View File

@ -14,6 +14,10 @@ import (
"github.com/docker/machine/ssh" "github.com/docker/machine/ssh"
) )
const (
dockerConfigDir = "/etc/docker"
)
// Driver is a struct compatible with the docker.hosts.drivers.Driver interface. // Driver is a struct compatible with the docker.hosts.drivers.Driver interface.
type Driver struct { type Driver struct {
MachineName string MachineName string
@ -22,6 +26,8 @@ type Driver struct {
storePath string storePath string
UserName string UserName string
Project string Project string
CaCertPath string
PrivateKeyPath string
sshKeyPath string sshKeyPath string
publicSSHKeyPath string publicSSHKeyPath string
} }
@ -72,10 +78,12 @@ func GetCreateFlags() []cli.Flag {
} }
// NewDriver creates a Driver with the specified storePath. // NewDriver creates a Driver with the specified storePath.
func NewDriver(machineName string, storePath string) (drivers.Driver, error) { func NewDriver(machineName string, storePath string, caCert string, privateKey string) (drivers.Driver, error) {
return &Driver{ return &Driver{
MachineName: machineName, MachineName: machineName,
storePath: storePath, storePath: storePath,
CaCertPath: caCert,
PrivateKeyPath: privateKey,
sshKeyPath: path.Join(storePath, "id_rsa"), sshKeyPath: path.Join(storePath, "id_rsa"),
publicSSHKeyPath: path.Join(storePath, "id_rsa.pub"), publicSSHKeyPath: path.Join(storePath, "id_rsa.pub"),
}, nil }, nil
@ -225,6 +233,38 @@ func (driver *Driver) Kill() error {
return driver.Stop() return driver.Stop()
} }
func (d *Driver) StartDocker() error {
log.Debug("Starting Docker...")
cmd, err := d.GetSSHCommand("sudo service docker start")
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
return nil
}
func (d *Driver) StopDocker() error {
log.Debug("Stopping Docker...")
cmd, err := d.GetSSHCommand("sudo service docker stop")
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
return nil
}
func (d *Driver) GetDockerConfigDir() string {
return dockerConfigDir
}
// GetSSHCommand returns a command that will run over SSH on the GCE instance. // GetSSHCommand returns a command that will run over SSH on the GCE instance.
func (driver *Driver) GetSSHCommand(args ...string) (*exec.Cmd, error) { func (driver *Driver) GetSSHCommand(args ...string) (*exec.Cmd, error) {
ip, err := driver.GetIP() ip, err := driver.GetIP()

View File

@ -34,7 +34,7 @@ func GetCreateFlags() []cli.Flag {
} }
} }
func NewDriver(machineName string, storePath string) (drivers.Driver, error) { func NewDriver(machineName string, storePath string, caCert string, privateKey string) (drivers.Driver, error) {
return &Driver{}, nil return &Driver{}, nil
} }
@ -93,6 +93,18 @@ func (d *Driver) Kill() error {
return fmt.Errorf("hosts without a driver cannot be killed") return fmt.Errorf("hosts without a driver cannot be killed")
} }
func (d *Driver) StartDocker() error {
return fmt.Errorf("hosts without a driver cannot start docker")
}
func (d *Driver) StopDocker() error {
return fmt.Errorf("hosts without a driver cannot stop docker")
}
func (d *Driver) GetDockerConfigDir() string {
return ""
}
func (d *Driver) Upgrade() error { func (d *Driver) Upgrade() error {
return fmt.Errorf("hosts without a driver cannot be upgraded") return fmt.Errorf("hosts without a driver cannot be upgraded")
} }

View File

@ -26,6 +26,10 @@ import (
"github.com/docker/machine/state" "github.com/docker/machine/state"
) )
const (
dockerConfigDir = "/var/lib/boot2docker"
)
type Driver struct { type Driver struct {
MachineName string MachineName string
SSHPort int SSHPort int
@ -320,71 +324,50 @@ func (d *Driver) Create() error {
return err return err
} }
log.Debugf("Adding key to authorized-keys.d...") //log.Debugf("Adding key to authorized-keys.d...")
cmd, err := d.GetSSHCommand("sudo mkdir -p /var/lib/boot2docker/.docker && sudo chown -R docker /var/lib/boot2docker/.docker") //cmd, err := d.GetSSHCommand("sudo mkdir -p /var/lib/boot2docker/.docker && sudo chown -R docker /var/lib/boot2docker/.docker")
if err != nil { //if err != nil {
return err // return err
} //}
if err := cmd.Run(); err != nil { //if err := cmd.Run(); err != nil {
return err // return err
} //}
if err := drivers.AddPublicKeyToAuthorizedHosts(d, "/var/lib/boot2docker/.docker/authorized-keys.d"); err != nil { //if err := drivers.AddPublicKeyToAuthorizedHosts(d, "/var/lib/boot2docker/.docker/authorized-keys.d"); err != nil {
return err // return err
} //}
cmd, err = d.GetSSHCommand("if [ -e /var/run/docker.pid ]; then sudo /etc/init.d/docker stop; fi") //// HACK: configure docker to use persisted auth
if err != nil { //cmd, err = d.GetSSHCommand("echo DOCKER_TLS=no | sudo tee -a /var/lib/boot2docker/profile")
return err //if err != nil {
} // return err
if err := cmd.Run(); err != nil { //}
return err //if err := cmd.Run(); err != nil {
} // return err
//}
// HACK: configure docker to use persisted auth //extraArgs := `EXTRA_ARGS='--auth=identity
cmd, err = d.GetSSHCommand("echo DOCKER_TLS=no | sudo tee -a /var/lib/boot2docker/profile") //--auth-authorized-dir=/var/lib/boot2docker/.docker/authorized-keys.d
if err != nil { //--auth-known-hosts=/var/lib/boot2docker/.docker/known-hosts.json
return err //--identity=/var/lib/boot2docker/.docker/key.json
} //-H tcp://0.0.0.0:2376'`
if err := cmd.Run(); err != nil { //sshCmd := fmt.Sprintf("echo \"%s\" | sudo tee -a /var/lib/boot2docker/profile", extraArgs)
return err //cmd, err = d.GetSSHCommand(sshCmd)
} //if err != nil {
// return err
//}
//if err := cmd.Run(); err != nil {
// return err
//}
extraArgs := `EXTRA_ARGS='--auth=identity //cmd, err = d.GetSSHCommand("sudo /etc/init.d/docker restart")
--auth-authorized-dir=/var/lib/boot2docker/.docker/authorized-keys.d //if err != nil {
--auth-known-hosts=/var/lib/boot2docker/.docker/known-hosts.json // return err
--identity=/var/lib/boot2docker/.docker/key.json //}
-H tcp://0.0.0.0:2376'` //if err := cmd.Run(); err != nil {
sshCmd := fmt.Sprintf("echo \"%s\" | sudo tee -a /var/lib/boot2docker/profile", extraArgs) // return err
cmd, err = d.GetSSHCommand(sshCmd) //}
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
cmd, err = d.GetSSHCommand("sudo /etc/init.d/docker start")
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
cmd, err = d.GetSSHCommand(fmt.Sprintf(
"sudo hostname %s && echo \"%s\" | sudo tee /var/lib/boot2docker/etc/hostname",
d.MachineName,
d.MachineName,
))
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
return nil return nil
} }
@ -541,6 +524,38 @@ func (d *Driver) GetSSHCommand(args ...string) (*exec.Cmd, error) {
return ssh.GetSSHCommand("localhost", d.SSHPort, "docker", d.sshKeyPath(), args...), nil return ssh.GetSSHCommand("localhost", d.SSHPort, "docker", d.sshKeyPath(), args...), nil
} }
func (d *Driver) StartDocker() error {
log.Debug("Starting Docker...")
cmd, err := d.GetSSHCommand("sudo /etc/init.d/docker start")
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
return nil
}
func (d *Driver) StopDocker() error {
log.Debug("Stopping Docker...")
cmd, err := d.GetSSHCommand("sudo /etc/init.d/docker stop ; exit 0")
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
return nil
}
func (d *Driver) GetDockerConfigDir() string {
return dockerConfigDir
}
func (d *Driver) sshKeyPath() string { func (d *Driver) sshKeyPath() string {
return filepath.Join(d.storePath, "id_rsa") return filepath.Join(d.storePath, "id_rsa")
} }

View File

@ -28,6 +28,7 @@ import (
const ( const (
B2D_USER = "docker" B2D_USER = "docker"
B2D_PASS = "tcuser" B2D_PASS = "tcuser"
dockerConfigDir = "/var/lib/boot2docker"
) )
// Driver for VMware Fusion // Driver for VMware Fusion
@ -37,6 +38,8 @@ type Driver struct {
DiskSize int DiskSize int
ISO string ISO string
Boot2DockerURL string Boot2DockerURL string
CaCertPath string
PrivateKeyPath string
storePath string storePath string
} }
@ -78,8 +81,8 @@ func GetCreateFlags() []cli.Flag {
} }
} }
func NewDriver(machineName string, storePath string) (drivers.Driver, error) { func NewDriver(machineName string, storePath string, caCert string, privateKey string) (drivers.Driver, error) {
return &Driver{MachineName: machineName, storePath: storePath}, nil return &Driver{MachineName: machineName, storePath: storePath, CaCertPath: caCert, PrivateKeyPath: privateKey}, nil
} }
func (d *Driver) DriverName() string { func (d *Driver) DriverName() string {
@ -229,6 +232,13 @@ func (d *Driver) Create() error {
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
return err return err
} }
//cmd, err := d.GetSSHCommand("sudo /etc/init.d/docker restart; sleep 5")
//if err != nil {
// return err
//}
//if err := cmd.Run(); err != nil {
// return err
//}
return nil return nil
} }
@ -266,6 +276,38 @@ func (d *Driver) Kill() error {
return nil return nil
} }
func (d *Driver) StartDocker() error {
log.Debug("Starting Docker...")
cmd, err := d.GetSSHCommand("sudo /etc/init.d/docker start")
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
return nil
}
func (d *Driver) StopDocker() error {
log.Debug("Stopping Docker...")
cmd, err := d.GetSSHCommand("sudo /etc/init.d/docker stop ; exit 0")
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
return nil
}
func (d *Driver) GetDockerConfigDir() string {
return dockerConfigDir
}
func (d *Driver) Upgrade() error { func (d *Driver) Upgrade() error {
return nil return nil
} }

View File

@ -23,6 +23,10 @@ import (
"github.com/docker/machine/state" "github.com/docker/machine/state"
) )
const (
dockerConfigDir = "/etc/docker"
)
type Driver struct { type Driver struct {
UserName string UserName string
UserPassword string UserPassword string
@ -750,6 +754,38 @@ func (d *Driver) Kill() error {
} }
func (d *Driver) StartDocker() error {
log.Debug("Starting Docker...")
cmd, err := d.GetSSHCommand("sudo service docker start")
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
return nil
}
func (d *Driver) StopDocker() error {
log.Debug("Stopping Docker...")
cmd, err := d.GetSSHCommand("sudo service docker stop")
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
return nil
}
func (d *Driver) GetDockerConfigDir() string {
return dockerConfigDir
}
func (d *Driver) Upgrade() error { func (d *Driver) Upgrade() error {
// Stolen from DigitalOcean ;-) // Stolen from DigitalOcean ;-)
sshCmd, err := d.GetSSHCommand("apt-get update && apt-get install lxc-docker") sshCmd, err := d.GetSSHCommand("apt-get update && apt-get install lxc-docker")

View File

@ -28,6 +28,7 @@ const (
DATASTORE_DIR = "boot2docker-iso" DATASTORE_DIR = "boot2docker-iso"
B2D_ISO_NAME = "boot2docker.iso" B2D_ISO_NAME = "boot2docker.iso"
DEFAULT_CPU_NUMBER = 2 DEFAULT_CPU_NUMBER = 2
dockerConfigDir = "/var/lib/boot2docker"
) )
type Driver struct { type Driver struct {
@ -379,6 +380,38 @@ func (d *Driver) Kill() error {
return d.Stop() return d.Stop()
} }
func (d *Driver) StartDocker() error {
log.Debug("Starting Docker...")
cmd, err := d.GetSSHCommand("sudo /etc/init.d/docker start")
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
return nil
}
func (d *Driver) StopDocker() error {
log.Debug("Stopping Docker...")
cmd, err := d.GetSSHCommand("sudo /etc/init.d/docker stop")
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
return nil
}
func (d *Driver) GetDockerConfigDir() string {
return dockerConfigDir
}
func (d *Driver) Upgrade() error { func (d *Driver) Upgrade() error {
return fmt.Errorf("upgrade is not supported for vsphere driver at this moment") return fmt.Errorf("upgrade is not supported for vsphere driver at this moment")
} }

49
host.go
View File

@ -156,15 +156,11 @@ func (h *Host) ConfigureAuth() error {
caCertPath := filepath.Join(h.storePath, "ca.pem") caCertPath := filepath.Join(h.storePath, "ca.pem")
serverKeyPath := filepath.Join(h.storePath, "server-key.pem") serverKeyPath := filepath.Join(h.storePath, "server-key.pem")
cmd, err := d.GetSSHCommand("sudo stop docker") if err := d.StopDocker(); err != nil {
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err return err
} }
cmd, err = d.GetSSHCommand("sudo mkdir -p /etc/docker") cmd, err := d.GetSSHCommand(fmt.Sprintf("sudo mkdir -p %s", d.GetDockerConfigDir()))
if err != nil { if err != nil {
return err return err
} }
@ -177,18 +173,21 @@ func (h *Host) ConfigureAuth() error {
if err != nil { if err != nil {
return err return err
} }
machineCaCertPath := filepath.Join(d.GetDockerConfigDir(), "ca.pem")
serverCert, err := ioutil.ReadFile(serverCertPath) serverCert, err := ioutil.ReadFile(serverCertPath)
if err != nil { if err != nil {
return err return err
} }
machineServerCertPath := filepath.Join(d.GetDockerConfigDir(), "server.pem")
serverKey, err := ioutil.ReadFile(serverKeyPath) serverKey, err := ioutil.ReadFile(serverKeyPath)
if err != nil { if err != nil {
return err return err
} }
machineServerKeyPath := filepath.Join(d.GetDockerConfigDir(), "server-key.pem")
cmd, err = d.GetSSHCommand(fmt.Sprintf("echo \"%s\" | sudo tee -a /etc/docker/ca.pem", string(caCert))) cmd, err = d.GetSSHCommand(fmt.Sprintf("echo \"%s\" | sudo tee -a %s", string(caCert), machineCaCertPath))
if err != nil { if err != nil {
return err return err
} }
@ -196,7 +195,7 @@ func (h *Host) ConfigureAuth() error {
return err return err
} }
cmd, err = d.GetSSHCommand(fmt.Sprintf("echo \"%s\" | sudo tee -a /etc/docker/server-key.pem", string(serverKey))) cmd, err = d.GetSSHCommand(fmt.Sprintf("echo \"%s\" | sudo tee -a %s", string(serverKey), machineServerKeyPath))
if err != nil { if err != nil {
return err return err
} }
@ -204,7 +203,7 @@ func (h *Host) ConfigureAuth() error {
return err return err
} }
cmd, err = d.GetSSHCommand(fmt.Sprintf("echo \"%s\" | sudo tee -a /etc/docker/server.pem", string(serverCert))) cmd, err = d.GetSSHCommand(fmt.Sprintf("echo \"%s\" | sudo tee -a %s", string(serverCert), machineServerCertPath))
if err != nil { if err != nil {
return err return err
} }
@ -212,12 +211,28 @@ func (h *Host) ConfigureAuth() error {
return err return err
} }
cmd, err = d.GetSSHCommand(`echo 'export DOCKER_OPTS=" \ daemonOpts := fmt.Sprintf(`--tlsverify \
--tlsverify \ --tlsverify \
--tlscacert=/etc/docker/ca.pem \ --tlscacert=%s \
--tlskey=/etc/docker/server-key.pem \ --tlskey=%s \
--tlscert=/etc/docker/server.pem \ --tlscert=%s \
--host=unix:///var/run/docker.sock --host=tcp://0.0.0.0:2376"' | sudo tee -a /etc/default/docker`) --host=unix:///var/run/docker.sock --host=tcp://0.0.0.0:2376`, machineCaCertPath,
machineServerKeyPath, machineServerCertPath)
var (
daemonOptsCfg string
daemonCfg string
)
switch d.DriverName() {
case "virtualbox", "vmwarefusion", "vmwarevsphere":
daemonOptsCfg = filepath.Join(d.GetDockerConfigDir(), "profile")
daemonCfg = fmt.Sprintf("EXTRA_ARGS='%s'", daemonOpts)
default:
daemonOptsCfg = "/etc/default/docker"
daemonCfg = fmt.Sprintf("export DOCKER_OPTS='%s'", daemonOpts)
}
cmd, err = d.GetSSHCommand(fmt.Sprintf("echo \"%s\" | sudo tee -a %s", daemonCfg, daemonOptsCfg))
if err != nil { if err != nil {
return err return err
} }
@ -225,11 +240,7 @@ func (h *Host) ConfigureAuth() error {
return err return err
} }
cmd, err = d.GetSSHCommand("sudo start docker") if err := d.StartDocker(); err != nil {
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err return err
} }