targets/linux/rpm/distro/dnf_install.go (178 lines of code) (raw):

package distro import ( "context" "fmt" "path/filepath" "strings" "github.com/Azure/dalec" "github.com/Azure/dalec/frontend" "github.com/moby/buildkit/client/llb" gwclient "github.com/moby/buildkit/frontend/gateway/client" ) var dnfRepoPlatform = dalec.RepoPlatformConfig{ ConfigRoot: "/etc/yum.repos.d", GPGKeyRoot: "/etc/pki/rpm-gpg", ConfigExt: ".repo", } type PackageInstaller func(*dnfInstallConfig, string, []string) llb.RunOption type dnfInstallConfig struct { // Disables GPG checking when installing RPMs. // this is needed when installing unsigned RPMs. noGPGCheck bool // path for gpg keys to import for using a repo. These files for these keys // must also be added as mounts keys []string // Sets the root path to install rpms too. // this acts like installing to a chroot. root string // Additional mounts to add to the (t?)dnf install command (useful if installing RPMS which are mounted to a local directory) mounts []llb.RunOption constraints []llb.ConstraintsOpt downloadOnly bool allDeps bool downloadDir string } type DnfInstallOpt func(*dnfInstallConfig) func DnfNoGPGCheck(cfg *dnfInstallConfig) { cfg.noGPGCheck = true } // see comment in tdnfInstall for why this additional option is needed func DnfImportKeys(keys []string) DnfInstallOpt { return func(cfg *dnfInstallConfig) { cfg.keys = append(cfg.keys, keys...) } } func DnfWithMounts(opts ...llb.RunOption) DnfInstallOpt { return func(cfg *dnfInstallConfig) { cfg.mounts = append(cfg.mounts, opts...) } } func DnfAtRoot(root string) DnfInstallOpt { return func(cfg *dnfInstallConfig) { cfg.root = root } } func DnfDownloadAllDeps(dest string) DnfInstallOpt { return func(cfg *dnfInstallConfig) { cfg.downloadOnly = true cfg.allDeps = true cfg.downloadDir = dest } } func dnfInstallWithConstraints(opts []llb.ConstraintsOpt) DnfInstallOpt { return func(cfg *dnfInstallConfig) { cfg.constraints = opts } } func dnfInstallFlags(cfg *dnfInstallConfig) string { var cmdOpts string if cfg.noGPGCheck { cmdOpts += " --nogpgcheck" } if cfg.root != "" { cmdOpts += " --installroot=" + cfg.root cmdOpts += " --setopt=reposdir=/etc/yum.repos.d" } if cfg.downloadOnly { cmdOpts += " --downloadonly" } if cfg.allDeps { cmdOpts += " --alldeps" } if cfg.downloadDir != "" { cmdOpts += " --downloaddir " + cfg.downloadDir } return cmdOpts } func dnfInstallOptions(cfg *dnfInstallConfig, opts []DnfInstallOpt) { for _, o := range opts { o(cfg) } } func importGPGScript(keyPaths []string) string { // all keys that are included should be mounted under this path keyRoot := "/etc/pki/rpm-gpg" var importScript string = "#!/usr/bin/env sh\nset -eux\n" for _, keyPath := range keyPaths { keyName := filepath.Base(keyPath) importScript += fmt.Sprintf("gpg --import %s\n", filepath.Join(keyRoot, keyName)) } return importScript } func TdnfInstall(cfg *dnfInstallConfig, releaseVer string, pkgs []string) llb.RunOption { cmdFlags := dnfInstallFlags(cfg) // tdnf makecache is needed to ensure that the package metadata is up to date if extra repo // config files have been mounted cmdArgs := fmt.Sprintf("set -ex; tdnf makecache -y; tdnf install -y --refresh --setopt=varsdir=/etc/dnf/vars --releasever=%s %s %s", releaseVer, cmdFlags, strings.Join(pkgs, " ")) var runOpts []llb.RunOption // TODO(adamperlin): see if this can be removed for dnf // If we have keys to import in order to access a repo, we need to create a script to use `gpg` to import them // This is an unfortunate consequence of a bug in tdnf (see https://github.com/vmware/tdnf/issues/471). // The keys *should* be imported automatically by tdnf as long as the repo config references them correctly and // we mount the key files themselves under the right path. However, tdnf does NOT do this // currently if the keys are referenced via a `file:///` type url, // and we must manually import the keys as well. if len(cfg.keys) > 0 { importScript := importGPGScript(cfg.keys) cmdArgs = "/tmp/import-keys.sh; " + cmdArgs runOpts = append(runOpts, llb.AddMount("/tmp/import-keys.sh", llb.Scratch().File(llb.Mkfile("/import-keys.sh", 0755, []byte(importScript))), llb.SourcePath("/import-keys.sh"))) } runOpts = append(runOpts, dalec.ShArgs(cmdArgs)) runOpts = append(runOpts, cfg.mounts...) return dalec.WithRunOptions(runOpts...) } func DnfInstall(cfg *dnfInstallConfig, releaseVer string, pkgs []string) llb.RunOption { cmdFlags := dnfInstallFlags(cfg) // tdnf makecache is needed to ensure that the package metadata is up to date if extra repo // config files have been mounted cmdArgs := fmt.Sprintf("set -ex; dnf makecache -y; dnf install -y --refresh --releasever=%s --setopt=varsdir=/etc/dnf/vars %s %s", releaseVer, cmdFlags, strings.Join(pkgs, " ")) var runOpts []llb.RunOption // TODO: see if this can be removed for dnf // If we have keys to import in order to access a repo, we need to create a script to use `gpg` to import them // This is an unfortunate consequence of a bug in tdnf (see https://github.com/vmware/tdnf/issues/471). // The keys *should* be imported automatically by tdnf as long as the repo config references them correctly and // we mount the key files themselves under the right path. However, tdnf does NOT do this // currently if the keys are referenced via a `file:///` type url, // and we must manually import the keys as well. if len(cfg.keys) > 0 { importScript := importGPGScript(cfg.keys) cmdArgs = "/tmp/import-keys.sh; " + cmdArgs runOpts = append(runOpts, llb.AddMount("/tmp/import-keys.sh", llb.Scratch().File(llb.Mkfile("/import-keys.sh", 0755, []byte(importScript))), llb.SourcePath("/import-keys.sh"))) } runOpts = append(runOpts, dalec.ShArgs(cmdArgs)) runOpts = append(runOpts, cfg.mounts...) return dalec.WithRunOptions(runOpts...) } type buildDepsInstallerFunc func(context.Context, gwclient.Client, dalec.SourceOpts) (llb.RunOption, error) func (cfg *Config) installBuildDepsPackage(worker llb.State, target string, packageName string, deps map[string]dalec.PackageConstraints, installOpts ...DnfInstallOpt) buildDepsInstallerFunc { // depsOnly is a simple dalec spec that only includes build dependencies and their constraints depsOnly := dalec.Spec{ Name: fmt.Sprintf("%s-build-dependencies", packageName), Description: "Provides build dependencies for mariner2 and azlinux3", Version: "1.0", License: "Apache 2.0", Revision: "1", Dependencies: &dalec.PackageDependencies{ Runtime: deps, }, } return func(ctx context.Context, client gwclient.Client, sOpt dalec.SourceOpts) (llb.RunOption, error) { pg := dalec.ProgressGroup("Building container for build dependencies") // create an RPM with just the build dependencies, using our same base worker rpmDir, err := cfg.BuildPkg(ctx, client, worker, sOpt, &depsOnly, target, pg) if err != nil { return nil, err } var opts []llb.ConstraintsOpt opts = append(opts, dalec.ProgressGroup("Install build deps")) rpmMountDir := "/tmp/rpms" installOpts = append([]DnfInstallOpt{ DnfNoGPGCheck, DnfWithMounts(llb.AddMount(rpmMountDir, rpmDir, llb.SourcePath("/RPMS"))), dnfInstallWithConstraints(opts), }, installOpts...) // install the built RPMs into the worker itself return cfg.Install([]string{"/tmp/rpms/*/*.rpm"}, installOpts...), nil } } func (cfg *Config) InstallBuildDeps(ctx context.Context, client gwclient.Client, spec *dalec.Spec, sOpt dalec.SourceOpts, targetKey string, opts ...llb.ConstraintsOpt) llb.StateOption { deps := spec.GetBuildDeps(targetKey) if len(deps) == 0 { return func(in llb.State) llb.State { return in } } repos := spec.GetBuildRepos(targetKey) sOpt, err := frontend.SourceOptFromClient(ctx, client, sOpt.TargetPlatform) if err != nil { return nil } return func(in llb.State) llb.State { return in.Async(func(ctx context.Context, s llb.State, c *llb.Constraints) (llb.State, error) { repoMounts, keyPaths, err := cfg.RepoMounts(repos, sOpt, opts...) if err != nil { return llb.Scratch(), err } importRepos := []DnfInstallOpt{DnfWithMounts(repoMounts), DnfImportKeys(keyPaths)} opts = append(opts, dalec.ProgressGroup("Install build deps")) installOpt, err := cfg.installBuildDepsPackage(s, targetKey, spec.Name, deps, append(importRepos, dnfInstallWithConstraints(opts))...)(ctx, client, sOpt) if err != nil { return llb.Scratch(), err } return s.Run(installOpt, dalec.WithConstraints(opts...)).Root(), nil }) } }