From 6d1d6cc0b20eeb96169d7ec5d10a152f765773e9 Mon Sep 17 00:00:00 2001 From: Niall Crowe Date: Fri, 29 Apr 2022 11:42:20 +0100 Subject: [PATCH 1/8] play kube default log driver The default log driver is not used when using play kube without --log-driver. The LogDriver function needs to be called in order to use the default log driver. fixes #13781 Signed-off-by: Niall Crowe --- cmd/podman/common/create.go | 2 +- cmd/podman/common/create_opts.go | 2 +- cmd/podman/play/kube.go | 2 +- pkg/api/handlers/libpod/play.go | 10 ++++++++++ test/system/700-play.bats | 17 +++++++++++++++++ 5 files changed, 30 insertions(+), 3 deletions(-) diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go index 1c1a7c3e33..d28becc8a0 100644 --- a/cmd/podman/common/create.go +++ b/cmd/podman/common/create.go @@ -299,7 +299,7 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, logDriverFlagName := "log-driver" createFlags.StringVar( &cf.LogDriver, - logDriverFlagName, logDriver(), + logDriverFlagName, LogDriver(), "Logging driver for the container", ) _ = cmd.RegisterFlagCompletionFunc(logDriverFlagName, AutocompleteLogDriver) diff --git a/cmd/podman/common/create_opts.go b/cmd/podman/common/create_opts.go index 16f193b03c..c40d1ea515 100644 --- a/cmd/podman/common/create_opts.go +++ b/cmd/podman/common/create_opts.go @@ -530,7 +530,7 @@ func volumes() []string { return nil } -func logDriver() string { +func LogDriver() string { if !registry.IsRemote() { return containerConfig.Containers.LogDriver } diff --git a/cmd/podman/play/kube.go b/cmd/podman/play/kube.go index 40d14a609b..3be7396ce9 100644 --- a/cmd/podman/play/kube.go +++ b/cmd/podman/play/kube.go @@ -87,7 +87,7 @@ func init() { _ = kubeCmd.RegisterFlagCompletionFunc(staticIPFlagName, completion.AutocompleteNone) logDriverFlagName := "log-driver" - flags.StringVar(&kubeOptions.LogDriver, logDriverFlagName, "", "Logging driver for the container") + flags.StringVar(&kubeOptions.LogDriver, logDriverFlagName, common.LogDriver(), "Logging driver for the container") _ = kubeCmd.RegisterFlagCompletionFunc(logDriverFlagName, common.AutocompleteLogDriver) logOptFlagName := "log-opt" diff --git a/pkg/api/handlers/libpod/play.go b/pkg/api/handlers/libpod/play.go index ca9ada7615..b71afc28cf 100644 --- a/pkg/api/handlers/libpod/play.go +++ b/pkg/api/handlers/libpod/play.go @@ -70,6 +70,16 @@ func PlayKube(w http.ResponseWriter, r *http.Request) { password = authConf.Password } + logDriver := query.LogDriver + if logDriver == "" { + config, err := runtime.GetConfig() + if err != nil { + utils.Error(w, http.StatusInternalServerError, err) + return + } + query.LogDriver = config.Containers.LogDriver + } + containerEngine := abi.ContainerEngine{Libpod: runtime} options := entities.PlayKubeOptions{ Annotations: query.Annotations, diff --git a/test/system/700-play.bats b/test/system/700-play.bats index b0624cbf22..7988b26a40 100644 --- a/test/system/700-play.bats +++ b/test/system/700-play.bats @@ -278,3 +278,20 @@ status: {} run_podman 125 play kube - < $PODMAN_TMPDIR/test.yaml assert "$output" =~ "invalid annotation \"test\"=\"$RANDOMSTRING\"" "Expected to fail with annotation length greater than 63" } + +@test "podman play kube - default log driver" { + TESTDIR=$PODMAN_TMPDIR/testdir + mkdir -p $TESTDIR + echo "$testYaml" | sed "s|TESTDIR|${TESTDIR}|g" > $PODMAN_TMPDIR/test.yaml + # Get the default log driver + run_podman info --format "{{.Host.LogDriver}}" + default_driver=$output + + # Make sure that the default log driver is used + run_podman play kube $PODMAN_TMPDIR/test.yaml + run_podman inspect --format "{{.HostConfig.LogConfig.Type}}" test_pod-test + is "$output" "$default_driver" "play kube uses default log driver" + + run_podman stop -a -t 0 + run_podman pod rm -t 0 -f test_pod +} From 7b64cd7835f1bacd326096fc5e41d44957a79230 Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano Date: Wed, 4 May 2022 14:57:01 +0200 Subject: [PATCH 2/8] libpod: treat ECONNRESET as EOF when reading from the attach socket, treat ECONNRESET in the same way as EOF. [NO NEW TESTS NEEDED] Closes: https://github.com/containers/podman/issues/11446 Signed-off-by: Giuseppe Scrivano --- libpod/oci_attach_linux.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libpod/oci_attach_linux.go b/libpod/oci_attach_linux.go index c6af294d5b..06f8f8719e 100644 --- a/libpod/oci_attach_linux.go +++ b/libpod/oci_attach_linux.go @@ -9,6 +9,7 @@ import ( "net" "os" "path/filepath" + "syscall" "github.com/containers/common/pkg/config" "github.com/containers/podman/v4/libpod/define" @@ -259,7 +260,7 @@ func redirectResponseToOutputStreams(outputStream, errorStream io.Writer, writeO } } } - if er == io.EOF { + if errors.Is(er, io.EOF) || errors.Is(er, syscall.ECONNRESET) { break } if er != nil { From b2025c64f43e74c84fb928f07df7bab3f776fe2d Mon Sep 17 00:00:00 2001 From: Brent Baude Date: Tue, 3 May 2022 09:23:43 -0500 Subject: [PATCH 3/8] Add more unit tests Improve "code coverage" with more unit-tests. Signed-off-by: Brent Baude --- pkg/domain/utils/utils_test.go | 76 ++++++++++++++++ pkg/env/env_test.go | 162 +++++++++++++++++++++++++++++++++ 2 files changed, 238 insertions(+) create mode 100644 pkg/domain/utils/utils_test.go create mode 100644 pkg/env/env_test.go diff --git a/pkg/domain/utils/utils_test.go b/pkg/domain/utils/utils_test.go new file mode 100644 index 0000000000..952a4b5beb --- /dev/null +++ b/pkg/domain/utils/utils_test.go @@ -0,0 +1,76 @@ +package utils + +import ( + "net/url" + "sort" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestToLibpodFilters(t *testing.T) { + good := url.Values{} + good.Set("apple", "red") + good.Set("banana", "yellow") + good.Set("pear", "") + goodResult := []string{"apple=red", "banana=yellow", "pear="} + sort.Strings(goodResult) + + empty := url.Values{} + type args struct { + f url.Values + } + tests := []struct { + name string + args args + wantFilters []string + }{ + { + name: "GoodURLValue", + args: args{ + f: good, + }, + wantFilters: goodResult, + }, + { + name: "Empty", + args: args{ + f: empty, + }, + wantFilters: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.ElementsMatchf(t, ToLibpodFilters(tt.args.f), tt.wantFilters, "ToLibpodFilters() = %v, want %v", ToLibpodFilters(tt.args.f), tt.wantFilters) + }) + } +} + +func TestToURLValues(t *testing.T) { + good := url.Values{} + good.Set("apple", "red") + good.Set("banana", "yellow") + good.Set("pear", "") + goodResult := []string{"apple=red", "banana=yellow", "pear="} + + type args struct { + f []string + } + tests := []struct { + name string + args args + wantFilters url.Values + }{ + { + name: "Good", + args: args{goodResult}, + wantFilters: good, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.EqualValuesf(t, ToURLValues(tt.args.f), tt.wantFilters, "ToURLValues() = %v, want %v", ToURLValues(tt.args.f), tt.wantFilters) + }) + } +} diff --git a/pkg/env/env_test.go b/pkg/env/env_test.go new file mode 100644 index 0000000000..c77061ecf2 --- /dev/null +++ b/pkg/env/env_test.go @@ -0,0 +1,162 @@ +package env + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSlice(t *testing.T) { + goodMap := make(map[string]string, 0) + goodMap["apple"] = "red" + goodMap["banana"] = "yellow" + goodMap["pear"] = "" + goodResult := []string{"apple=red", "banana=yellow", "pear"} + type args struct { + m map[string]string + } + tests := []struct { + name string + args args + want []string + }{ + { + name: "Good", + args: args{ + m: goodMap, + }, + want: goodResult, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.ElementsMatchf(t, Slice(tt.args.m), tt.want, "Slice() = %v, want %v", Slice(tt.args.m), tt.want) + }) + } +} + +func TestJoin(t *testing.T) { + firstMap := make(map[string]string, 0) + firstMap["apple"] = "red" + secondMap := make(map[string]string, 0) + secondMap["banana"] = "yellow" + goodResult := make(map[string]string, 0) + goodResult["apple"] = "red" + goodResult["banana"] = "yellow" + overrideResult := make(map[string]string, 0) + overrideResult["apple"] = "green" + overrideResult["banana"] = "yellow" + overrideMap := make(map[string]string, 0) + overrideMap["banana"] = "yellow" + overrideMap["apple"] = "green" + type args struct { + base map[string]string + override map[string]string + } + tests := []struct { + name string + args args + want map[string]string + }{ + { + name: "GoodJoin", + args: args{ + base: firstMap, + override: secondMap, + }, + want: goodResult, + }, + { + name: "GoodOverride", + args: args{ + base: firstMap, + override: overrideMap, + }, + want: overrideResult, + }, + { + name: "EmptyOverride", + args: args{ + base: firstMap, + override: nil, + }, + want: firstMap, + }, + { + name: "EmptyBase", + args: args{ + base: nil, + override: firstMap, + }, + want: firstMap, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := Join(tt.args.base, tt.args.override) + assert.EqualValuesf(t, got, tt.want, "Join() = %v, want %v", got, tt.want) + }) + } +} + +func Test_parseEnv(t *testing.T) { + good := make(map[string]string) + + type args struct { + env map[string]string + line string + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "Good", + args: args{ + env: good, + line: "apple=red", + }, + wantErr: false, + }, + { + name: "GoodNoValue", + args: args{ + env: good, + line: "apple=", + }, + wantErr: false, + }, + { + name: "GoodNoKeyNoValue", + args: args{ + env: good, + line: "=", + }, + wantErr: true, + }, + { + name: "BadNoKey", + args: args{ + env: good, + line: "=foobar", + }, + wantErr: true, + }, + { + name: "BadOnlyDelim", + args: args{ + env: good, + line: "=", + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := parseEnv(tt.args.env, tt.args.line); (err != nil) != tt.wantErr { + t.Errorf("parseEnv() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} From a8b55a3b9f8f2395d0d8ac3a4112fb3ff30da630 Mon Sep 17 00:00:00 2001 From: cdoern Date: Thu, 28 Apr 2022 22:37:11 -0400 Subject: [PATCH 4/8] pass networks to container clone since the network config is a string map, json.unmarshal does not recognize the config and spec as the same entity, need to map this option manually resolves #13713 Signed-off-by: cdoern --- libpod/container.go | 14 ++++++++++++- pkg/domain/infra/abi/containers.go | 6 ++++++ pkg/specgen/generate/container.go | 2 ++ pkg/specgen/generate/container_create.go | 16 ++++++++------- test/e2e/container_clone_test.go | 26 ++++++++++++++++++++++++ 5 files changed, 56 insertions(+), 8 deletions(-) diff --git a/libpod/container.go b/libpod/container.go index 3e7ab7b0ac..457b290b72 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -288,6 +288,15 @@ func (c *Container) Config() *ContainerConfig { return nil } + if c != nil { + networks, err := c.networks() + if err != nil { + return nil + } + + returnConfig.Networks = networks + } + return returnConfig } @@ -1260,7 +1269,10 @@ func (c *Container) NetworkMode() string { // Unlocked accessor for networks func (c *Container) networks() (map[string]types.PerNetworkOptions, error) { - return c.runtime.state.GetNetworks(c) + if c != nil && c.runtime != nil && c.runtime.state != nil { // can fail if c.networks is called from the tests + return c.runtime.state.GetNetworks(c) + } + return nil, nil } // getInterfaceByName returns a formatted interface name for a given diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index 89b09bb1da..5ca678d6f0 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -1548,6 +1548,12 @@ func (ic *ContainerEngine) ContainerClone(ctx context.Context, ctrCloneOpts enti return nil, err } + if len(spec.Networks) > 0 && pod.SharesNet() { + logrus.Warning("resetting network config, cannot specify a network other than the pod's when sharing the net namespace") + spec.Networks = nil + spec.NetworkOptions = nil + } + allNamespaces := []struct { isShared bool value *specgen.Namespace diff --git a/pkg/specgen/generate/container.go b/pkg/specgen/generate/container.go index 831c1d7b9f..63caaa77c9 100644 --- a/pkg/specgen/generate/container.go +++ b/pkg/specgen/generate/container.go @@ -501,6 +501,8 @@ func ConfigToSpec(rt *libpod.Runtime, specg *specgen.SpecGenerator, contaierID s _, mounts := c.SortUserVolumes(c.Spec()) specg.Mounts = mounts specg.HostDeviceList = conf.DeviceHostSrc + specg.Networks = conf.Networks + mapSecurityConfig(conf, specg) if c.IsInfra() { // if we are creating this spec for a pod's infra ctr, map the compatible options diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index 8b9ed8ffe2..19a2b702c2 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -175,13 +175,15 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener return nil, nil, nil, errors.New("the given container could not be retrieved") } conf := c.Config() - out, err := json.Marshal(conf.Spec.Linux) - if err != nil { - return nil, nil, nil, err - } - err = json.Unmarshal(out, runtimeSpec.Linux) - if err != nil { - return nil, nil, nil, err + if conf != nil && conf.Spec != nil && conf.Spec.Linux != nil { + out, err := json.Marshal(conf.Spec.Linux) + if err != nil { + return nil, nil, nil, err + } + err = json.Unmarshal(out, runtimeSpec.Linux) + if err != nil { + return nil, nil, nil, err + } } if s.ResourceLimits != nil { switch { diff --git a/test/e2e/container_clone_test.go b/test/e2e/container_clone_test.go index da9b511e09..94ccd6ffe4 100644 --- a/test/e2e/container_clone_test.go +++ b/test/e2e/container_clone_test.go @@ -266,4 +266,30 @@ var _ = Describe("Podman container clone", func() { Expect(clone).ToNot(Exit(0)) }) + + It("podman container clone network passing", func() { + networkCreate := podmanTest.Podman([]string{"network", "create", "testing123"}) + networkCreate.WaitWithDefaultTimeout() + defer podmanTest.removeNetwork("testing123") + Expect(networkCreate).To(Exit(0)) + run := podmanTest.Podman([]string{"run", "--network", "bridge", "-dt", ALPINE}) + run.WaitWithDefaultTimeout() + Expect(run).To(Exit(0)) + + connect := podmanTest.Podman([]string{"network", "connect", "testing123", run.OutputToString()}) + connect.WaitWithDefaultTimeout() + Expect(connect).To(Exit(0)) + + clone := podmanTest.Podman([]string{"container", "clone", run.OutputToString()}) + clone.WaitWithDefaultTimeout() + Expect(clone).To(Exit(0)) + + inspect := podmanTest.Podman([]string{"inspect", clone.OutputToString()}) + inspect.WaitWithDefaultTimeout() + Expect(inspect).To(Exit(0)) + Expect(inspect.InspectContainerToJSON()[0].NetworkSettings.Networks).To(HaveLen(2)) + _, ok := inspect.InspectContainerToJSON()[0].NetworkSettings.Networks["testing123"] + Expect(ok).To(BeTrue()) + + }) }) From e87b07f7c7de9ed06519d92999836cf74ec68e36 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Thu, 5 May 2022 16:04:48 -0400 Subject: [PATCH 5/8] Release notes for v4.1.0 final Signed-off-by: Matthew Heon --- RELEASE_NOTES.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index eefb68a462..d4da948654 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -2,7 +2,7 @@ ## 4.1.0 ### Features -- Podman now supports Docker Compose v2.2 and higher ([#11822](https://github.com/containers/podman/issues/11822)). +- Podman now supports Docker Compose v2.2 and higher ([#11822](https://github.com/containers/podman/issues/11822)). Please note that it may be necessary to disable the use of Buildkit by setting the environment variable `DOCKER_BUILDKIT=0`. - A new container command has been added, `podman container clone`. This command makes a copy of an existing container, with the ability to change some settings (e.g. resource limits) while doing so. - A new machine command has been added, `podman machine inspect`. This command provides details on the configuration of machine VMs. - The `podman machine set` command can now change the CPUs, memory, and disk space available to machines after they were initially created, using the new `--cpus`, `--disk-size`, and `--memory` options ([#13633](https://github.com/containers/podman/issues/13633)). @@ -52,12 +52,14 @@ - Fixed a bug where containers created by the `podman play kube` command did not record the raw image name used to create containers. - Fixed a bug where VMs created by `podman machine` could not start containers which forwarded ports when run on a host with a proxy configured ([#13628](https://github.com/containers/podman/issues/13628)). - Fixed a bug where VMs created by the `podman machine` command could not be connected to when the username of the current user was sufficiently long ([#12751](https://github.com/containers/podman/issues/12751)). +- Fixed a bug where the `podman system reset` command on Linux did not fully remove virtual machines created by `podman machine`. - Fixed a bug where the `podman machine rm` command would error when removing a VM that was never started ([#13834](https://github.com/containers/podman/issues/13834)). - Fixed a bug where the remote Podman client's `podman manifest push` command could not push to registries that required authentication ([#13629](https://github.com/containers/podman/issues/13629)). - Fixed a bug where containers joining a pod with volumes did not have the pod's volumes added ([#13548](https://github.com/containers/podman/issues/13548)). - Fixed a bug where the `podman version --format` command could not return the OS of the server ([#13690](https://github.com/containers/podman/issues/13690)). - Fixed a bug where the `podman play kube` command would error when a volume specified by a `configMap` already existed ([#13715](https://github.com/containers/podman/issues/13715)). - Fixed a bug where the `podman play kube` command did not respect the `hostNetwork` setting in Pod YAML ([#14015](https://github.com/containers/podman/issues/14015)). +- Fixed a bug where the `podman play kube` command would, when the `--log-driver` flag was not specified, ignore Podman's default log driver ([#13781](https://github.com/containers/podman/issues/13781)). - Fixed a bug where the `podman generate kube` command could generate YAML with too-long labels ([#13962](https://github.com/containers/podman/issues/13962)). - Fixed a bug where the `podman logs --tail=1` command would fail when the log driver was `journald` and the container was restarted ([#13098](https://github.com/containers/podman/issues/13098)). - Fixed a bug where containers created from images with a healthcheck that did not specify an interval would never run their healthchecks ([#13912](https://github.com/containers/podman/issues/13912)). @@ -65,6 +67,7 @@ - Fixed a bug where the `--tls-verify option to the `remote Podman client's `podman build` command was nonfunctional. - Fixed a bug where the `podman pod inspect` command incorrectly reported whether the pod used the host's network ([#14028](https://github.com/containers/podman/issues/14028)). - Fixed a bug where Podman would, when run on WSL2, ports specified without an IP address (e.g. `-p 8080:8080`) would be bound to IPv6 addresses ([#12292](https://github.com/containers/podman/issues/12292)). +- Fixed a bug where the remote Podman client's `podman info` could report an incorrect path to the socket used to access the Podman service ([#12023](https://github.com/containers/podman/issues/12023)). ### API - Containers created via the Libpod Create API that set a memory limit, but not a swap limit, will automatically have a swap limit set ([#13145](https://github.com/containers/podman/issues/13145)). @@ -77,6 +80,10 @@ ### Misc - A number of dependencies have been pruned from the project, resulting in a significant reduction in the size of the Podman binary. - Using `podman play kube` on a YAML that only includes `configMap` objects (and no pods or deployments) now prints a much clearer error message. +- Updated Buildah to v1.26.1 +- Updated the containers/storage library to v1.40.2 +- Updated the containers/image library to v5.21.1 +- Updated the containers/common library to v0.48.0 ## 4.0.3 ### Security From e4b03902052294d4f342a185bb54702ed5bed8b1 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Thu, 5 May 2022 16:07:47 -0400 Subject: [PATCH 6/8] Bump to v4.1.0 Signed-off-by: Matthew Heon --- version/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version/version.go b/version/version.go index 5b2ca0d743..6555a5857a 100644 --- a/version/version.go +++ b/version/version.go @@ -27,7 +27,7 @@ const ( // NOTE: remember to bump the version at the top // of the top-level README.md file when this is // bumped. -var Version = semver.MustParse("4.1.0-dev") +var Version = semver.MustParse("4.1.0") // See https://docs.docker.com/engine/api/v1.40/ // libpod compat handlers are expected to honor docker API versions From 3b4de8efc33d3eba4c4f280487f5d9eacb852da4 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Thu, 5 May 2022 16:08:08 -0400 Subject: [PATCH 7/8] Bump to v4.1.1-dev Signed-off-by: Matthew Heon --- version/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version/version.go b/version/version.go index 6555a5857a..716c2837dd 100644 --- a/version/version.go +++ b/version/version.go @@ -27,7 +27,7 @@ const ( // NOTE: remember to bump the version at the top // of the top-level README.md file when this is // bumped. -var Version = semver.MustParse("4.1.0") +var Version = semver.MustParse("4.1.1-dev") // See https://docs.docker.com/engine/api/v1.40/ // libpod compat handlers are expected to honor docker API versions From ab4d33e146eab7cd685bb266e38ca62782642036 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Thu, 5 May 2022 17:14:34 -0400 Subject: [PATCH 8/8] Update README for 4.1.0 release Signed-off-by: Matthew Heon --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d8a29b6ff5..ce96350aac 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Podman (the POD MANager) is a tool for managing containers and images, volumes mounted into those containers, and pods made from groups of containers. Podman is based on libpod, a library for container lifecycle management that is also contained in this repository. The libpod library provides APIs for managing containers, pods, container images, and volumes. -* [Latest Version: 4.0.3](https://github.com/containers/podman/releases/tag/v4.0.3) +* [Latest Version: 4.1.0](https://github.com/containers/podman/releases/tag/v4.1.0) * Latest Remote client for Windows * Latest Remote client for macOS * Latest Static Remote client for Linux