diff --git a/examples/terraform/aws-simple/terraform.tfvars.template b/examples/terraform/aws-simple/terraform.tfvars.template index 117c9fa4..8280083e 100644 --- a/examples/terraform/aws-simple/terraform.tfvars.template +++ b/examples/terraform/aws-simple/terraform.tfvars.template @@ -8,7 +8,7 @@ aws = { launchpad = { drain = false - mcr_version = "25.0.13" + mcr_version = "25.0.14.2" mke_version = "3.8.8" msr_version = "2.9.28" @@ -32,7 +32,7 @@ subnets = { "main" = { "cidr" = "172.31.0.0/17", "private" = false, - "nodegroups" = ["MngrA", "WrkA", "MsrA"] + "nodegroups" = ["MngrA", "Wrkubuntu", "Wrkrhel", "Wrksles"] } } @@ -46,22 +46,30 @@ nodegroups = { "role" = "manager", "user_data" = "sudo ufw allow 7946/tcp ; sudo ufw allow 10250/tcp " }, - "WrkA" = { - "platform" = "ubuntu_22.04", + "Wrkrhel" = { + "platform" = "rhel-9", "count" = 1, "type" = "c6a.xlarge", "volume_size" = "100", "role" = "worker", "user_data" = "sudo ufw allow 7946/tcp ; sudo ufw allow 10250/tcp " - } - "MsrA" = { + }, + "Wrkubuntu" = { "platform" = "ubuntu_22.04", "count" = 1, "type" = "c6a.xlarge", "volume_size" = "100", - "role" = "msr", + "role" = "worker", "user_data" = "sudo ufw allow 7946/tcp ; sudo ufw allow 10250/tcp " - } + }, + "Wrksles" = { + "platform" = "sles-15", + "count" = 1, + "type" = "c6a.xlarge", + "volume_size" = "100", + "role" = "worker", + "user_data" = "sudo ufw allow 7946/tcp ; sudo ufw allow 10250/tcp " + }, } // set a windows password, if you have windows nodes diff --git a/jn-todo.org b/jn-todo.org new file mode 100644 index 00000000..477f4cb2 --- /dev/null +++ b/jn-todo.org @@ -0,0 +1,17 @@ +* JNesbitt informal todo list + +** TODO Move product/mke/api/host.go:Host.MCRConfigure to /pkg/configurer interfaces + +The MCRConfigure() method likely exists on the host as a convenience, but as we have common +configurer functionality and the other MCR methods are on the configurer it doesn't belong +on the host. +Another option would be to move functionality to the /pkg/mcr package. + +On top of this, the configurer.MCRConfigPath should perhaps be an accessor pair of content, +instead of a filepath. + +** TODO Move MCR install script from a pkg/product/phase/download_installer to the windows configurer + +The phase is in a weird place, and also is no longer needed for linux installations (we dropped +the linux script usage.) +It should get moved to the Windows configurer and integrated into the Windows configurer MCRInstall(). diff --git a/pkg/configurer/centos/centos.go b/pkg/configurer/centos/centos.go index 000b2aff..d4de2bf5 100644 --- a/pkg/configurer/centos/centos.go +++ b/pkg/configurer/centos/centos.go @@ -1,12 +1,10 @@ package centos import ( - "fmt" - - "github.com/Mirantis/launchpad/pkg/configurer/enterpriselinux" "github.com/k0sproject/rig" - "github.com/k0sproject/rig/os" "github.com/k0sproject/rig/os/registry" + + "github.com/Mirantis/launchpad/pkg/configurer/enterpriselinux" ) // Configurer is the CentOS specific implementation of a host configurer. @@ -14,20 +12,12 @@ type Configurer struct { enterpriselinux.Configurer } -// InstallMKEBasePackages install all the needed base packages on the host. -func (c Configurer) InstallMKEBasePackages(h os.Host) error { - if err := c.InstallPackage(h, "curl", "socat", "iptables", "iputils", "gzip"); err != nil { - return fmt.Errorf("failed to install base packages: %w", err) - } - return nil -} - func init() { registry.RegisterOSModule( func(os rig.OSVersion) bool { return os.ID == "centos" }, - func() interface{} { + func() any { return Configurer{} }, ) diff --git a/pkg/configurer/enterpriselinux/el.go b/pkg/configurer/enterpriselinux/el.go index 25f7d889..e11e26c5 100644 --- a/pkg/configurer/enterpriselinux/el.go +++ b/pkg/configurer/enterpriselinux/el.go @@ -4,12 +4,14 @@ import ( "fmt" "strings" - "github.com/Mirantis/launchpad/pkg/configurer" - common "github.com/Mirantis/launchpad/pkg/product/common/api" + log "github.com/sirupsen/logrus" + "github.com/k0sproject/rig/exec" "github.com/k0sproject/rig/os" "github.com/k0sproject/rig/os/linux" - log "github.com/sirupsen/logrus" + + "github.com/Mirantis/launchpad/pkg/configurer" + common "github.com/Mirantis/launchpad/pkg/product/common/api" ) // Configurer is the EL family specific implementation of a host configurer. @@ -26,6 +28,43 @@ func (c Configurer) InstallMKEBasePackages(h os.Host) error { return nil } +// InstallMCR install Docker EE engine on Linux. +func (c Configurer) InstallMCR(h os.Host, scriptPath string, engineConfig common.MCRConfig) error { + ver, verErr := configurer.ResolveLinux(h) + if verErr != nil { + return fmt.Errorf("could not discover Linux version information") + } + + if isEC2 := c.isAWSInstance(h); !isEC2 { + log.Debugf("%s: confirmed that this is not an AWS instance", h) + } else if c.InstallPackage(h, "rh-amazon-rhui-client") == nil { + log.Infof("%s: appears to be an AWS EC2 instance, installed rh-amazon-rhui-client", h) + } + + // e.g. https://repos.mirantis.com/rhel/$releasever/$basearch/ + baseUrl := fmt.Sprintf("%s/%s/%s/%s/%s", engineConfig.RepoURL, ver.ID, "$releasever", "$basearch", engineConfig.Channel) + // e.g. https://repos.mirantis.com/oraclelinux/gpg + gpgUrl := fmt.Sprintf("%s/%s/gpg", engineConfig.RepoURL, ver.ID) + elRepoFilePath := "/etc/yum.repos.d/docker-ee.repo" + elRepoTemplate := `[mirantis] +name=Mirantis Container Runtime +baseurl=%s +enabled=1 +gpgcheck=1 +gpgkey=%s +` + elRepo := fmt.Sprintf(elRepoTemplate, baseUrl, gpgUrl) + + if err := c.WriteFile(h, elRepoFilePath, elRepo, "0600"); err != nil { + return fmt.Errorf("Could not write Yum repo file for MCR") + } + + if err := c.InstallPackage(h, "docker.ee"); err != nil { + return fmt.Errorf("Package manager could not install docker-ee") + } + return nil +} + // UninstallMCR uninstalls docker-ee engine. func (c Configurer) UninstallMCR(h os.Host, _ string, engineConfig common.MCRConfig) error { info, getDockerError := c.GetDockerInfo(h) @@ -53,20 +92,6 @@ func (c Configurer) UninstallMCR(h os.Host, _ string, engineConfig common.MCRCon return nil } -// InstallMCR install Docker EE engine on Linux. -func (c Configurer) InstallMCR(h os.Host, scriptPath string, engineConfig common.MCRConfig) error { - if isEC2 := c.isAWSInstance(h); !isEC2 { - log.Debugf("%s: confirmed that this is not an AWS instance", h) - } else if c.InstallPackage(h, "rh-amazon-rhui-client") == nil { - log.Infof("%s: appears to be an AWS EC2 instance, installed rh-amazon-rhui-client", h) - } - - if err := c.LinuxConfigurer.InstallMCR(h, scriptPath, engineConfig); err != nil { - return fmt.Errorf("failed to install MCR: %w", err) - } - return nil -} - // function to check if the host is an AWS instance - https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html func (c Configurer) isAWSInstance(h os.Host) bool { found, err := h.ExecOutput("curl -s -m 5 http://169.254.169.254/latest/dynamic/instance-identity/document | grep region") diff --git a/pkg/configurer/enterpriselinux/rhel.go b/pkg/configurer/enterpriselinux/rhel.go index 63349717..c2597e85 100644 --- a/pkg/configurer/enterpriselinux/rhel.go +++ b/pkg/configurer/enterpriselinux/rhel.go @@ -15,7 +15,7 @@ func init() { func(os rig.OSVersion) bool { return os.ID == "rhel" }, - func() interface{} { + func() any { return Rhel{} }, ) diff --git a/pkg/configurer/enterpriselinux/rockylinux.go b/pkg/configurer/enterpriselinux/rockylinux.go index 101b9b95..8b7147ad 100644 --- a/pkg/configurer/enterpriselinux/rockylinux.go +++ b/pkg/configurer/enterpriselinux/rockylinux.go @@ -15,7 +15,7 @@ func init() { func(os rig.OSVersion) bool { return os.ID == "rocky" }, - func() interface{} { + func() any { return RockyLinux{} }, ) diff --git a/pkg/configurer/linux.go b/pkg/configurer/linux.go index cac3a70f..20946d72 100644 --- a/pkg/configurer/linux.go +++ b/pkg/configurer/linux.go @@ -3,7 +3,6 @@ package configurer import ( "errors" "fmt" - "io/fs" "path" "path/filepath" "regexp" @@ -14,6 +13,7 @@ import ( "github.com/Mirantis/launchpad/pkg/constant" common "github.com/Mirantis/launchpad/pkg/product/common/api" "github.com/Mirantis/launchpad/pkg/util/iputil" + "github.com/k0sproject/rig" "github.com/k0sproject/rig/exec" "github.com/k0sproject/rig/os" log "github.com/sirupsen/logrus" @@ -26,6 +26,10 @@ const ( SbinPath = `PATH=/usr/local/sbin:/usr/sbin:/sbin:$PATH` ) +var ( + LinuxMCRInstallError = errors.New("failed to install MCR on linux") +) + // LinuxConfigurer is a generic linux host configurer. type LinuxConfigurer struct { riglinux os.Linux @@ -54,60 +58,6 @@ func (c LinuxConfigurer) InstallMCRLicense(h os.Host, lic string) error { return nil } -// InstallMCR install MCR on Linux. -func (c LinuxConfigurer) InstallMCR(h os.Host, scriptPath string, engineConfig common.MCRConfig) error { - base := path.Base(scriptPath) - - installScriptDir := engineConfig.InstallScriptRemoteDirLinux - if installScriptDir == "" { - installScriptDir = c.riglinux.Pwd(h) - } - - _, err := h.ExecOutput(fmt.Sprintf("mkdir -p %s", installScriptDir)) - if err != nil { - return fmt.Errorf("failed to create directory %s: %w", installScriptDir, err) - } - - installer := path.Join(installScriptDir, base) - - err = h.Upload(scriptPath, installer, fs.FileMode(0o640)) - if err != nil { - log.Errorf("failed: %s", err.Error()) - return fmt.Errorf("upload %s to %s: %w", scriptPath, installer, err) - } - defer func() { - if err := c.riglinux.DeleteFile(h, installer); err != nil { - log.Warnf("failed to delete installer script: %s", err.Error()) - } - }() - - envs := fmt.Sprintf("DOCKER_URL=%s CHANNEL=%s VERSION=%s ", engineConfig.RepoURL, engineConfig.Channel, engineConfig.Version) - if engineConfig.AdditionalRuntimes != "" { - envs += fmt.Sprintf("ADDITIONAL_RUNTIMES=%s ", engineConfig.AdditionalRuntimes) - } - if engineConfig.DefaultRuntime != "" { - envs += fmt.Sprintf("DEFAULT_RUNTIME=%s ", engineConfig.DefaultRuntime) - } - cmd := envs + fmt.Sprintf("bash %s", escape.Quote(installer)) - - log.Infof("%s: running installer", h) - log.Debugf("%s: installer command: %s", h, cmd) - - if err := h.Exec(cmd); err != nil { - return fmt.Errorf("run MCR installer: %w", err) - } - - if err := c.riglinux.EnableService(h, "docker"); err != nil { - return fmt.Errorf("enable docker service: %w", err) - } - - if err := c.riglinux.StartService(h, "docker"); err != nil { - return fmt.Errorf("start docker service: %w", err) - } - - return nil -} - // RestartMCR restarts Docker EE engine. func (c LinuxConfigurer) RestartMCR(h os.Host) error { if err := c.riglinux.RestartService(h, "docker"); err != nil { @@ -406,3 +356,26 @@ func (c LinuxConfigurer) attemptPathSudoDelete(h os.Host, path string) { } log.Infof("%s: removed %s successfully", h, path) } + +var ( + errAbort = errors.New("base os detected but version resolving failed") +) + +// ResolveLinux stolen from k0sproject/rig +func ResolveLinux(h os.Host) (rig.OSVersion, error) { + if err := h.Exec("uname | grep -q Linux"); err != nil { + return rig.OSVersion{}, fmt.Errorf("not a linux host (%w)", err) + } + + output, err := h.ExecOutput("cat /etc/os-release || cat /usr/lib/os-release") + if err != nil { + // at this point it is known that this is a linux host, so any error from here on should signal the resolver to not try the next + return rig.OSVersion{}, fmt.Errorf("%w: unable to read os-release file: %w", errAbort, err) + } + + var version rig.OSVersion + if err := rig.ParseOSReleaseFile(output, &version); err != nil { + return rig.OSVersion{}, errors.Join(errAbort, err) + } + return version, nil +} diff --git a/pkg/configurer/mkex/README.md b/pkg/configurer/mkex/README.md deleted file mode 100644 index 9c7179e6..00000000 --- a/pkg/configurer/mkex/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# MKEx configurers - -Here we have configurers meant to allow launchpad to run with the Mirantis MKEx providing -platforms. - -Note that at least some portion of these configurers use dummy functionality for some -behaviour in order to make launchpad compatible with the MKEx idea of installing MKE -without installing MCR, as it will already be installed in the base OS. - -## Resolution - -Resolution is handled by injecting a new linux resolver into the rig.Resolvers list, -which can handle rig.Connections for MKEx OSes by looking for the expected file system. -The injection is executed in the mkex/resolver.go init() - -## OSes - -### MKEx - -This is the base MKEx Rocky linux w/ ostree base OS from Mirantis/CIQ. - -The OS is meant to allow runtime changes such as Docker operations, and some global -configuration changes, but should not run any system changes such as package management. - -User management should be unnecesary as well. diff --git a/pkg/configurer/mkex/mkex.go b/pkg/configurer/mkex/mkex.go deleted file mode 100644 index e02406e2..00000000 --- a/pkg/configurer/mkex/mkex.go +++ /dev/null @@ -1,55 +0,0 @@ -package mkex - -// it is important that the el.Rocky.init() is run before our init() -// so that our registry.RegisterOSModule() call is run second, giving -// our register priority. -import ( - "github.com/Mirantis/launchpad/pkg/configurer/enterpriselinux" - common "github.com/Mirantis/launchpad/pkg/product/common/api" - "github.com/k0sproject/rig/os" - "github.com/k0sproject/rig/os/registry" - log "github.com/sirupsen/logrus" -) - -type RockyLinux struct { - enterpriselinux.RockyLinux -} - -func init() { - registry.RegisterOSModule( - isMKExOSVersion, - func() interface{} { - log.Debug("Building an MKEX configurer for a host. This configurer will behave differently, skipping some expected installation steps.") - return RockyLinux{} - }, - ) -} - -// InstallMKEBasePackages install all the needed base packages on the host. -func (c RockyLinux) InstallMKEBasePackages(h os.Host) error { - log.Debugf("%s: InstallMKEBasePackage on MKEx takes no action. The base OS is expected to meet requirements already.", h) - return nil -} - -// UninstallMCR uninstalls docker-ee engine. -func (c RockyLinux) UninstallMCR(h os.Host, _ string, _ common.MCRConfig) error { - log.Debugf("%s: UninstallMCR on MKEx takes no action. The base OS should not be managed by Launchpad.", h) - return nil -} - -// InstallMCR install Docker EE engine on Linux. -func (c RockyLinux) InstallMCR(h os.Host, _ string, _ common.MCRConfig) error { - log.Debugf("%s: InstallMCR on MKEx takes no action. The base OS is expected to meet requirements already", h) - return nil -} - -// AuthorizeDocker adds the current user to the docker group. -func (c RockyLinux) AuthorizeDocker(h os.Host) error { - log.Debugf("%s: AuthorizeDocker on MKEx takes no action. The base OS is expected to have docker user/groups setup.", h) - return nil -} - -// CleanupLingeringMCR removes left over MCR files after Launchpad reset. -func (c RockyLinux) CleanupLingeringMCR(h os.Host, _ common.DockerInfo) { - log.Debugf("%s: UninstallMCR on MKEx takes no action. The base OS should not be managed by Launchpad", h) -} diff --git a/pkg/configurer/mkex/resolve.go b/pkg/configurer/mkex/resolve.go deleted file mode 100644 index a4b17e0a..00000000 --- a/pkg/configurer/mkex/resolve.go +++ /dev/null @@ -1,70 +0,0 @@ -package mkex - -import ( - "errors" - "fmt" - - "github.com/k0sproject/rig" -) - -const ( - mkexDetectExtraFieldKey = "MKEX" - mkexDetectReleaseFile = "/usr/lib/mirantis-release" -) - -var ( - errAbort = errors.New("base os detected but version resolving failed") // duplicate from k0sproject/rig(resolver.go) - ErrNotMirOS = fmt.Errorf("%w; not Mirantis MKEx OS", rig.ErrNotSupported) -) - -func init() { - // Add our MKEx OSVersion resolver to the rig set (as the first resolver) - rig.Resolvers = append([]rig.ResolveFunc{mkexRigResolver}, rig.Resolvers...) -} - -func isMKExOSVersion(v rig.OSVersion) bool { - _, ok := v.ExtraFields[mkexDetectExtraFieldKey] - return ok -} - -// mkexRigResolver Resolve if this connection is for an MKEX host -// -// if the MKEX file is on the machine, then it is an MKEX machine, and -// so we treat it like a linux machine, but add our custom flag. -func mkexRigResolver(conn *rig.Connection) (rig.OSVersion, error) { - if conn.IsWindows() { - return rig.OSVersion{}, ErrNotMirOS - } - output, err := conn.ExecOutput(fmt.Sprintf("cat %s", mkexDetectReleaseFile)) - if err != nil { - return rig.OSVersion{}, fmt.Errorf("%w: %s", ErrNotMirOS, err.Error()) - } - - osv, err := resolveLinux(conn) - if err != nil { - return osv, fmt.Errorf("%w: %s", ErrNotMirOS, err.Error()) - } - - osv.ExtraFields[mkexDetectExtraFieldKey] = output - osv.Name = fmt.Sprintf("%s [%s]", osv.Name, output) // without this it is hard to see in the output that we did resolve. - return osv, nil -} - -// resolveLinux duplicated from k0sprojest/rig(resolver.go). -func resolveLinux(conn *rig.Connection) (rig.OSVersion, error) { - if err := conn.Exec("uname | grep -q Linux"); err != nil { - return rig.OSVersion{}, fmt.Errorf("not a linux host (%w)", err) - } - - output, err := conn.ExecOutput("cat /etc/os-release || cat /usr/lib/os-release") - if err != nil { - // at this point it is known that this is a linux host, so any error from here on should signal the resolver to not try the next - return rig.OSVersion{}, fmt.Errorf("%w: unable to read os-release file: %w", errAbort, err) - } - - var version rig.OSVersion - if err := rig.ParseOSReleaseFile(output, &version); err != nil { - return rig.OSVersion{}, errors.Join(errAbort, err) - } - return version, nil -} diff --git a/pkg/configurer/mkex/resolve_test.go b/pkg/configurer/mkex/resolve_test.go deleted file mode 100644 index c6f69047..00000000 --- a/pkg/configurer/mkex/resolve_test.go +++ /dev/null @@ -1,21 +0,0 @@ -package mkex - -import ( - "testing" - - "github.com/k0sproject/rig" -) - -func TestIsMKEOSVersion(t *testing.T) { - osvIsMKE := rig.OSVersion{ExtraFields: map[string]string{}} - osvIsMKE.ExtraFields[mkexDetectExtraFieldKey] = "test" - - osvIsNotMKE := rig.OSVersion{} - - if !isMKExOSVersion(osvIsMKE) { - t.Error("MKEx detector failed to detect an MKEx OSVersion") - } - if isMKExOSVersion(osvIsNotMKE) { - t.Error("MKEx detector detected an MKEx OSVersion when it shouldn't have") - } -} diff --git a/pkg/configurer/oracle/oracle.go b/pkg/configurer/oracle/oracle.go index c9f48046..1bdfb20b 100644 --- a/pkg/configurer/oracle/oracle.go +++ b/pkg/configurer/oracle/oracle.go @@ -16,7 +16,7 @@ func init() { func(os rig.OSVersion) bool { return os.ID == "ol" }, - func() interface{} { + func() any { return Configurer{} }, ) diff --git a/pkg/configurer/sles/sles.go b/pkg/configurer/sles/sles.go index cc35ebe9..889021b8 100644 --- a/pkg/configurer/sles/sles.go +++ b/pkg/configurer/sles/sles.go @@ -1,9 +1,12 @@ package sles import ( + "errors" "fmt" "strings" + log "github.com/sirupsen/logrus" + "github.com/Mirantis/launchpad/pkg/configurer" common "github.com/Mirantis/launchpad/pkg/product/common/api" "github.com/k0sproject/rig" @@ -13,6 +16,22 @@ import ( "github.com/k0sproject/rig/os/registry" ) +func init() { + registry.RegisterOSModule( + func(os rig.OSVersion) bool { + return os.ID == "sles" + }, + func() any { + return Configurer{} + }, + ) +} + +const ( + ZYPPER_REPO_ALIAS = "mirantis" + ZYPPER_PACKAGE_NAME = "docker-ee" +) + // Configurer is a generic Ubuntu level configurer implementation. Some of the configurer interface implementation // might be on OS version specific implementation such as for Bionic. type Configurer struct { @@ -28,6 +47,40 @@ func (c Configurer) InstallMKEBasePackages(h os.Host) error { return nil } +// InstallMCR install Docker EE engine on Linux. +func (c Configurer) InstallMCR(h os.Host, scriptPath string, engineConfig common.MCRConfig) error { + ver, verErr := configurer.ResolveLinux(h) + if verErr != nil { + return fmt.Errorf("could not discover Linux version information") + } + + zypperRepoUrl := fmt.Sprintf("%s/%s/%s/%s/%s", engineConfig.RepoURL, ver.ID, "$releasever_major", "$basearch", engineConfig.Channel) + zypperGpgUrl := fmt.Sprintf("%s/%s/gpg", engineConfig.RepoURL, ver.ID) + + // remove the repo if it exists (always recreate the repo in case our values have changes) + if out, err := h.ExecOutput("zypper repos"); err != nil { + return fmt.Errorf("%s: could not list zypper repos", h) + } else if strings.Contains(out, ZYPPER_REPO_ALIAS) { + if err := h.Exec(fmt.Sprintf("zypper removerepo %s", ZYPPER_REPO_ALIAS), exec.Sudo(h)); err != nil { + return errors.Join(fmt.Errorf("failed to remove existing zypper MCR repo: %s", ZYPPER_REPO_ALIAS), err) + } + } + log.Debugf("%s: sles MCR GPG key import %s", h, zypperGpgUrl) + if err := h.Exec(fmt.Sprintf("sudo rpm --import %s", zypperGpgUrl), exec.Sudo(h)); err != nil { + return errors.Join(fmt.Errorf("failed to add zypper GPG key for MCR"), err) + } + if err := h.Exec(fmt.Sprintf("zypper addrepo --refresh '%s' mirantis", zypperRepoUrl), exec.Sudo(h)); err != nil { + return errors.Join(fmt.Errorf("failed to add zypper MCR repo: %s", zypperRepoUrl), err) + } + log.Debugf("%s: sles MCR install version", h) + if err := c.InstallPackage(h, "docker-ee"); err != nil { + return errors.Join(fmt.Errorf("failed to install zypper MCR packages"), err) + } + log.Debugf("%s: sles MCR installed from channel %s", h, engineConfig.Channel) + + return nil +} + // UninstallMCR uninstalls docker-ee engine. func (c Configurer) UninstallMCR(h os.Host, _ string, engineConfig common.MCRConfig) error { info, getDockerError := c.GetDockerInfo(h) @@ -54,24 +107,3 @@ func (c Configurer) UninstallMCR(h os.Host, _ string, engineConfig common.MCRCon return nil } - -// LocalAddresses returns a list of local addresses, SLES12 has an old version of "hostname" without "--all-ip-addresses" and because of that, ip addr show is used here. -func (c Configurer) LocalAddresses(h os.Host) ([]string, error) { - output, err := h.ExecOutput("ip addr show | grep 'inet ' | awk '{print $2}' | cut -d/ -f1") - if err != nil { - return nil, fmt.Errorf("failed to get local addresses: %w", err) - } - - return strings.Fields(output), nil -} - -func init() { - registry.RegisterOSModule( - func(os rig.OSVersion) bool { - return os.ID == "sles" - }, - func() interface{} { - return Configurer{} - }, - ) -} diff --git a/pkg/configurer/ubuntu/ubuntu.go b/pkg/configurer/ubuntu/ubuntu.go index c527f815..80b82e55 100644 --- a/pkg/configurer/ubuntu/ubuntu.go +++ b/pkg/configurer/ubuntu/ubuntu.go @@ -25,6 +25,50 @@ func (c Configurer) InstallMKEBasePackages(h os.Host) error { return nil } +// InstallMCR install Docker EE engine on Linux. +func (c Configurer) InstallMCR(h os.Host, scriptPath string, engineConfig common.MCRConfig) error { + ver, verErr := configurer.ResolveLinux(h) + if verErr != nil { + return fmt.Errorf("could not discover Linux version information") + } + + // WARNING: we do not check if this is ubuntu - make sure that it is if you use this code (we did it elsewhere) + codename, _ := ver.ExtraFields["VERSION_CODENAME"] // e.g. jammy + // e.g. https://repos.mirantis.com/rhel/$releasever/$basearch/ + baseUrl := fmt.Sprintf("%s/%s", engineConfig.RepoURL, ver.ID) + gpgUrl := fmt.Sprintf("%s/%s/gpg", engineConfig.RepoURL, ver.ID) + debRepoFilePath := "/etc/apt/sources.list.d/mirantis.sources" + debRepoTemplate := `Types: deb +URIs: %s +Suites: %s +Architectures: amd64 +Components: %s +Signed-by: /usr/share/keyrings/mirantis-archive-keyring.gpg +` + debRepo := fmt.Sprintf(debRepoTemplate, baseUrl, codename, engineConfig.Channel) + + // https://docs.mirantis.com/mcr/25.0/install/mcr-linux/ubuntu.html instructions + + // 2. import the mirantis gpg key + if err := h.Exec(fmt.Sprintf("sudo gpg --batch --yes --output /usr/share/keyrings/mirantis-archive-keyring.gpg --dearmor <<< $(curl -fsSL %s)", gpgUrl), exec.Sudo(h)); err != nil { + return fmt.Errorf("Could not install the Mirantis Ubuntu GPG signing key") + } + + // 4. write the repo file + // @TODO check if we can use apt-add-repository instead of writing a file (probably has better validation) + if err := c.WriteFile(h, debRepoFilePath, debRepo, "0600"); err != nil { + return fmt.Errorf("Could not write APT repo file for MCR") + } + if err := h.Exec("apt update", exec.Sudo(h)); err != nil { + return fmt.Errorf("could not update apt package info") + } + + if err := c.InstallPackage(h, "docker-ee"); err != nil { + return fmt.Errorf("Package manager could not install docker-ee") + } + return nil +} + // UninstallMCR uninstalls docker-ee engine. func (c Configurer) UninstallMCR(h os.Host, _ string, engineConfig common.MCRConfig) error { info, getDockerError := c.GetDockerInfo(h) diff --git a/pkg/constant/constant.go b/pkg/constant/constant.go index f00dd644..b1281dd2 100644 --- a/pkg/constant/constant.go +++ b/pkg/constant/constant.go @@ -6,13 +6,11 @@ const ( // ImageRepoLegacy is the default image repo to use for older versions. ImageRepoLegacy = "docker.io/docker" // MCRVersion is the default engine version. - MCRVersion = "20.10.13" + MCRVersion = "25.0" // MCRChannel is the default engine channel. MCRChannel = "stable" // MCRRepoURL is the default engine repo. MCRRepoURL = "https://repos.mirantis.com" - // MCRInstallURLLinux is the default engine install script location for linux. - MCRInstallURLLinux = "https://get.mirantis.com/" // MCRInstallURLWindows is the default engine install script location for windows. MCRInstallURLWindows = "https://get.mirantis.com/install.ps1" // StateBaseDir defines the base dir for all local state. diff --git a/pkg/kubeclient/testutil.go b/pkg/kubeclient/testutil.go index b6551c67..d9cdaff9 100644 --- a/pkg/kubeclient/testutil.go +++ b/pkg/kubeclient/testutil.go @@ -1,5 +1,3 @@ -//go:build testing - package kubeclient import ( diff --git a/pkg/product/common/api/mcr_config.go b/pkg/product/common/api/mcr_config.go index 429ddbea..5ad7389d 100644 --- a/pkg/product/common/api/mcr_config.go +++ b/pkg/product/common/api/mcr_config.go @@ -40,10 +40,9 @@ type MCRConfig struct { AdditionalRuntimes string `yaml:"additionalRuntimes,omitempty"` DefaultRuntime string `yaml:"defaultRuntime,omitempty"` License string `yaml:"license"` - InstallURLLinux string `yaml:"installURLLinux,omitempty"` InstallScriptRemoteDirLinux string `yaml:"installScriptRemoteDirLinux,omitempty"` InstallURLWindows string `yaml:"installURLWindows,omitempty"` - Channel string `yaml:"channel,omitempty"` + Channel string `yaml:"channel,omitempty,validate=regexp=test|stable"` Prune bool `yaml:"prune,omitempty"` ForceUpgrade bool `yaml:"forceUpgrade,omitempty"` SwarmInstallFlags Flags `yaml:"swarmInstallFlags,omitempty,flow"` @@ -58,7 +57,7 @@ type MCRMetadata struct { } // UnmarshalYAML puts in sane defaults when unmarshaling from yaml. -func (c *MCRConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { +func (c *MCRConfig) UnmarshalYAML(unmarshal func(any) error) error { type mcrConfig MCRConfig c.Metadata = &MCRMetadata{} yc := (*mcrConfig)(c) @@ -87,10 +86,6 @@ func (c *MCRConfig) SetDefaults() { c.RepoURL = constant.MCRRepoURL } - if c.InstallURLLinux == "" { - c.InstallURLLinux = constant.MCRInstallURLLinux - } - if c.InstallURLWindows == "" { c.InstallURLWindows = constant.MCRInstallURLWindows } diff --git a/pkg/product/mke/api/cluster_test.go b/pkg/product/mke/api/cluster_test.go index 3987e860..47b7c7d1 100644 --- a/pkg/product/mke/api/cluster_test.go +++ b/pkg/product/mke/api/cluster_test.go @@ -240,7 +240,7 @@ spec: ucp: version: 3.3.7 engine: - installURL: http://example.com/ + installURLWindows: http://example.com/install.ps1 hosts: - address: "1.2.3.4" sshPort: 9022 @@ -253,7 +253,7 @@ spec: require.NoError(t, err) require.Equal(t, "launchpad.mirantis.com/mke/v1.5", c.APIVersion) - require.Equal(t, c.Spec.MCR.InstallURLLinux, "http://example.com/") + require.Equal(t, c.Spec.MCR.InstallURLWindows, "http://example.com/install.ps1") require.Equal(t, c.Spec.Hosts[0].SSH.Port, 9022) require.Equal(t, c.Spec.Hosts[0].SSH.User, "foofoo") } @@ -266,7 +266,7 @@ spec: ucp: version: 3.3.7 engine: - installURL: http://example.com/ + installURLWindows: http://example.com/install.ps1 hosts: - address: "1.2.3.4" role: manager @@ -300,7 +300,7 @@ spec: require.NoError(t, err) require.Equal(t, "launchpad.mirantis.com/mke/v1.5", c.APIVersion) - require.Equal(t, constant.MCRInstallURLLinux, c.Spec.MCR.InstallURLLinux) + require.Equal(t, constant.MCRInstallURLWindows, c.Spec.MCR.InstallURLWindows) require.Equal(t, 9022, c.Spec.Hosts[0].SSH.Port) require.Equal(t, "foofoo", c.Spec.Hosts[0].SSH.User) } diff --git a/pkg/product/mke/api/configurer_test.go b/pkg/product/mke/api/configurer_test.go index 6a3b72a8..4c162815 100644 --- a/pkg/product/mke/api/configurer_test.go +++ b/pkg/product/mke/api/configurer_test.go @@ -5,7 +5,6 @@ import ( "github.com/Mirantis/launchpad/pkg/configurer/centos" "github.com/Mirantis/launchpad/pkg/configurer/enterpriselinux" - "github.com/Mirantis/launchpad/pkg/configurer/mkex" "github.com/Mirantis/launchpad/pkg/configurer/oracle" "github.com/Mirantis/launchpad/pkg/configurer/sles" "github.com/Mirantis/launchpad/pkg/configurer/ubuntu" @@ -22,7 +21,6 @@ func TestHostConfigurerInterface(t *testing.T) { require.True(t, castConfigurer(centos.Configurer{}), "configurer does not implement HostConfigurer") require.True(t, castConfigurer(enterpriselinux.Configurer{}), "configurer does not implement HostConfigurer") require.True(t, castConfigurer(enterpriselinux.Rhel{}), "configurer does not implement HostConfigurer") - require.True(t, castConfigurer(mkex.RockyLinux{}), "configurer does not implement HostConfigurer") require.True(t, castConfigurer(oracle.Configurer{}), "configurer does not implement HostConfigurer") require.True(t, castConfigurer(sles.Configurer{}), "configurer does not implement HostConfigurer") require.True(t, castConfigurer(windows.Windows2019Configurer{}), "configurer does not implement HostConfigurer") diff --git a/pkg/product/mke/phase/detect_os.go b/pkg/product/mke/phase/detect_os.go index c81d3a95..301bdb5e 100644 --- a/pkg/product/mke/phase/detect_os.go +++ b/pkg/product/mke/phase/detect_os.go @@ -8,8 +8,6 @@ import ( // anonymous import is needed to load the os configurers. _ "github.com/Mirantis/launchpad/pkg/configurer/enterpriselinux" // anonymous import is needed to load the os configurers. - _ "github.com/Mirantis/launchpad/pkg/configurer/mkex" - // anonymous import is needed to load the os configurers. _ "github.com/Mirantis/launchpad/pkg/configurer/oracle" // anonymous import is needed to load the os configurers. _ "github.com/Mirantis/launchpad/pkg/configurer/sles" diff --git a/pkg/product/mke/phase/download_installer.go b/pkg/product/mke/phase/download_installer.go index 0d456374..eea91b46 100644 --- a/pkg/product/mke/phase/download_installer.go +++ b/pkg/product/mke/phase/download_installer.go @@ -14,60 +14,47 @@ import ( log "github.com/sirupsen/logrus" ) +// @TODO probably can move this into the windows configurer, now that we are not using +// the linux install script anymore. + // DownloadInstaller phase implementation does all the prep work we need for the hosts. type DownloadInstaller struct { phase.Analytics phase.BasicPhase - linuxPath string - winPath string + winPath string } // Title for the phase. func (p *DownloadInstaller) Title() string { - return "Download Mirantis Container Runtime installer" + return "Download Mirantis Container Runtime installer for windows" +} + +// ShouldRun default implementation for MSR phase returns true when the config has MSR nodes. +func (p *DownloadInstaller) ShouldRun() bool { + return p.Config.Spec.Hosts.Count(func(h *api.Host) bool { return h.IsWindows() }) > 0 } // Run does all the prep work on the hosts in parallel. func (p *DownloadInstaller) Run() error { - linuxScript, err := p.getScript(p.Config.Spec.MCR.InstallURLLinux) + + winScript, err := p.getScript(p.Config.Spec.MCR.InstallURLWindows) if err != nil { - return err + return fmt.Errorf("failed to get Windows installer script: %w", err) } - f, err := os.CreateTemp("", "installerLinux") + f, err := os.CreateTemp("", "installerWindows") if err != nil { - return fmt.Errorf("failed to create temporary file: %w", err) + return fmt.Errorf("failed to create temporary file for windows installer script: %w", err) } - _, err = f.WriteString(linuxScript) + _, err = f.WriteString(winScript) if err != nil { - return fmt.Errorf("failed to write to temporary file: %w", err) - } - p.linuxPath = f.Name() - - if p.Config.Spec.Hosts.Count(func(h *api.Host) bool { return h.IsWindows() }) > 0 { - winScript, err := p.getScript(p.Config.Spec.MCR.InstallURLWindows) - if err != nil { - return fmt.Errorf("failed to get Windows installer script: %w", err) - } - f, err := os.CreateTemp("", "installerWindows") - if err != nil { - return fmt.Errorf("failed to create temporary file for windows installer script: %w", err) - } - - _, err = f.WriteString(winScript) - if err != nil { - return fmt.Errorf("failed to write to temporary file for windows installer script: %w", err) - } - p.winPath = f.Name() + return fmt.Errorf("failed to write to temporary file for windows installer script: %w", err) } + p.winPath = f.Name() - for _, h := range p.Config.Spec.Hosts { - if h.IsWindows() { - h.Metadata.MCRInstallScript = p.winPath - } else { - h.Metadata.MCRInstallScript = p.linuxPath - } + for _, h := range p.Config.Spec.Hosts.Filter(func(h *api.Host) bool { return h.IsWindows() }) { + h.Metadata.MCRInstallScript = p.winPath } return nil @@ -146,9 +133,6 @@ func (p *DownloadInstaller) CleanUp() { if p.winPath != "" { removeIfExist(p.winPath) } - if p.linuxPath != "" { - removeIfExist(p.linuxPath) - } } func removeIfExist(path string) { diff --git a/pkg/product/mke/phase/install_mcr.go b/pkg/product/mke/phase/install_mcr.go index dd0354b0..4d9132b1 100644 --- a/pkg/product/mke/phase/install_mcr.go +++ b/pkg/product/mke/phase/install_mcr.go @@ -6,7 +6,6 @@ import ( "github.com/Mirantis/launchpad/pkg/mcr" "github.com/Mirantis/launchpad/pkg/phase" "github.com/Mirantis/launchpad/pkg/product/mke/api" - retry "github.com/avast/retry-go" log "github.com/sirupsen/logrus" ) @@ -43,7 +42,7 @@ func (p *InstallMCR) Title() string { // Run installs the engine on each host. func (p *InstallMCR) Run() error { - p.EventProperties = map[string]interface{}{ + p.EventProperties = map[string]any{ "engine_version": p.Config.Spec.MCR.Version, } @@ -54,17 +53,10 @@ func (p *InstallMCR) Run() error { } func (p *InstallMCR) installMCR(h *api.Host) error { - if err := retry.Do( - func() error { - log.Infof("%s: installing container runtime (%s)", h, p.Config.Spec.MCR.Version) - if err := h.Configurer.InstallMCR(h, h.Metadata.MCRInstallScript, p.Config.Spec.MCR); err != nil { - log.Errorf("%s: failed to install container runtime: %s", h, err.Error()) - return fmt.Errorf("%s: failed to install container runtime: %w", h, err) - } - return nil - }, - ); err != nil { - return fmt.Errorf("retry count exceeded: %w", err) + log.Infof("%s: installing container runtime (%s)", h, p.Config.Spec.MCR.Version) + if err := h.Configurer.InstallMCR(h, h.Metadata.MCRInstallScript, p.Config.Spec.MCR); err != nil { + log.Errorf("%s: failed to install container runtime: %s", h, err.Error()) + return fmt.Errorf("%s: failed to install container runtime: %w", h, err) } if err := h.AuthorizeDocker(); err != nil {