deps.go (190 lines of code) (raw):

package dalec import ( goerrors "errors" "slices" "github.com/moby/buildkit/frontend/dockerfile/shell" "github.com/pkg/errors" ) // PackageConstraints is used to specify complex constraints for a package dependency. type PackageConstraints struct { // Version is a list of version constraints for the package. // The format of these strings is dependent on the package manager of the target system. // Examples: // [">=1.0.0", "<2.0.0"] Version []string `yaml:"version,omitempty" json:"version,omitempty"` // Arch is a list of architecture constraints for the package. // Use this to specify that a package constraint only applies to certain architectures. Arch []string `yaml:"arch,omitempty" json:"arch,omitempty"` } // PackageDependencies is a list of dependencies for a package. // This will be included in the package metadata so that the package manager can install the dependencies. // It also includes build-time dedendencies, which we'll install before running any build steps. type PackageDependencies struct { // Build is the list of packagese required to build the package. Build map[string]PackageConstraints `yaml:"build,omitempty" json:"build,omitempty"` // Runtime is the list of packages required to install/run the package. Runtime map[string]PackageConstraints `yaml:"runtime,omitempty" json:"runtime,omitempty"` // Recommends is the list of packages recommended to install with the generated package. // Note: Not all package managers support this (e.g. rpm) Recommends map[string]PackageConstraints `yaml:"recommends,omitempty" json:"recommends,omitempty"` // Test lists any extra packages required for running tests // These packages are only installed for tests which have steps that require // running a command in the built container. // See [TestSpec] for more information. Test []string `yaml:"test,omitempty" json:"test,omitempty"` // ExtraRepos is used to inject extra package repositories that may be used to // satisfy package dependencies in various stages. ExtraRepos []PackageRepositoryConfig `yaml:"extra_repos,omitempty" json:"extra_repos,omitempty"` } // PackageRepositoryConfig type PackageRepositoryConfig struct { // Keys are the list of keys that need to be imported to use the configured // repositories Keys map[string]Source `yaml:"keys,omitempty" json:"keys,omitempty"` // Config list of repo configs to to add to the environment. The format of // these configs are distro specific (e.g. apt/yum configs). Config map[string]Source `yaml:"config" json:"config"` // Data lists all the extra data that needs to be made available for the // provided repository config to work. // As an example, if the provided config is referencing a file backed repository // then data would include the file data, assuming its not already available // in the environment. Data []SourceMount `yaml:"data,omitempty" json:"data,omitempty"` // Envs specifies the list of environments to make the repositories available // during. // Acceptable values are: // - "build" - Repositories are added prior to installing build dependencies // - "test" - Repositories are added prior to installing test dependencies // - "install" - Repositories are added prior to installing the output // package in a container build target. Envs []string `yaml:"envs" json:"envs" jsonschema:"enum=build,enum=test,enum=install"` } func (d *PackageDependencies) processBuildArgs(lex *shell.Lex, args map[string]string, allowArg func(string) bool) error { if d == nil { return nil } for k, v := range d.Build { for i, ver := range v.Version { updated, err := expandArgs(lex, ver, args, allowArg) if err != nil { return errors.Wrapf(err, "build version %s", ver) } v.Version[i] = updated } d.Build[k] = v } for k, v := range d.Runtime { for i, ver := range v.Version { updated, err := expandArgs(lex, ver, args, allowArg) if err != nil { return errors.Wrapf(err, "runtime version %s", ver) } v.Version[i] = updated } d.Runtime[k] = v } var errs []error for i, repo := range d.ExtraRepos { if err := repo.processBuildArgs(lex, args, allowArg); err != nil { errs = append(errs, errors.Wrapf(err, "extra repos index %d", i)) } d.ExtraRepos[i] = repo } return goerrors.Join(errs...) } func (r *PackageRepositoryConfig) processBuildArgs(lex *shell.Lex, args map[string]string, allowArg func(string) bool) error { if r == nil { return nil } var errs []error for k := range r.Config { src := r.Config[k] if err := src.processBuildArgs(lex, args, allowArg); err != nil { errs = append(errs, errors.Wrapf(err, "config %s", k)) continue } r.Config[k] = src } for k := range r.Keys { src := r.Keys[k] if err := src.processBuildArgs(lex, args, allowArg); err != nil { errs = append(errs, errors.Wrapf(err, "key %s", k)) continue } r.Keys[k] = src } for i := range r.Data { d := r.Data[i] if err := d.processBuildArgs(lex, args, allowArg); err != nil { errs = append(errs, errors.Wrapf(err, "data index %d", i)) continue } r.Data[i] = d } return goerrors.Join(errs...) } func (d *PackageDependencies) fillDefaults() { if d == nil { return } for i, r := range d.ExtraRepos { r.fillDefaults() d.ExtraRepos[i] = r } } func (r *PackageRepositoryConfig) fillDefaults() { if len(r.Envs) == 0 { // default to all stages for the extra repo if unspecified r.Envs = []string{"build", "install", "test"} } for i, src := range r.Config { fillDefaults(&src) r.Config[i] = src } for i, src := range r.Keys { fillDefaults(&src) // Default to 0644 permissions for gpg keys. This is because apt will will only import // keys with a particular permission set. if src.HTTP != nil { src.HTTP.Permissions = 0644 } r.Keys[i] = src } for i, mount := range r.Data { mount.fillDefaults() r.Data[i] = mount } } func (d *PackageDependencies) validate() error { if d == nil { return nil } var errs []error for i, r := range d.ExtraRepos { if err := r.validate(); err != nil { errs = append(errs, errors.Wrapf(err, "extra repo %d", i)) } } return goerrors.Join(errs...) } func (r *PackageRepositoryConfig) validate() error { var errs []error for name, src := range r.Keys { if err := src.validate(); err != nil { errs = append(errs, errors.Wrapf(err, "key %s", name)) } } for name, src := range r.Config { if err := src.validate(); err != nil { errs = append(errs, errors.Wrapf(err, "config %s", name)) } } for _, mnt := range r.Data { if err := mnt.validate("/"); err != nil { errs = append(errs, errors.Wrapf(err, "data mount path %s", mnt.Dest)) } } return goerrors.Join(errs...) } func (p *PackageDependencies) GetExtraRepos(env string) []PackageRepositoryConfig { return GetExtraRepos(p.ExtraRepos, env) } func GetExtraRepos(repos []PackageRepositoryConfig, env string) []PackageRepositoryConfig { var out []PackageRepositoryConfig for _, repo := range repos { if slices.Contains(repo.Envs, env) { out = append(repos, repo) } } return out } func (s *Spec) GetBuildRepos(targetKey string) []PackageRepositoryConfig { deps := s.GetPackageDeps(targetKey) if deps == nil { deps = s.Dependencies if deps == nil { return nil } } return deps.GetExtraRepos("build") } func (s *Spec) GetInstallRepos(targetKey string) []PackageRepositoryConfig { deps := s.GetPackageDeps(targetKey) if deps == nil { deps = s.Dependencies if deps == nil { return nil } } return deps.GetExtraRepos("install") } func (s *Spec) GetTestRepos(targetKey string) []PackageRepositoryConfig { deps := s.GetPackageDeps(targetKey) if deps == nil { deps = s.Dependencies if deps == nil { return nil } } return deps.GetExtraRepos("test") }