Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions pkg/configurer/enterpriselinux/el.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,9 @@ gpgkey=%s
if err := c.InstallPackage(h, "containerd.io"); err != nil {
return fmt.Errorf("package manager could not install containerd.io")
}
if err := c.InstallPackage(h, "docker-ee"); err != nil {
return fmt.Errorf("package manager could not install docker-ee")
mcrPackages := configurer.MCRPackages(engineConfig)
if err := c.InstallPackage(h, mcrPackages...); err != nil {
return fmt.Errorf("package manager could not install %s", strings.Join(mcrPackages, ", "))
}

if err := c.EnableMCR(h, engineConfig); err != nil {
Expand Down
23 changes: 23 additions & 0 deletions pkg/configurer/linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,29 @@ const (
SbinPath = `PATH=/usr/local/sbin:/usr/sbin:/sbin:$PATH`
)

const (
// mcrPackage is the Mirantis Container Runtime package name. It is identical
// across the rpm and deb repositories on repos.mirantis.com.
mcrPackage = "docker-ee"
// mcrCLIPackage is the docker CLI package name, likewise identical across
// rpm and deb.
mcrCLIPackage = "docker-ee-cli"
)

// MCRPackages returns the packages to install for MCR, in the order they should
// be passed to the package manager.
//
// The CLI is only listed when spec.mcr.installCLI is set. It is returned in the
// same slice rather than installed separately so that both land in a single
// package manager transaction and cannot be resolved to mismatched versions.
func MCRPackages(engineConfig commonconfig.MCRConfig) []string {
if engineConfig.InstallCLI {
return []string{mcrPackage, mcrCLIPackage}
}

return []string{mcrPackage}
}

var ErrLinuxMCRInstall = errors.New("failed to install MCR on linux")

// LinuxConfigurer is a generic linux host configurer.
Expand Down
37 changes: 37 additions & 0 deletions pkg/configurer/mcr_packages_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package configurer_test

import (
"testing"

"github.com/stretchr/testify/require"

"github.com/Mirantis/launchpad/pkg/configurer"
commonconfig "github.com/Mirantis/launchpad/pkg/product/common/config"
)

// TestMCRPackages covers the package set every Linux configurer installs.
//
// The runtime package lists the CLI only as a Recommends (a weak dependency),
// not a Requires/Depends, so the default must stay runtime-only: adding the CLI
// unconditionally would change what every existing cluster installs.
// spec.mcr.installCLI opts in for hosts where weak-dependency resolution is
// disabled or otherwise cannot be relied on. See PRODENG-3641.
func TestMCRPackages(t *testing.T) {
t.Run("default installs the runtime only", func(t *testing.T) {
require.Equal(t, []string{"docker-ee"}, configurer.MCRPackages(commonconfig.MCRConfig{}))
})

t.Run("installCLI adds the cli package", func(t *testing.T) {
require.Equal(t, []string{"docker-ee", "docker-ee-cli"},
configurer.MCRPackages(commonconfig.MCRConfig{InstallCLI: true}))
})

t.Run("both packages are returned together for a single transaction", func(t *testing.T) {
// Returned as one slice, and the runtime stays first, so callers hand both
// to the package manager in one invocation. Installing them separately
// would allow the runtime and CLI to resolve to mismatched versions.
pkgs := configurer.MCRPackages(commonconfig.MCRConfig{InstallCLI: true})
require.Len(t, pkgs, 2)
require.Equal(t, "docker-ee", pkgs[0])
})
}
5 changes: 3 additions & 2 deletions pkg/configurer/sles/sles.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,9 @@ func (c Configurer) InstallMCR(h os.Host, engineConfig commonconfig.MCRConfig) e
if err := h.Exec("zypper -n install -y --allow-vendor-change containerd.io", exec.Sudo(h)); err != nil {
return fmt.Errorf("package manager could not install containerd.io: %w", err)
}
if err := h.Exec("zypper -n install -y --allow-vendor-change docker-ee", exec.Sudo(h)); err != nil {
return fmt.Errorf("package manager could not install docker-ee: %w", err)
mcrPackages := configurer.MCRPackages(engineConfig)
if err := h.Exec("zypper -n install -y --allow-vendor-change "+strings.Join(mcrPackages, " "), exec.Sudo(h)); err != nil {
return fmt.Errorf("package manager could not install %s: %w", strings.Join(mcrPackages, ", "), err)
}

if err := c.EnableMCR(h, engineConfig); err != nil {
Expand Down
6 changes: 4 additions & 2 deletions pkg/configurer/ubuntu/ubuntu.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package ubuntu

import (
"fmt"
"strings"

"github.com/Mirantis/launchpad/pkg/configurer"
commonconfig "github.com/Mirantis/launchpad/pkg/product/common/config"
Expand Down Expand Up @@ -80,8 +81,9 @@ Signed-by: /usr/share/keyrings/mirantis-archive-keyring.gpg
if err := c.InstallPackage(h, "containerd.io"); err != nil {
return fmt.Errorf("package manager could not install containerd.io")
}
if err := c.InstallPackage(h, "docker-ee"); err != nil {
return fmt.Errorf("package manager could not install docker-ee")
mcrPackages := configurer.MCRPackages(engineConfig)
if err := c.InstallPackage(h, mcrPackages...); err != nil {
return fmt.Errorf("package manager could not install %s", strings.Join(mcrPackages, ", "))
}

if err := c.EnableMCR(h, engineConfig); err != nil {
Expand Down
19 changes: 19 additions & 0 deletions pkg/product/common/config/mcr_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,25 @@ type MCRConfig struct {
SwarmInstallFlags Flags `yaml:"swarmInstallFlags,omitempty,flow"`
SwarmUpdateCommands []string `yaml:"swarmUpdateCommands,omitempty,flow"`

// InstallCLI additionally installs the docker CLI package (docker-ee-cli)
// alongside the runtime, in the same package manager transaction so the two
// cannot end up at mismatched versions.
//
// The runtime package lists the CLI only as a Recommends (a weak
// dependency), not a Requires/Depends, on every repos.mirantis.com package
// checked -- rpm (dnf/yum) and deb (apt) alike. Package managers install
// Recommends by default, so this is normally not needed. It is needed when
// weak-dependency installation is disabled, which common hardening
// baselines do explicitly (dnf/yum: install_weak_deps=false; apt:
// APT::Install-Recommends "false"). Set it on hosts where that applies, or
// where the runtime has otherwise installed without the CLI. See
// PRODENG-3641.
//
// Linux only. Windows installs MCR through install.ps1, which ships the CLI
// as part of the same archive and has no separate package, so this value is
// ignored on Windows hosts.
InstallCLI bool `yaml:"installCLI,omitempty"`

Metadata *MCRMetadata `yaml:"-"`
}

Expand Down
32 changes: 32 additions & 0 deletions pkg/product/common/config/mcr_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,35 @@ func TestSwarmUpdateCommands(t *testing.T) {
require.Equal(t, 1, slices.Index(cfg.SwarmUpdateCommands, "command2"))
require.Equal(t, 2, slices.Index(cfg.SwarmUpdateCommands, "command3"))
}

func TestMCRConfig_InstallCLI(t *testing.T) {
// The yaml key is the user-facing contract: a mismatch here would silently
// ignore the setting and reintroduce PRODENG-3641 for anyone opting in.
for _, tc := range []struct {
name string
yaml string
expected bool
}{
{
name: "absent defaults to false",
yaml: "channel: stable",
expected: false,
},
{
name: "installCLI true is honoured",
yaml: "channel: stable\ninstallCLI: true",
expected: true,
},
{
name: "installCLI false is honoured",
yaml: "channel: stable\ninstallCLI: false",
expected: false,
},
} {
t.Run(tc.name, func(t *testing.T) {
cfg := commonconfig.MCRConfig{}
require.NoError(t, yaml.Unmarshal([]byte(tc.yaml), &cfg))
require.Equal(t, tc.expected, cfg.InstallCLI)
})
}
}