internal/packagemanager/packagemanager.go (267 lines of code) (raw):

package packagemanager import ( "context" "fmt" "net/http" "os" "os/exec" "runtime" "time" "github.com/pkg/errors" "go.uber.org/zap" "github.com/aws/eks-hybrid/internal/artifact" "github.com/aws/eks-hybrid/internal/containerd" "github.com/aws/eks-hybrid/internal/system" "github.com/aws/eks-hybrid/internal/util" "github.com/aws/eks-hybrid/internal/util/cmd" ) const ( aptPackageManager = "apt" snapPackageManager = "snap" yumPackageManager = "yum" snapInstallVerb = "install" snapUpdateVerb = "refresh" snapRemoveVerb = "remove" yumUtilsManager = "yum-config-manager" yumUtilsManagerPkg = "yum-utils" centOsDockerRepo = "https://download.docker.com/linux/centos/docker-ce.repo" ubuntuDockerRepo = "https://download.docker.com/linux/ubuntu" ubuntuDockerGpgKey = "https://download.docker.com/linux/ubuntu/gpg" ubuntuDockerGpgKeyPath = "/etc/apt/keyrings/docker.asc" ubuntuDockerGpgKeyFilePerms = 0o755 aptDockerRepoSourceFilePath = "/etc/apt/sources.list.d/docker.list" yumDockerRepoSourceFilePath = "/etc/yum.repos.d/docker-ce.repo" containerdDistroPkgName = "containerd" containerdDockerPkgName = "containerd.io" runcPkgName = "runc" caCertsPkgName = "ca-certificates" iptablesPkgName = "iptables" ssmPkgName = "amazon-ssm-agent" ) var aptDockerRepoConfig = fmt.Sprintf("deb [arch=%s signed-by=%s] %s %s stable\n", runtime.GOARCH, ubuntuDockerGpgKeyPath, ubuntuDockerRepo, system.GetVersionCodeName()) // DistroPackageManager defines a new package manager using apt or yum type DistroPackageManager struct { manager string installVerb string updateVerb string deleteVerb string refreshMetadataVerb string dockerRepo string logger *zap.Logger } func New(containerdSource containerd.SourceName, logger *zap.Logger) (*DistroPackageManager, error) { manager, err := getOsPackageManager() if err != nil { return nil, err } pm := &DistroPackageManager{ manager: manager, logger: logger, installVerb: packageManagerInstallCmd[manager], updateVerb: packageManagerUpdateCmd[manager], deleteVerb: packageManagerDeleteCmd[manager], refreshMetadataVerb: packageManagerMetadataRefreshCmd[manager], } if containerdSource == containerd.ContainerdSourceDocker { pm.dockerRepo = managerToDockerRepoMap[manager] } return pm, nil } // Configure configures the package manager. func (pm *DistroPackageManager) Configure(ctx context.Context) error { // Add docker repos to the package manager if pm.dockerRepo != "" { if pm.manager == yumPackageManager { return pm.configureYumPackageManagerWithDockerRepo(ctx) } if pm.manager == aptPackageManager { return pm.configureAptPackageManagerWithDockerRepo(ctx) } } return nil } // configureYumPackageManagerWithDockerRepo configures yum package manager with docker repos func (pm *DistroPackageManager) configureYumPackageManagerWithDockerRepo(ctx context.Context) error { // Check and remove runc if installed, as it conflicts with docker repo if _, errNotFound := exec.LookPath(runcPkgName); errNotFound == nil { pm.logger.Info("Removing runc to avoid package conflicts from docker repos...") if err := cmd.Retry(ctx, pm.runcPackage().UninstallCmd, 5*time.Second); err != nil { return errors.Wrapf(err, "failed to remove runc using package manager") } } // Sometimes install fails due to conflicts with other processes // updating packages, specially when automating at machine startup. // We assume errors are transient and just retry for a bit. if err := cmd.Retry(ctx, pm.yumUtilsPackage().InstallCmd, 5*time.Second); err != nil { return errors.Wrapf(err, "failed to install %s using package manager", yumUtilsManagerPkg) } // Get yumUtilsManager full path yumUtilsManagerPath, err := exec.LookPath(yumUtilsManager) if err != nil { return errors.Wrapf(err, "failed to locate yum utils manager in $PATH") } pm.logger.Info("Adding docker repo to package manager...") configureCmd := exec.Command(yumUtilsManagerPath, "--add-repo", centOsDockerRepo) out, err := configureCmd.CombinedOutput() if err != nil { return errors.Wrapf(err, "failed adding docker repo to package manager: %s", out) } return nil } // configureAptPackageManagerWithDockerRepo configures apt package manager with docker repos func (pm *DistroPackageManager) configureAptPackageManagerWithDockerRepo(ctx context.Context) error { // Sometimes install fails due to conflicts with other processes // updating packages, specially when automating at machine startup. // We assume errors are transient and just retry for a bit. if err := cmd.Retry(ctx, pm.caCertsPackage().InstallCmd, 5*time.Second); err != nil { return errors.Wrapf(err, "failed running commands to configure package manager") } // Download docker gpg key and write it to file resp, err := http.Get(ubuntuDockerGpgKey) if err != nil { return err } defer resp.Body.Close() if err := util.WriteFileWithDirFromReader(ubuntuDockerGpgKeyPath, resp.Body, ubuntuDockerGpgKeyFilePerms); err != nil { return err } // Add docker repo config for ubuntu-apt to apt sources if err := util.WriteFileWithDir(aptDockerRepoSourceFilePath, []byte(aptDockerRepoConfig), ubuntuDockerGpgKeyFilePerms); err != nil { return err } // Run update to pull docker repo's metadata pm.logger.Info("Updating packages to refresh docker repo metadata...") err = pm.RefreshMetadataCache(ctx) if err != nil { return errors.Wrapf(err, "failed running commands to configure package manager") } return nil } // uninstallDockerRepo uninstalls docker repos installed by package managers when containerd source is docker func (pm *DistroPackageManager) uninstallDockerRepo() error { removeRepoFile := func(path, pkgType string) error { _, err := os.Stat(path) if os.IsNotExist(err) { return nil } if err != nil { return errors.Wrapf(err, "encountered error while trying to reach %s docker repo file at %s", pkgType, path) } if err := os.Remove(path); err != nil { return errors.Wrapf(err, "failed to remove %s docker repo from %s", pkgType, path) } return nil } switch pm.manager { case yumPackageManager: return removeRepoFile(yumDockerRepoSourceFilePath, yumPackageManager) case aptPackageManager: if err := os.Remove(ubuntuDockerGpgKeyPath); err != nil { if !os.IsNotExist(err) { return err } } return removeRepoFile(aptDockerRepoSourceFilePath, aptPackageManager) default: return nil } } func (pm *DistroPackageManager) appendPackageVersion(packageName, version string) string { if version == "" { return packageName } switch pm.manager { case yumPackageManager: return fmt.Sprintf("%s-%s", packageName, version) case aptPackageManager: return fmt.Sprintf("%s=%s", packageName, version) default: return packageName } } func (pm *DistroPackageManager) getContainerdPackageNameWithVersion(version string) string { containerdPkgName := containerdDistroPkgName if pm.dockerRepo != "" { containerdPkgName = containerdDockerPkgName } return pm.appendPackageVersion(containerdPkgName, version) } // RefreshMetadataCache refreshes the package managers metadata cache func (pm *DistroPackageManager) RefreshMetadataCache(ctx context.Context) error { return cmd.Retry(ctx, pm.refreshMetadataCacheCommand, 5*time.Second) } func (pm *DistroPackageManager) refreshMetadataCacheCommand(ctx context.Context) *exec.Cmd { return exec.CommandContext(ctx, pm.manager, pm.refreshMetadataVerb) } // GetContainerd gets the Package // Satisfies the containerd source interface func (pm *DistroPackageManager) GetContainerd(version string) artifact.Package { packageName := pm.getContainerdPackageNameWithVersion(version) return artifact.NewPackageSource( artifact.NewCmd(pm.manager, pm.installVerb, packageName, "-y"), artifact.NewCmd(pm.manager, pm.deleteVerb, packageName, "-y"), artifact.NewCmd(pm.manager, pm.updateVerb, packageName, "-y"), ) } // GetIptables satisfies the getiptables source interface func (pm *DistroPackageManager) GetIptables() artifact.Package { return artifact.NewPackageSource( artifact.NewCmd(pm.manager, pm.installVerb, iptablesPkgName, "-y"), artifact.NewCmd(pm.manager, pm.deleteVerb, iptablesPkgName, "-y"), artifact.NewCmd(pm.manager, pm.updateVerb, iptablesPkgName, "-y"), ) } // GetSSMPackage satisfies the getssmpackage source interface func (pm *DistroPackageManager) GetSSMPackage() artifact.Package { // SSM is installed using snap package manager. If apt package manager // is detected, use snap to install/uninstall SSM. if pm.manager == aptPackageManager { return artifact.NewPackageSource( artifact.NewCmd(snapPackageManager, snapInstallVerb, ssmPkgName), artifact.NewCmd(snapPackageManager, snapRemoveVerb, ssmPkgName), artifact.NewCmd(snapPackageManager, snapUpdateVerb, ssmPkgName), ) } return artifact.NewPackageSource( artifact.NewCmd(pm.manager, pm.installVerb, ssmPkgName, "-y"), artifact.NewCmd(pm.manager, pm.deleteVerb, ssmPkgName, "-y"), artifact.NewCmd(pm.manager, pm.updateVerb, ssmPkgName, "-y"), ) } func (pm *DistroPackageManager) caCertsPackage() artifact.Package { return artifact.NewPackageSource( artifact.NewCmd(pm.manager, pm.installVerb, caCertsPkgName, "-y"), artifact.NewCmd(pm.manager, pm.deleteVerb, caCertsPkgName, "-y"), artifact.NewCmd(pm.manager, pm.updateVerb, caCertsPkgName, "-y"), ) } func (pm *DistroPackageManager) yumUtilsPackage() artifact.Package { return artifact.NewPackageSource( artifact.NewCmd(pm.manager, pm.installVerb, yumUtilsManagerPkg, "-y"), artifact.NewCmd(pm.manager, pm.deleteVerb, yumUtilsManagerPkg, "-y"), artifact.NewCmd(pm.manager, pm.updateVerb, yumUtilsManagerPkg, "-y"), ) } func (pm *DistroPackageManager) runcPackage() artifact.Package { return artifact.NewPackageSource( artifact.NewCmd(pm.manager, pm.installVerb, runcPkgName, "-y"), artifact.NewCmd(pm.manager, pm.deleteVerb, runcPkgName, "-y"), artifact.NewCmd(pm.manager, pm.updateVerb, runcPkgName, "-y"), ) } // Cleanup cleans up any artifacts used by package manager during nodeadm install process func (pm *DistroPackageManager) Cleanup() error { // Removes docker repos if installed by nodeadm ("Containerd: docker" was set in tracker file) if pm.dockerRepo != "" { if err := pm.uninstallDockerRepo(); err != nil { return err } } return nil } func getOsPackageManager() (string, error) { supportedManagers := []string{yumPackageManager, aptPackageManager} for _, manager := range supportedManagers { if _, err := exec.LookPath(manager); err == nil { return manager, nil } } return "", errors.New("unsupported package manager encountered. Please run nodeadm from a supported os") } var packageManagerInstallCmd = map[string]string{ aptPackageManager: "install", yumPackageManager: "install", } var packageManagerUpdateCmd = map[string]string{ aptPackageManager: "upgrade", yumPackageManager: "update", } var packageManagerDeleteCmd = map[string]string{ aptPackageManager: "autoremove", yumPackageManager: "remove", } var packageManagerMetadataRefreshCmd = map[string]string{ aptPackageManager: "update", yumPackageManager: "makecache", } var managerToDockerRepoMap = map[string]string{ yumPackageManager: "https://download.docker.com/linux/centos/docker-ce.repo", aptPackageManager: "https://download.docker.com/linux/ubuntu", }