debian-forge-composer/internal/rpmmd/repository.go
Jacob Kozol 085ba6fec9 rpmmd: convert sources to repos
Add function to convert between a user source and a repo which can be
passed to dnf-json. This is neccessary because user-defined sources have
a slightly different format than dnf repos.
2019-11-14 12:16:07 +01:00

180 lines
3.6 KiB
Go

package rpmmd
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"os/exec"
"sort"
"time"
"github.com/osbuild/osbuild-composer/internal/store"
)
type RepoConfig struct {
Id string `json:"id"`
Name string `json:"name"`
BaseURL string `json:"baseurl,omitempty"`
Metalink string `json:"metalink,omitempty"`
MirrorList string `json:"mirrorlist,omitempty"`
}
type PackageList []Package
type Package struct {
Name string
Summary string
Description string
URL string
Epoch uint
Version string
Release string
Arch string
BuildTime time.Time
License string
}
type PackageSpec struct {
Name string `json:"name"`
Epoch uint `json:"epoch"`
Version string `json:"version,omitempty"`
Release string `json:"release,omitempty"`
Arch string `json:"arch,omitempty"`
}
type RPMMD interface {
FetchPackageList(repos []RepoConfig) (PackageList, error)
Depsolve(specs []string, repos []RepoConfig) ([]PackageSpec, error)
}
type DNFError struct {
Kind string `json:"kind"`
Reason string `json:"reason"`
}
func (err *DNFError) Error() string {
return fmt.Sprintf("DNF error occured: %s: %s", err.Kind, err.Reason)
}
func runDNF(command string, arguments interface{}, result interface{}) error {
var call = struct {
Command string `json:"command"`
Arguments interface{} `json:"arguments,omitempty"`
}{
command,
arguments,
}
cmd := exec.Command("python3", "dnf-json")
stdin, err := cmd.StdinPipe()
if err != nil {
return err
}
cmd.Stderr = os.Stderr
stdout, err := cmd.StdoutPipe()
if err != nil {
return err
}
err = cmd.Start()
if err != nil {
return err
}
err = json.NewEncoder(stdin).Encode(call)
if err != nil {
return err
}
stdin.Close()
output, err := ioutil.ReadAll(stdout)
if err != nil {
return err
}
err = cmd.Wait()
const DnfErrorExitCode = 10
if runError, ok := err.(*exec.ExitError); ok && runError.ExitCode() == DnfErrorExitCode {
var dnfError DNFError
err = json.Unmarshal(output, &dnfError)
if err != nil {
return err
}
return &dnfError
}
err = json.Unmarshal(output, result)
if err != nil {
return err
}
return nil
}
type rpmmdImpl struct{}
func NewRPMMD() RPMMD {
return &rpmmdImpl{}
}
func (*rpmmdImpl) FetchPackageList(repos []RepoConfig) (PackageList, error) {
var arguments = struct {
Repos []RepoConfig `json:"repos"`
}{repos}
var packages PackageList
err := runDNF("dump", arguments, &packages)
sort.Slice(packages, func(i, j int) bool {
return packages[i].Name < packages[j].Name
})
return packages, err
}
func (*rpmmdImpl) Depsolve(specs []string, repos []RepoConfig) ([]PackageSpec, error) {
var arguments = struct {
PackageSpecs []string `json:"package-specs"`
Repos []RepoConfig `json:"repos"`
}{specs, repos}
var dependencies []PackageSpec
err := runDNF("depsolve", arguments, &dependencies)
return dependencies, err
}
func (packages PackageList) Search(name string) (int, int) {
first := sort.Search(len(packages), func(i int) bool {
return packages[i].Name >= name
})
if first == len(packages) || packages[first].Name != name {
return first, 0
}
last := first + 1
for last < len(packages) && packages[last].Name == name {
last++
}
return first, last - first
}
func SourceToRepo(source store.SourceConfig) RepoConfig {
var repo RepoConfig
repo.Name = source.Name
repo.Id = source.Name
if source.Type == "yum-baseurl" {
repo.BaseURL = source.URL
} else if source.Type == "yum-metalink" {
repo.Metalink = source.URL
} else if source.Type == "yum-mirrorlist" {
repo.MirrorList = source.URL
}
return repo
}