diff --git a/cmd/selenoid.go b/cmd/selenoid.go index d39f621..0eac65c 100644 --- a/cmd/selenoid.go +++ b/cmd/selenoid.go @@ -32,6 +32,7 @@ func init() { initFlags() selenoidCmd.AddCommand(selenoidDownloadCmd) + selenoidCmd.AddCommand(selenoidArgsCmd) selenoidCmd.AddCommand(selenoidConfigureCmd) selenoidCmd.AddCommand(selenoidStartCmd) selenoidCmd.AddCommand(selenoidStopCmd) @@ -40,6 +41,7 @@ func init() { selenoidCmd.AddCommand(selenoidStatusCmd) selenoidUICmd.AddCommand(selenoidDownloadUICmd) + selenoidUICmd.AddCommand(selenoidUIArgsCmd) selenoidUICmd.AddCommand(selenoidStartUICmd) selenoidUICmd.AddCommand(selenoidStopUICmd) selenoidUICmd.AddCommand(selenoidUpdateUICmd) @@ -50,6 +52,7 @@ func init() { func initFlags() { for _, c := range []*cobra.Command{ selenoidDownloadCmd, + selenoidArgsCmd, selenoidConfigureCmd, selenoidStartCmd, selenoidStopCmd, @@ -57,6 +60,7 @@ func initFlags() { selenoidCleanupCmd, selenoidStatusCmd, selenoidDownloadUICmd, + selenoidUIArgsCmd, selenoidStartUICmd, selenoidStopUICmd, selenoidUpdateUICmd, @@ -67,6 +71,7 @@ func initFlags() { } for _, c := range []*cobra.Command{ selenoidDownloadCmd, + selenoidArgsCmd, selenoidConfigureCmd, selenoidStartCmd, selenoidStopCmd, @@ -79,6 +84,7 @@ func initFlags() { } for _, c := range []*cobra.Command{ selenoidDownloadUICmd, + selenoidUIArgsCmd, selenoidStartUICmd, selenoidStopUICmd, selenoidUpdateUICmd, @@ -91,10 +97,12 @@ func initFlags() { for _, c := range []*cobra.Command{ selenoidDownloadCmd, + selenoidArgsCmd, selenoidConfigureCmd, selenoidStartCmd, selenoidUpdateCmd, selenoidDownloadUICmd, + selenoidUIArgsCmd, selenoidStartUICmd, selenoidUpdateUICmd, } { @@ -103,10 +111,12 @@ func initFlags() { } for _, c := range []*cobra.Command{ selenoidDownloadCmd, + selenoidArgsCmd, selenoidConfigureCmd, selenoidStartCmd, selenoidUpdateCmd, selenoidDownloadUICmd, + selenoidUIArgsCmd, selenoidStartUICmd, selenoidUpdateUICmd, } { @@ -128,9 +138,11 @@ func initFlags() { } for _, c := range []*cobra.Command{ selenoidDownloadCmd, + selenoidArgsCmd, selenoidConfigureCmd, selenoidStartCmd, selenoidDownloadUICmd, + selenoidUIArgsCmd, selenoidStartUICmd, } { c.Flags().BoolVarP(&force, "force", "f", false, "force action") diff --git a/cmd/selenoid_args.go b/cmd/selenoid_args.go new file mode 100644 index 0000000..8d72ab1 --- /dev/null +++ b/cmd/selenoid_args.go @@ -0,0 +1,32 @@ +package cmd + +import ( + "github.com/aerokube/cm/selenoid" + "github.com/spf13/cobra" + "os" +) + +var selenoidArgsCmd = &cobra.Command{ + Use: "args", + Short: "Shows Selenoid available args", + Run: func(cmd *cobra.Command, args []string) { + argsImpl(uiConfigDir, uiPort, func(lc *selenoid.Lifecycle) error { + return lc.PrintArgs() + }, force) + }, +} + +func argsImpl(configDir string, port uint16, argsAction func(*selenoid.Lifecycle) error, force bool) { + lifecycle, err := createLifecycle(configDir, port) + if err != nil { + stderr("Failed to initialize: %v\n", err) + os.Exit(1) + } + lifecycle.Force = force + err = argsAction(lifecycle) + if err != nil { + lifecycle.Errorf("Failed to print args: %v", err) + os.Exit(1) + } + os.Exit(0) +} diff --git a/cmd/selenoid_ui_args.go b/cmd/selenoid_ui_args.go new file mode 100644 index 0000000..06ef4d8 --- /dev/null +++ b/cmd/selenoid_ui_args.go @@ -0,0 +1,16 @@ +package cmd + +import ( + "github.com/aerokube/cm/selenoid" + "github.com/spf13/cobra" +) + +var selenoidUIArgsCmd = &cobra.Command{ + Use: "args", + Short: "Shows Selenoid UI available args", + Run: func(cmd *cobra.Command, args []string) { + argsImpl(uiConfigDir, uiPort, func(lc *selenoid.Lifecycle) error { + return lc.PrintUIArgs() + }, force) + }, +} diff --git a/docs/selenoid-commands.adoc b/docs/selenoid-commands.adoc index 5bcb1c3..6dab1f6 100644 --- a/docs/selenoid-commands.adoc +++ b/docs/selenoid-commands.adoc @@ -80,6 +80,7 @@ Supported commands are: |=== | Command | Meaning +| args | Print Selenoid command line arguments | cleanup | Removes Selenoid traces | configure | Creates Selenoid configuration file (implies download) | download | Downloads Selenoid binary or container image diff --git a/docs/selenoid-ui-commands.adoc b/docs/selenoid-ui-commands.adoc index 7e9ab01..34a9b21 100644 --- a/docs/selenoid-ui-commands.adoc +++ b/docs/selenoid-ui-commands.adoc @@ -15,6 +15,7 @@ Selenoid UI configuration algorithm is similar to Selenoid one - it is started e |=== | Command | Meaning +| args | Print Selenoid UI command line arguments | cleanup | Removes Selenoid UI traces | download | Downloads Selenoid UI binary or container image | start | Starts Selenoid UI process or container (implies download) diff --git a/selenoid/base.go b/selenoid/base.go index e307d6e..6baa991 100644 --- a/selenoid/base.go +++ b/selenoid/base.go @@ -11,11 +11,16 @@ import ( colorable "github.com/mattn/go-colorable" ) -type StatusAware interface { +type StatusProvider interface { Status() UIStatus() } +type ArgsProvider interface { + PrintArgs() error + PrintUIArgs() error +} + type Downloadable interface { IsDownloaded() bool Download() (string, error) diff --git a/selenoid/docker.go b/selenoid/docker.go index 9ef22bf..d2419c4 100644 --- a/selenoid/docker.go +++ b/selenoid/docker.go @@ -37,6 +37,7 @@ import ( "github.com/aerokube/cm/render/rewriter" dc "github.com/aerokube/util/docker" "github.com/fatih/color" + "io" "net/url" . "vbom.ml/util/sortorder" ) @@ -585,6 +586,14 @@ func (c *DockerConfigurator) getContainer(name string, port int) *types.Containe return nil } +func (c *DockerConfigurator) PrintArgs() error { + image := c.getSelenoidImage() + if image == nil { + return errors.New("Selenoid image is not downloaded: this is probably a bug") + } + return c.startContainer("", image, 0, 0, []string{}, []string{}, []string{"--help"}, []string{}, true) +} + func (c *DockerConfigurator) Start() error { image := c.getSelenoidImage() if image == nil { @@ -622,7 +631,7 @@ func (c *DockerConfigurator) Start() error { if !strings.Contains(c.Env, "OVERRIDE_VIDEO_OUTPUT_DIR") { overrideEnv = append(overrideEnv, fmt.Sprintf("OVERRIDE_VIDEO_OUTPUT_DIR=%s", videoConfigDir)) } - return c.startContainer(selenoidContainerName, image, c.Port, SelenoidDefaultPort, volumes, []string{}, cmd, overrideEnv) + return c.startContainer(selenoidContainerName, image, c.Port, SelenoidDefaultPort, volumes, []string{}, cmd, overrideEnv, false) } func (c *DockerConfigurator) isDockerForWindows() bool { @@ -675,6 +684,14 @@ func chooseVolumeConfigDir(defaultConfigDir string, elem []string) string { return defaultConfigDir } +func (c *DockerConfigurator) PrintUIArgs() error { + image := c.getSelenoidUIImage() + if image == nil { + return errors.New("Selenoid UI image is not downloaded: this is probably a bug") + } + return c.startContainer("", image, 0, 0, []string{}, []string{}, []string{"--help"}, []string{}, true) +} + func (c *DockerConfigurator) StartUI() error { image := c.getSelenoidUIImage() if image == nil { @@ -693,7 +710,7 @@ func (c *DockerConfigurator) StartUI() error { } overrideEnv := strings.Fields(c.Env) - return c.startContainer(selenoidUIContainerName, image, c.Port, SelenoidUIDefaultPort, []string{}, links, cmd, overrideEnv) + return c.startContainer(selenoidUIContainerName, image, c.Port, SelenoidUIDefaultPort, []string{}, links, cmd, overrideEnv, false) } func validateEnviron(envs []string) []string { @@ -707,7 +724,7 @@ func validateEnviron(envs []string) []string { return validEnv } -func (c *DockerConfigurator) startContainer(name string, image *types.ImageSummary, hostPort int, servicePort int, volumes []string, links []string, cmd []string, envOverride []string) error { +func (c *DockerConfigurator) startContainer(name string, image *types.ImageSummary, hostPort int, servicePort int, volumes []string, links []string, cmd []string, envOverride []string, printLogs bool) error { ctx := context.Background() env := validateEnviron(os.Environ()) env = append(env, fmt.Sprintf("TZ=%s", time.Local)) @@ -717,34 +734,42 @@ func (c *DockerConfigurator) startContainer(name string, image *types.ImageSumma if !contains(env, dockerApiVersion) { env = append(env, fmt.Sprintf("%s=%s", dockerApiVersion, c.docker.ClientVersion())) } - hostPortString := strconv.Itoa(hostPort) servicePortString := strconv.Itoa(servicePort) port, err := nat.NewPort("tcp", servicePortString) if err != nil { return fmt.Errorf("failed to init port: %v", err) } - exposedPorts := map[nat.Port]struct{}{port: {}} - portBindings := nat.PortMap{} - portBindings[port] = []nat.PortBinding{{HostIP: "0.0.0.0", HostPort: hostPortString}} containerConfig := container.Config{ - Hostname: "localhost", - Image: image.RepoTags[0], - Env: env, - ExposedPorts: exposedPorts, + Hostname: "localhost", + Image: image.RepoTags[0], + Env: env, + } + if servicePort > 0 { + containerConfig.ExposedPorts = map[nat.Port]struct{}{port: {}} } if len(cmd) > 0 { containerConfig.Cmd = strslice.StrSlice(cmd) } + hostConfig := container.HostConfig{ + Binds: volumes, + Links: links, + } + if printLogs { + containerConfig.Tty = true + } else { + hostConfig.RestartPolicy = container.RestartPolicy{ + Name: "always", + } + } + if hostPort > 0 && servicePort > 0 { + hostPortString := strconv.Itoa(hostPort) + portBindings := nat.PortMap{} + portBindings[port] = []nat.PortBinding{{HostIP: "0.0.0.0", HostPort: hostPortString}} + hostConfig.PortBindings = portBindings + } ctr, err := c.docker.ContainerCreate(ctx, &containerConfig, - &container.HostConfig{ - Binds: volumes, - Links: links, - PortBindings: portBindings, - RestartPolicy: container.RestartPolicy{ - Name: "always", - }, - }, + &hostConfig, &network.NetworkingConfig{}, name) if err != nil { return fmt.Errorf("failed to create container: %v", err) @@ -754,6 +779,18 @@ func (c *DockerConfigurator) startContainer(name string, image *types.ImageSumma c.removeContainer(ctr.ID) return fmt.Errorf("failed to start container: %v", err) } + if printLogs { + defer c.removeContainer(ctr.ID) + r, err := c.docker.ContainerLogs(ctx, ctr.ID, types.ContainerLogsOptions{ + ShowStdout: true, + ShowStderr: true, + }) + if err != nil { + return fmt.Errorf("failed to read container logs: %v", err) + } + defer r.Close() + io.Copy(os.Stderr, r) + } return nil } diff --git a/selenoid/docker_test.go b/selenoid/docker_test.go index ab718c7..df275ed 100644 --- a/selenoid/docker_test.go +++ b/selenoid/docker_test.go @@ -158,6 +158,13 @@ func mux() http.Handler { w.WriteHeader(http.StatusNoContent) }, )) + mux.HandleFunc("/v1.29/containers/e90e34656806/logs", http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + w.Write([]byte("Some logs...\n")) + }, + )) mux.HandleFunc("/v1.29/containers/e90e34656806", http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) @@ -378,6 +385,7 @@ func TestDownload(t *testing.T) { ref, err := c.Download() AssertThat(t, ref, Not{nil}) AssertThat(t, err, Is{nil}) + AssertThat(t, c.PrintArgs(), Is{nil}) } func TestDownloadUI(t *testing.T) { @@ -396,6 +404,7 @@ func TestDownloadUI(t *testing.T) { ref, err := c.DownloadUI() AssertThat(t, ref, Not{nil}) AssertThat(t, err, Is{nil}) + AssertThat(t, c.PrintUIArgs(), Is{nil}) } func TestGetSelenoidImage(t *testing.T) { diff --git a/selenoid/drivers.go b/selenoid/drivers.go index cfe13b6..13b81f2 100644 --- a/selenoid/drivers.go +++ b/selenoid/drivers.go @@ -34,7 +34,6 @@ const ( owner = "aerokube" selenoidRepo = "selenoid" selenoidUIRepo = "selenoid-ui" - microsoftEdge = "MicrosoftEdge" ) type Browsers map[string]Browser @@ -567,6 +566,10 @@ func (d *DriversConfigurator) IsUIRunning() bool { return len(selenoidUIProcesses) > 0 } +func (d *DriversConfigurator) PrintArgs() error { + return runCommand(d.getSelenoidBinaryPath(), []string{"--help"}, []string{}) +} + func (d *DriversConfigurator) Start() error { args := []string{} overrideArgs := strings.Fields(d.Args) @@ -595,6 +598,10 @@ func contains(haystack []string, needle string) bool { return false } +func (d *DriversConfigurator) PrintUIArgs() error { + return runCommand(d.getSelenoidUIBinaryPath(), []string{"--help"}, []string{}) +} + func (d *DriversConfigurator) StartUI() error { args := strings.Fields(d.Args) if !contains(args, "-listen") { @@ -604,7 +611,7 @@ func (d *DriversConfigurator) StartUI() error { return runCommand(d.getSelenoidUIBinaryPath(), args, env) } -var killFunc func(os.Process) error = func(p os.Process) error { +var killFunc = func(p os.Process) error { return p.Kill() } diff --git a/selenoid/drivers_test.go b/selenoid/drivers_test.go index f812c27..ad44cb5 100644 --- a/selenoid/drivers_test.go +++ b/selenoid/drivers_test.go @@ -437,12 +437,14 @@ func TestStartStopProcess(t *testing.T) { AssertThat(t, configurator.Start(), Is{nil}) configurator.Status() AssertThat(t, configurator.Stop(), Is{nil}) + AssertThat(t, configurator.PrintArgs(), Is{nil}) lcConfig.Port = SelenoidUIDefaultPort AssertThat(t, configurator.IsUIRunning(), Is{false}) AssertThat(t, configurator.StartUI(), Is{nil}) configurator.UIStatus() AssertThat(t, configurator.StopUI(), Is{nil}) + AssertThat(t, configurator.PrintUIArgs(), Is{nil}) }) } diff --git a/selenoid/lifecycle.go b/selenoid/lifecycle.go index 817d557..e68bae7 100644 --- a/selenoid/lifecycle.go +++ b/selenoid/lifecycle.go @@ -37,7 +37,8 @@ type Lifecycle struct { Logger Forceable Config *LifecycleConfig - statusAware StatusAware + argsAware ArgsProvider + statusAware StatusProvider downloadable Downloadable configurable Configurable runnable Runnable @@ -56,6 +57,7 @@ func NewLifecycle(config *LifecycleConfig) (*Lifecycle, error) { if err != nil { return nil, err } + lc.argsAware = dockerCfg lc.statusAware = dockerCfg lc.downloadable = dockerCfg lc.configurable = dockerCfg @@ -64,6 +66,7 @@ func NewLifecycle(config *LifecycleConfig) (*Lifecycle, error) { } else { lc.Titlef("Docker is not supported - using binaries...") driversCfg := NewDriversConfigurator(config) + lc.argsAware = driversCfg lc.statusAware = driversCfg lc.downloadable = driversCfg lc.configurable = driversCfg @@ -129,6 +132,18 @@ func (l *Lifecycle) Configure() error { }) } +func (l *Lifecycle) PrintArgs() error { + return chain([]func() error{ + func() error { + return l.Download() + }, + func() error { + l.Titlef("Printing Selenoid args...") + return l.argsAware.PrintArgs() + }, + }) +} + func (l *Lifecycle) Start() error { return chain([]func() error{ func() error { @@ -158,6 +173,18 @@ func (l *Lifecycle) Start() error { }) } +func (l *Lifecycle) PrintUIArgs() error { + return chain([]func() error{ + func() error { + return l.DownloadUI() + }, + func() error { + l.Titlef("Printing Selenoid UI args...") + return l.argsAware.PrintUIArgs() + }, + }) +} + func (l *Lifecycle) StartUI() error { return chain([]func() error{ func() error { diff --git a/selenoid/lifecycle_test.go b/selenoid/lifecycle_test.go index ff28f11..66995c2 100644 --- a/selenoid/lifecycle_test.go +++ b/selenoid/lifecycle_test.go @@ -56,6 +56,14 @@ func (ms *MockStrategy) IsUIRunning() bool { return ms.isRunning } +func (ms *MockStrategy) PrintArgs() error { + return nil +} + +func (ms *MockStrategy) PrintUIArgs() error { + return nil +} + func (ms *MockStrategy) Start() error { return nil } @@ -100,6 +108,7 @@ func TestLifecycle(t *testing.T) { defer lc.Close() lc.Status() AssertThat(t, lc.Download(), Is{nil}) + AssertThat(t, lc.PrintArgs(), Is{nil}) AssertThat(t, lc.Configure(), Is{nil}) AssertThat(t, lc.Start(), Is{nil}) strategy.isRunning = true @@ -113,6 +122,7 @@ func createTestLifecycle(strategy MockStrategy) Lifecycle { Logger: Logger{Quiet: false}, Forceable: Forceable{Force: true}, Config: &LifecycleConfig{}, + argsAware: &strategy, statusAware: &strategy, downloadable: &strategy, configurable: &strategy, @@ -127,6 +137,7 @@ func TestUILifecycle(t *testing.T) { defer lc.Close() lc.UIStatus() AssertThat(t, lc.DownloadUI(), Is{nil}) + AssertThat(t, lc.PrintUIArgs(), Is{nil}) AssertThat(t, lc.StartUI(), Is{nil}) strategy.isRunning = true AssertThat(t, lc.StartUI(), Is{nil})