// CheckAvailablePorts ensures that ports used by OpenShift are available on the Docker host func (c *ClientStartConfig) CheckAvailablePorts(out io.Writer) error { c.DNSPort = openshift.DefaultDNSPort err := c.OpenShiftHelper().TestPorts(openshift.AllPorts) if err == nil { return nil } if !openshift.IsPortsNotAvailableErr(err) { return err } unavailable := sets.NewInt(openshift.UnavailablePorts(err)...) if unavailable.HasAny(openshift.BasePorts...) { return errors.NewError("a port needed by OpenShift is not available").WithCause(err) } if unavailable.Has(openshift.DefaultDNSPort) { if unavailable.Has(openshift.AlternateDNSPort) { return errors.NewError("a port needed by OpenShift is not available").WithCause(err) } c.DNSPort = openshift.AlternateDNSPort fmt.Fprintf(out, "WARNING: Binding DNS on port %d instead of 53, which may not be resolvable from all clients.\n", openshift.AlternateDNSPort) } for _, port := range openshift.RouterPorts { if unavailable.Has(port) { fmt.Fprintf(out, "WARNING: Port %d is already in use and may cause routing issues for applications.\n", port) } } return nil }
// CheckAndPull checks whether a Docker image exists. If not, it pulls it. func (h *Helper) CheckAndPull(image string, out io.Writer) error { glog.V(5).Infof("Inspecting Docker image %q", image) imageMeta, err := h.client.InspectImage(image) if err == nil { glog.V(5).Infof("Image %q found: %#v", image, imageMeta) return nil } if err != docker.ErrNoSuchImage { return starterrors.NewError("unexpected error inspecting image %s", image).WithCause(err) } glog.V(5).Infof("Image %q not found. Pulling", image) fmt.Fprintf(out, "Pulling image %s\n", image) logProgress := func(s string) { fmt.Fprintf(out, "%s\n", s) } outputStream := imageprogress.NewPullWriter(logProgress) if glog.V(5) { outputStream = out } err = h.client.PullImage(docker.PullImageOptions{ Repository: image, RawJSONStream: bool(!glog.V(5)), OutputStream: outputStream, }, docker.AuthConfiguration{}) if err != nil { return starterrors.NewError("error pulling Docker image %s", image).WithCause(err) } fmt.Fprintf(out, "Image pull complete\n") return nil }
// Status prints the OpenShift cluster status func (c *ClientStatusConfig) Status(f *clientcmd.Factory, out io.Writer) error { dockerClient, _, err := getDockerClient(out, c.DockerMachine, false) if err != nil { return errors.ErrNoDockerClient(err) } helper := dockerhelper.NewHelper(dockerClient, nil) container, running, err := helper.GetContainerState(openshift.OpenShiftContainer) if err != nil { return errors.NewError("cannot get state of OpenShift container %s", openshift.OpenShiftContainer).WithCause(err) } if !running { return errors.NewError("OpenShift cluster is not running") } healthy, err := isHealthy(f) if err != nil { return err } if !healthy { return errors.NewError("OpenShift cluster health check failed") } config, err := openshift.GetConfigFromContainer(dockerClient) if err != nil { return err } fmt.Print(status(container, config)) return nil }
func instantiateTemplate(client client.Interface, mapper configcmd.Mapper, templateNamespace, templateName, targetNamespace string, params map[string]string) error { template, err := client.Templates(templateNamespace).Get(templateName) if err != nil { return errors.NewError("cannot retrieve template %q from namespace %q", templateName, templateNamespace).WithCause(err) } // process the template result, err := genappcmd.TransformTemplate(template, client, targetNamespace, params) if err != nil { return errors.NewError("cannot process template %s/%s", templateNamespace, templateName).WithCause(err) } // Create objects bulk := &configcmd.Bulk{ Mapper: mapper, Op: configcmd.Create, } itemsToCreate := &kapi.List{ Items: result.Objects, } if errs := bulk.Run(itemsToCreate, targetNamespace); len(errs) > 0 { err = kerrors.NewAggregate(errs) return errors.NewError("cannot create objects from template %s/%s", templateNamespace, templateName).WithCause(err) } return nil }
func (c *ClientStartConfig) determineIP(out io.Writer) (string, error) { if ip := net.ParseIP(c.PublicHostname); ip != nil && !ip.IsUnspecified() { fmt.Fprintf(out, "Using public hostname IP %s as the host IP\n", ip) return ip.String(), nil } if len(c.DockerMachine) > 0 { glog.V(2).Infof("Using docker machine %q to determine server IP", c.DockerMachine) ip, err := dockermachine.IP(c.DockerMachine) if err != nil { return "", errors.NewError("Could not determine IP address").WithCause(err).WithSolution("Ensure that docker-machine is functional.") } fmt.Fprintf(out, "Using docker-machine IP %s as the host IP\n", ip) return ip, nil } // First, try to get the host from the DOCKER_HOST if communicating via tcp var err error ip := c.DockerHelper().HostIP() if ip != "" { glog.V(2).Infof("Testing Docker host IP (%s)", ip) if err = c.OpenShiftHelper().TestIP(ip); err == nil { return ip, nil } } glog.V(2).Infof("Cannot use the Docker host IP(%s): %v", ip, err) // Next, use the the --print-ip output from openshift ip, err = c.OpenShiftHelper().ServerIP() if err == nil { glog.V(2).Infof("Testing openshift --print-ip (%s)", ip) if err = c.OpenShiftHelper().TestIP(ip); err == nil { return ip, nil } glog.V(2).Infof("OpenShift server ip test failed: %v", err) } glog.V(2).Infof("Cannot use OpenShift IP: %v", err) // Next, try other IPs on Docker host ips, err := c.OpenShiftHelper().OtherIPs(ip) if err != nil { return "", err } for i := range ips { glog.V(2).Infof("Testing additional IP (%s)", ip) if err = c.OpenShiftHelper().TestIP(ips[i]); err == nil { return ip, nil } glog.V(2).Infof("OpenShift additional ip test failed: %v", err) } return "", errors.NewError("cannot determine an IP to use for your server.") }
// CheckAndPull checks whether a Docker image exists. If not, it pulls it. func (h *Helper) CheckAndPull(image string, out io.Writer) error { glog.V(5).Infof("Inspecting Docker image %q", image) imageMeta, err := h.client.InspectImage(image) if err == nil { glog.V(5).Infof("Image %q found: %#v", image, imageMeta) return nil } if err != docker.ErrNoSuchImage { return starterrors.NewError("unexpected error inspecting image %s", image).WithCause(err) } glog.V(5).Infof("Image %q not found. Pulling", image) fmt.Fprintf(out, "Pulling image %s\n", image) extracting := false var outputStream io.Writer writeProgress := func(r *pullprogress.ProgressReport) { if extracting { return } if r.Downloading == 0 && r.Waiting == 0 && r.Extracting > 0 { fmt.Fprintf(out, "Extracting\n") extracting = true return } plural := "s" if r.Downloading == 1 { plural = " " } fmt.Fprintf(out, "Downloading %d layer%s (%3.0f%%)", r.Downloading, plural, r.DownloadPct) if r.Waiting > 0 { fmt.Fprintf(out, ", %d waiting\n", r.Waiting) } else { fmt.Fprintf(out, "\n") } } if !glog.V(5) { outputStream = pullprogress.NewPullProgressWriter(writeProgress) } else { outputStream = out } err = h.client.PullImage(docker.PullImageOptions{ Repository: image, RawJSONStream: bool(!glog.V(5)), OutputStream: outputStream, }, docker.AuthConfiguration{}) if err != nil { return starterrors.NewError("error pulling Docker image %s", image).WithCause(err) } fmt.Fprintf(out, "Image pull comlete\n") return nil }
func (h *Helper) TestIP(ip string) error { // Start test server on host id, err := h.runHelper.New().Image(h.image). Privileged(). HostNetwork(). Entrypoint("socat"). Command("TCP-LISTEN:8443,crlf,reuseaddr,fork", "SYSTEM:\"echo 'hello world'\"").Start() if err != nil { return errors.NewError("cannnot start simple server on Docker host").WithCause(err) } defer func() { errors.LogError(h.dockerHelper.StopAndRemoveContainer(id)) }() // Attempt to connect to test container testHost := fmt.Sprintf("%s:8443", ip) glog.V(4).Infof("Attempting to dial %s", testHost) if err = cmdutil.WaitForSuccessfulDial(false, "tcp", testHost, 200*time.Millisecond, 1*time.Second, 10); err != nil { glog.V(2).Infof("Dial error: %v", err) return err } glog.V(4).Infof("Successfully dialed %s", testHost) return nil }
func (h *HostHelper) EnsureHostDirectories() error { // Attempt to create host directories only if they are // the default directories. If the user specifies them, then the // user is responsible for ensuring they exist, are mountable, etc. dirs := []string{} if h.configDir == DefaultConfigDir { dirs = append(dirs, path.Join("/rootfs", h.configDir)) } if h.volumesDir == DefaultVolumesDir { dirs = append(dirs, path.Join("/rootfs", h.volumesDir)) } if len(dirs) > 0 { cmd := fmt.Sprintf(cmdEnsureHostDirs, strings.Join(dirs, " ")) rc, err := h.runner(). Image(h.image). DiscardContainer(). Privileged(). Bind("/var:/rootfs/var"). Entrypoint("/bin/bash"). Command("-c", cmd).Run() if err != nil || rc != 0 { return errors.NewError("cannot create host volumes directory").WithCause(err) } } return nil }
// CreateProject creates a new project for the current user func (c *ClientStartConfig) CreateProject(out io.Writer) error { f, err := openshift.LoggedInUserFactory() if err != nil { return errors.NewError("cannot get logged in user client").WithCause(err) } return openshift.CreateProject(f, initialProjectName, initialProjectDisplay, initialProjectDesc, "oc", out) }
// InstallRegistry checks whether a registry is installed and installs one if not already installed func (h *Helper) InstallRegistry(kubeClient kclient.Interface, f *clientcmd.Factory, configDir, images string, out io.Writer) error { _, err := kubeClient.Services("default").Get(svcDockerRegistry) if err == nil { // If there's no error, the registry already exists return nil } if !apierrors.IsNotFound(err) { return errors.NewError("error retrieving docker registry service").WithCause(err) } imageTemplate := variable.NewDefaultImageTemplate() imageTemplate.Format = images cfg := ®istry.RegistryConfig{ Name: "registry", Type: "docker-registry", ImageTemplate: imageTemplate, Ports: "5000", Replicas: 1, Labels: "docker-registry=default", Volume: "/registry", ServiceAccount: "registry", } cmd := registry.NewCmdRegistry(f, "", "registry", out) output := &bytes.Buffer{} err = registry.RunCmdRegistry(f, cmd, output, cfg, []string{}) glog.V(4).Infof("Registry command output:\n%s", output.String()) return err }
func (h *Runner) startContainer(id string) error { err := h.client.StartContainer(id, nil) if err != nil { return errors.NewError("cannot start container %s", id).WithCause(err) } return nil }
// CheckAvailablePorts ensures that ports used by OpenShift are available on the Docker host func (c *ClientStartConfig) CheckAvailablePorts(out io.Writer) error { for _, port := range openshift.RouterPorts { err := c.OpenShiftHelper().TestPorts([]int{port}) if err != nil { fmt.Fprintf(out, "WARNING: Port %d is already in use and may cause routing issues for applications.\n", port) } } err := c.OpenShiftHelper().TestPorts(openshift.DefaultPorts) if err == nil { c.DNSPort = openshift.DefaultDNSPort return nil } if !openshift.IsPortsNotAvailableErr(err) { return err } conflicts := openshift.UnavailablePorts(err) if len(conflicts) == 1 && conflicts[0] == openshift.DefaultDNSPort { err = c.OpenShiftHelper().TestPorts(openshift.PortsWithAlternateDNS) if err == nil { c.DNSPort = openshift.AlternateDNSPort fmt.Fprintf(out, "WARNING: Binding DNS on port %d instead of 53, which may not be resolvable from all clients.\n", openshift.AlternateDNSPort) return nil } } return errors.NewError("a port needed by OpenShift is not available").WithCause(err) }
// CheckExistingOpenShiftContainer checks the state of an OpenShift container. If one // is already running, it throws an error. If one exists, it removes it so a new one // can be created. func (c *ClientStartConfig) CheckExistingOpenShiftContainer(out io.Writer) error { container, running, err := c.DockerHelper().GetContainerState(openshift.OpenShiftContainer) if err != nil { return errors.NewError("unexpected error while checking OpenShift container state").WithCause(err) } if running { return errors.NewError("OpenShift is already running").WithSolution("To start OpenShift again, stop the current cluster:\n$ %s\n", cmdutil.SiblingCommand(c.command, "down")) } if container != nil { err = c.DockerHelper().RemoveContainer(openshift.OpenShiftContainer) if err != nil { return errors.NewError("cannot delete existing OpenShift container").WithCause(err) } fmt.Fprintf(out, "Deleted existing OpenShift container\n") } return nil }
func (h *HostHelper) ensureVolumesDirShare() error { cmd := fmt.Sprintf(cmdCreateVolumesDirShare, h.volumesDir) rc, err := h.hostPidCmd(cmd) if err != nil || rc != 0 { return errors.NewError("cannot create volumes dir share").WithCause(err) } return nil }
// CheckExistingOpenShiftContainer checks the state of an OpenShift container. If one // is already running, it throws an error. If one exists, it removes it so a new one // can be created. func (c *ClientStartConfig) CheckExistingOpenShiftContainer(out io.Writer) error { exists, running, err := c.DockerHelper().GetContainerState(openShiftContainer) if err != nil { return errors.NewError("unexpected error while checking OpenShift container state").WithCause(err) } if running { return errors.NewError("OpenShift is already running").WithSolution("To start OpenShift again, stop current %q container.", openShiftContainer) } if exists { err = c.DockerHelper().RemoveContainer(openShiftContainer) if err != nil { return errors.NewError("cannot delete existing OpenShift container").WithCause(err) } fmt.Fprintf(out, "Deleted existing OpenShift container\n") } return nil }
func newRunError(rc int, cause error, stdOut, errOut []byte, config *docker.Config) error { return &runError{ error: errors.NewError("Docker run error rc=%d", rc).WithCause(cause), out: stdOut, err: errOut, config: config, } }
func exec(h *ExecHelper, cmd []string, stdIn io.Reader, stdOut, errOut io.Writer) error { glog.V(4).Infof("Remote exec on container: %s\nCommand: %v", h.container, cmd) exec, err := h.client.CreateExec(docker.CreateExecOptions{ AttachStdin: stdIn != nil, AttachStdout: true, AttachStderr: true, Cmd: cmd, Container: h.container, }) if err != nil { return errors.NewError("Cannot create exec for command %v on container %s", cmd, h.container).WithCause(err) } glog.V(5).Infof("Created exec %q", exec.ID) logOut, logErr := &bytes.Buffer{}, &bytes.Buffer{} outStream := io.MultiWriter(stdOut, logOut) errStream := io.MultiWriter(errOut, logErr) glog.V(5).Infof("Starting exec %q and blocking", exec.ID) err = h.client.StartExec(exec.ID, docker.StartExecOptions{ InputStream: stdIn, OutputStream: outStream, ErrorStream: errStream, }) if err != nil { return errors.NewError("Cannot start exec for command %v on container %s", cmd, h.container).WithCause(err) } if glog.V(5) { glog.Infof("Exec %q completed", exec.ID) if logOut.Len() > 0 { glog.Infof("Stdout:\n%s", logOut.String()) } if logErr.Len() > 0 { glog.Infof("Stderr:\n%s", logErr.String()) } } glog.V(5).Infof("Inspecting exec %q", exec.ID) info, err := h.client.InspectExec(exec.ID) if err != nil { return errors.NewError("Cannot inspect result of exec for command %v on container %s", cmd, h.container).WithCause(err) } glog.V(5).Infof("Exec %q info: %#v", exec.ID, info) if info.ExitCode != 0 { return newExecError(err, info.ExitCode, logOut.Bytes(), logErr.Bytes(), h.container, cmd) } return nil }
// DetermineServerIP gets an appropriate IP address to communicate with the OpenShift server func (c *ClientStartConfig) DetermineServerIP(out io.Writer) error { ip, err := c.determineIP(out) if err != nil { return errors.NewError("cannot determine a server IP to use").WithCause(err) } c.ServerIP = ip fmt.Fprintf(out, "Using %s as the server IP\n", ip) return nil }
// InstallMetrics checks whether metrics is installed and installs it if not already installed func (h *Helper) InstallMetrics(f *clientcmd.Factory, hostName, imagePrefix, imageVersion string) error { osClient, kubeClient, err := f.Clients() if err != nil { return errors.NewError("cannot obtain API clients").WithCause(err).WithDetails(h.OriginLog()) } _, err = kubeClient.Services(infraNamespace).Get(svcMetrics) if err == nil { // If there's no error, the metrics service already exists return nil } if !apierrors.IsNotFound(err) { return errors.NewError("error retrieving metrics service").WithCause(err).WithDetails(h.OriginLog()) } // Create metrics deployer service account routerSA := &kapi.ServiceAccount{} routerSA.Name = metricsDeployerSA _, err = kubeClient.ServiceAccounts(infraNamespace).Create(routerSA) if err != nil { return errors.NewError("cannot create metrics deployer service account").WithCause(err).WithDetails(h.OriginLog()) } // Add edit role to deployer service account if err = AddRoleToServiceAccount(osClient, "edit", metricsDeployerSA, infraNamespace); err != nil { return errors.NewError("cannot add edit role to metrics deployer service account").WithCause(err).WithDetails(h.OriginLog()) } // Add view role to the hawkular service account if err = AddRoleToServiceAccount(osClient, "view", "hawkular", infraNamespace); err != nil { return errors.NewError("cannot add view role to the hawkular service account").WithCause(err).WithDetails(h.OriginLog()) } // Add cluster reader role to heapster service account if err = AddClusterRole(osClient, "cluster-reader", "system:serviceaccount:openshift-infra:heapster"); err != nil { return errors.NewError("cannot add cluster reader role to heapster service account").WithCause(err).WithDetails(h.OriginLog()) } // Create metrics deployer secret deployerSecret := &kapi.Secret{} deployerSecret.Name = metricsDeployerSecret deployerSecret.Data = map[string][]byte{"nothing": []byte("/dev/null")} if _, err = kubeClient.Secrets(infraNamespace).Create(deployerSecret); err != nil { return errors.NewError("cannot create metrics deployer secret").WithCause(err).WithDetails(h.OriginLog()) } // Create deployer Pod deployerPod := metricsDeployerPod(hostName, imagePrefix, imageVersion) if _, err = kubeClient.Pods(infraNamespace).Create(deployerPod); err != nil { return errors.NewError("cannot create metrics deployer pod").WithCause(err).WithDetails(h.OriginLog()) } return nil }
func newExecError(cause error, rc int, stdOut, errOut []byte, container string, cmd []string) error { return &execError{ error: errors.NewError("Docker exec error").WithCause(cause), out: stdOut, err: errOut, container: container, cmd: cmd, rc: rc, } }
// RemoveContainer removes the container with the given id func (h *Helper) RemoveContainer(id string) error { glog.V(5).Infof("Removing container %q", id) err := h.client.RemoveContainer(docker.RemoveContainerOptions{ ID: id, }) if err != nil { return starterrors.NewError("cannot delete container %s", id).WithCause(err) } glog.V(5).Infof("Removed container %q", id) return nil }
func getDockerMachineClient(machine string, out io.Writer) (*docker.Client, *dockerclient.Client, error) { if !dockermachine.IsRunning(machine) { fmt.Fprintf(out, "Starting Docker machine '%s'\n", machine) err := dockermachine.Start(machine) if err != nil { return nil, nil, errors.NewError("cannot start Docker machine %q", machine).WithCause(err) } fmt.Fprintf(out, "Started Docker machine '%s'\n", machine) } return dockermachine.Client(machine) }
func (h *Helper) TestPorts(ports []int) error { portData, _, err := h.runHelper.New().Image(h.image). DiscardContainer(). Privileged(). HostNetwork(). HostPid(). Entrypoint("/bin/bash"). Command("-c", "cat /proc/net/tcp && ( [ -e /proc/net/tcp6 ] && cat /proc/net/tcp6 || true)"). CombinedOutput() if err != nil { return errors.NewError("Cannot get TCP port information from Kubernetes host").WithCause(err) } return checkPortsInUse(portData, ports) }
func (h *Helper) TestForwardedIP(ip string) error { // Start test server on host id, err := h.runHelper.New().Image(h.image). PortForward(8443, 8443). Entrypoint("socat"). Command("TCP-LISTEN:8443,crlf,reuseaddr,fork", "SYSTEM:\"echo 'hello world'\"").Start() if err != nil { return errors.NewError("cannnot start simple server on Docker host").WithCause(err) } defer func() { errors.LogError(h.dockerHelper.StopAndRemoveContainer(id)) }() return testIPDial(ip) }
func (c *ClientStartConfig) importObjects(out io.Writer, locations map[string]string) error { f, err := c.Factory() if err != nil { return err } for name, location := range locations { glog.V(2).Infof("Importing %s from %s", name, location) err = openshift.ImportObjects(f, openShiftNamespace, location) if err != nil { return errors.NewError("cannot import %s", name).WithCause(err).WithDetails(c.OpenShiftHelper().OriginLog()) } } return nil }
func (h *Runner) Create() (string, error) { glog.V(4).Infof("Creating container named %q\nconfig:\n%s\nhost config:\n%s\n", h.name, printConfig(h.config), printHostConfig(h.hostConfig)) container, err := h.client.CreateContainer(docker.CreateContainerOptions{ Name: h.name, Config: h.config, HostConfig: h.hostConfig, }) if err != nil { return "", errors.NewError("cannot create container using image %s", h.config.Image).WithCause(err) } glog.V(5).Infof("Container created with id %q", container.ID) glog.V(5).Infof("Container: %#v", container) return container.ID, nil }
func (h *HostHelper) ensureVolumesDir() error { cmd := fmt.Sprintf(cmdCreateVolumesDir, path.Join("/rootfs", h.volumesDir)) rc, err := h.runner(). Image(h.image). DiscardContainer(). Privileged(). Bind("/:/rootfs"). Entrypoint("/bin/bash"). Command("-c", cmd).Run() if err != nil || rc != 0 { return errors.NewError("cannot create host volumes directory").WithCause(err) } return nil }
// DetermineServerIP gets an appropriate IP address to communicate with the OpenShift server func (c *ClientStartConfig) DetermineServerIP(out io.Writer) error { ip, err := c.determineIP(out) if err != nil { return errors.NewError("cannot determine a server IP to use").WithCause(err) } if c.PortForwarding { c.ServerIP = "127.0.0.1" c.RouterIP = ip } else { c.ServerIP = ip c.RouterIP = ip } fmt.Fprintf(out, "Using %s as the server IP\n", c.ServerIP) return nil }
// InstallRegistry checks whether a registry is installed and installs one if not already installed func (h *Helper) InstallRegistry(kubeClient kclientset.Interface, f *clientcmd.Factory, configDir, images string, out, errout io.Writer) error { _, err := kubeClient.Core().Services(DefaultNamespace).Get(SvcDockerRegistry) if err == nil { // If there's no error, the registry already exists return nil } if !apierrors.IsNotFound(err) { return errors.NewError("error retrieving docker registry service").WithCause(err).WithDetails(h.OriginLog()) } imageTemplate := variable.NewDefaultImageTemplate() imageTemplate.Format = images opts := ®istry.RegistryOptions{ Config: ®istry.RegistryConfig{ Name: "registry", Type: "docker-registry", ImageTemplate: imageTemplate, Ports: "5000", Replicas: 1, Labels: "docker-registry=default", Volume: "/registry", ServiceAccount: "registry", }, } cmd := registry.NewCmdRegistry(f, "", "registry", out, errout) output := &bytes.Buffer{} err = opts.Complete(f, cmd, output, output, []string{}) if err != nil { return errors.NewError("error completing the registry configuration").WithCause(err) } err = opts.RunCmdRegistry() glog.V(4).Infof("Registry command output:\n%s", output.String()) if err != nil { return errors.NewError("cannot install registry").WithCause(err).WithDetails(h.OriginLog()) } return nil }
func (h *Helper) startSocatTunnel() error { // Previous process should have been killed with // 'oc cluster down', call again here in case it wasn't err := KillExistingSocat() if err != nil { glog.V(1).Infof("error: cannot kill socat: %v", err) } cmd := exec.Command("socat", "TCP-L:8443,reuseaddr,fork,backlog=20", "SYSTEM:\"docker exec -i origin socat - TCP\\:localhost\\:8443,nodelay\"") cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} err = cmd.Start() if err != nil { return errors.NewError("cannot start socat tunnel").WithCause(err) } glog.V(1).Infof("Started socat with pid: %d", cmd.Process.Pid) return SaveSocatPid(cmd.Process.Pid) }