jx/internal/resource/package.go
Matthew Rich bcf4e768ff
Some checks are pending
Lint / golangci-lint (push) Waiting to run
Declarative Tests / test (push) Waiting to run
Declarative Tests / build-fedora (push) Waiting to run
Declarative Tests / build-ubuntu-focal (push) Waiting to run
Fix an issue with the package resource where a missing package would cause a fatal error
WIP: add support container image build using local filesytem contexts or contextes generated from resource definitions
WIP: added support for the create command in the exec resource
Fix a type matching error in `types` package use of generics
2024-07-22 15:03:22 -07:00

1006 lines
26 KiB
Go

// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package resource
import (
"context"
"encoding/json"
"errors"
"fmt"
"gopkg.in/yaml.v3"
"io"
"log/slog"
"net/url"
_ "os"
_ "os/exec"
"path/filepath"
"strings"
"gitea.rosskeen.house/rosskeen.house/machine"
"decl/internal/codec"
"decl/internal/command"
)
type PackageType string
const (
PackageTypeApk PackageType = "apk"
PackageTypeApt PackageType = "apt"
PackageTypeDeb PackageType = "deb"
PackageTypeDnf PackageType = "dnf"
PackageTypeRpm PackageType = "rpm"
PackageTypePip PackageType = "pip"
PackageTypeYum PackageType = "yum"
)
var ErrUnsupportedPackageType error = errors.New("The PackageType is not supported on this system")
var ErrInvalidPackageType error = errors.New("invalid PackageType value")
var SystemPackageType PackageType = FindSystemPackageType()
type Package struct {
stater machine.Stater `yaml:"-" json:"-"`
Source string `yaml:"source,omitempty" json:"source,omitempty"`
Name string `yaml:"name" json:"name"`
Required string `json:"required,omitempty" yaml:"required,omitempty"`
Version string `yaml:"version,omitempty" json:"version,omitempty"`
PackageType PackageType `yaml:"type" json:"type"`
CreateCommand *command.Command `yaml:"-" json:"-"`
ReadCommand *command.Command `yaml:"-" json:"-"`
UpdateCommand *command.Command `yaml:"-" json:"-"`
DeleteCommand *command.Command `yaml:"-" json:"-"`
// state attributes
State string `yaml:"state,omitempty" json:"state,omitempty"`
config ConfigurationValueGetter
Resources ResourceMapper `yaml:"-" json:"-"`
}
func init() {
ResourceTypes.Register([]string{"package", string(PackageTypeApk), string(PackageTypeApt), string(PackageTypeDeb), string(PackageTypeDnf), string(PackageTypeRpm), string(PackageTypePip), string(PackageTypeYum)}, func(u *url.URL) Resource {
p := NewPackage()
return p
})
}
func FindSystemPackageType() PackageType {
for _, packageType := range []PackageType{PackageTypeApk, PackageTypeApt, PackageTypeDeb, PackageTypeDnf, PackageTypeRpm, PackageTypePip, PackageTypeYum} {
c := packageType.NewReadCommand()
if c.Exists() {
return packageType
}
}
return PackageTypeApk
}
func NewPackage() *Package {
return &Package{ PackageType: SystemPackageType }
}
func (p *Package) SetResourceMapper(resources ResourceMapper) {
p.Resources = resources
}
func (p *Package) Clone() Resource {
newp := &Package {
Name: p.Name,
Required: p.Required,
Version: p.Version,
PackageType: p.PackageType,
State: p.State,
}
newp.CreateCommand, newp.ReadCommand, newp.UpdateCommand, newp.DeleteCommand = newp.PackageType.NewCRUD()
return newp
}
func (p *Package) StateMachine() machine.Stater {
if p.stater == nil {
p.stater = StorageMachine(p)
}
return p.stater
}
func (p *Package) Notify(m *machine.EventMessage) {
ctx := context.Background()
slog.Info("Notify()", "package", p, "m", m)
switch m.On {
case machine.ENTERSTATEEVENT:
switch m.Dest {
case "start_read":
if _,readErr := p.Read(ctx); readErr == nil {
if triggerErr := p.StateMachine().Trigger("state_read"); triggerErr == nil {
return
} else {
p.State = "absent"
panic(triggerErr)
}
} else {
p.State = "absent"
if ! errors.Is(readErr, ErrResourceStateAbsent) {
panic(readErr)
}
}
case "start_create":
if e := p.Create(ctx); e == nil {
if triggerErr := p.StateMachine().Trigger("created"); triggerErr == nil {
return
}
}
p.State = "absent"
case "start_update":
if e := p.Update(ctx); e == nil {
if triggerErr := p.StateMachine().Trigger("updated"); triggerErr == nil {
return
}
}
case "start_delete":
if deleteErr := p.Delete(ctx); deleteErr == nil {
if triggerErr := p.StateMachine().Trigger("deleted"); triggerErr == nil {
return
} else {
p.State = "present"
panic(triggerErr)
}
} else {
p.State = "present"
panic(deleteErr)
}
case "absent":
p.State = "absent"
case "present", "created", "updated", "read":
p.State = "present"
}
case machine.EXITSTATEEVENT:
}
}
func (p *Package) URI() string {
return fmt.Sprintf("package://%s?version=%s&type=%s", p.Name, p.Version, p.PackageType)
}
func (p *Package) SetURI(uri string) error {
resourceUri, e := url.Parse(uri)
if e == nil {
if resourceUri.Scheme == "package" {
p.Name = filepath.Join(resourceUri.Hostname(), resourceUri.Path)
p.Version = resourceUri.Query().Get("version")
if p.Version == "" {
p.Version = "latest"
}
p.PackageType = PackageType(resourceUri.Query().Get("type"))
if p.PackageType == "" {
e = fmt.Errorf("%w: %s is not a package known resource ", ErrInvalidResourceURI, uri)
}
} else {
e = fmt.Errorf("%w: %s is not a package resource ", ErrInvalidResourceURI, uri)
}
}
return e
}
func (p *Package) UseConfig(config ConfigurationValueGetter) {
p.config = config
}
func (p *Package) JSON() ([]byte, error) {
return json.Marshal(p)
}
func (p *Package) Validate() error {
s := NewSchema(p.Type())
jsonDoc, jsonErr := p.JSON()
if jsonErr == nil {
return s.Validate(string(jsonDoc))
}
return jsonErr
}
func (p *Package) ResolveId(ctx context.Context) string {
slog.Info("Package.ResolveId()", "name", p.Name, "machine.state", p.StateMachine().CurrentState())
/*
imageInspect, _, err := p.apiClient.ImageInspectWithRaw(ctx, p.Name)
if err != nil {
triggerResult := p.StateMachine().Trigger("notexists")
slog.Info("ContainerImage.ResolveId()", "name", p.Name, "machine.state", p.StateMachine().CurrentState(), "resource.state", p.State, "trigger.error", triggerResult)
panic(fmt.Errorf("%w: %s %s", err, p.Type(), p.Name))
}
slog.Info("ContainerImage.ResolveId()", "name", c.Name, "machine.state", c.StateMachine().CurrentState(), "resource.state", c.State)
c.Id = imageInspect.ID
if c.Id != "" {
if triggerErr := c.StateMachine().Trigger("exists"); triggerErr != nil {
panic(fmt.Errorf("%w: %s %s", triggerErr, c.Type(), c.Name))
}
slog.Info("ContainerImage.ResolveId() trigger created", "machine", c.StateMachine(), "state", c.StateMachine().CurrentState())
} else {
if triggerErr := c.StateMachine().Trigger("notexists"); triggerErr != nil {
panic(fmt.Errorf("%w: %s %s", triggerErr, c.Type(), c.Name))
}
slog.Info("ContainerImage.ResolveId()", "name", c.Name, "machine.state", c.StateMachine().CurrentState(), "resource.state", c.State)
}
return c.Id
*/
if p.ReadCommand.Exists() {
if _, err := p.ReadCommand.Execute(p); err != nil {
if triggerResult := p.StateMachine().Trigger("notexists"); triggerResult != nil {
panic(fmt.Errorf("%w: %s %s", err, p.Type(), p.Name))
}
}
/*
exErr := p.ReadCommand.Extractor(out, p)
if exErr != nil {
return nil, exErr
}
return yaml.Marshal(p)
} else {
return nil, ErrUnsupportedPackageType
*/
}
return p.Name
}
func (p *Package) Create(ctx context.Context) error {
if p.Version == "latest" {
p.Version = ""
}
_, err := p.CreateCommand.Execute(p)
if err != nil {
return err
}
_,e := p.Read(ctx)
return e
}
func (p *Package) Update(ctx context.Context) error {
return p.Create(ctx)
}
func (p *Package) Delete(ctx context.Context) error {
_, err := p.DeleteCommand.Execute(p)
if err != nil {
return err
}
_,e := p.Read(ctx)
return e
}
func (p *Package) Apply() error {
if p.Version == "latest" {
p.Version = ""
}
_, err := p.CreateCommand.Execute(p)
if err != nil {
return err
}
_,e := p.Read(context.Background())
return e
}
func (p *Package) Load(r io.Reader) error {
return codec.NewYAMLDecoder(r).Decode(p)
}
func (p *Package) LoadDecl(yamlResourceDeclaration string) error {
return codec.NewYAMLStringDecoder(yamlResourceDeclaration).Decode(p)
}
func (p *Package) Type() string { return "package" }
func (p *Package) Read(ctx context.Context) (resourceYaml []byte, err error) {
if p.ReadCommand.Exists() {
var out []byte
out, err = p.ReadCommand.Execute(p)
if err == nil {
err = p.ReadCommand.Extractor(out, p)
} else {
err = fmt.Errorf("%w - %w", ErrResourceStateAbsent, err)
}
} else {
err = ErrUnsupportedPackageType
}
var yamlErr error
resourceYaml, yamlErr = yaml.Marshal(p)
if err == nil {
err = yamlErr
}
return
}
func (p *Package) UnmarshalJSON(data []byte) error {
if unmarshalErr := json.Unmarshal(data, p); unmarshalErr != nil {
return unmarshalErr
}
p.CreateCommand, p.ReadCommand, p.UpdateCommand, p.DeleteCommand = p.PackageType.NewCRUD()
return nil
}
func (p *Package) UnmarshalYAML(value *yaml.Node) error {
type decodePackage Package
if unmarshalErr := value.Decode((*decodePackage)(p)); unmarshalErr != nil {
return unmarshalErr
}
p.CreateCommand, p.ReadCommand, p.UpdateCommand, p.DeleteCommand = p.PackageType.NewCRUD()
return nil
}
func (p *PackageType) NewCRUD() (create *command.Command, read *command.Command, update *command.Command, del *command.Command) {
switch *p {
case PackageTypeApk:
return NewApkCreateCommand(), NewApkReadCommand(), NewApkUpdateCommand(), NewApkDeleteCommand()
case PackageTypeApt:
return NewAptCreateCommand(), NewAptReadCommand(), NewAptUpdateCommand(), NewAptDeleteCommand()
case PackageTypeDeb:
return NewDebCreateCommand(), NewDebReadCommand(), NewDebUpdateCommand(), NewDebDeleteCommand()
case PackageTypeDnf:
return NewDnfCreateCommand(), NewDnfReadCommand(), NewDnfUpdateCommand(), NewDnfDeleteCommand()
case PackageTypeRpm:
return NewRpmCreateCommand(), NewRpmReadCommand(), NewRpmUpdateCommand(), NewRpmDeleteCommand()
case PackageTypePip:
return NewPipCreateCommand(), NewPipReadCommand(), NewPipUpdateCommand(), NewPipDeleteCommand()
case PackageTypeYum:
return NewYumCreateCommand(), NewYumReadCommand(), NewYumUpdateCommand(), NewYumDeleteCommand()
default:
}
return nil, nil, nil, nil
}
func (p *PackageType) NewReadCommand() (read *command.Command) {
switch *p {
case PackageTypeApk:
return NewApkReadCommand()
case PackageTypeApt:
return NewAptReadCommand()
case PackageTypeDeb:
return NewDebReadCommand()
case PackageTypeDnf:
return NewDnfReadCommand()
case PackageTypeRpm:
return NewRpmReadCommand()
case PackageTypePip:
return NewPipReadCommand()
case PackageTypeYum:
return NewYumReadCommand()
default:
}
return nil
}
func (p *PackageType) NewReadPackagesCommand() (read *command.Command) {
switch *p {
case PackageTypeApk:
return NewApkReadPackagesCommand()
case PackageTypeApt:
return NewAptReadPackagesCommand()
case PackageTypeDeb:
return NewDebReadPackagesCommand()
case PackageTypeDnf:
// return NewDnfReadPackagesCommand()
case PackageTypeRpm:
// return NewRpmReadPackagesCommand()
case PackageTypePip:
// return NewPipReadPackagesCommand()
case PackageTypeYum:
// return NewYumReadPackagesCommand()
default:
}
return nil
}
func (p *PackageType) UnmarshalValue(value string) error {
switch value {
case string(PackageTypeApk), string(PackageTypeApt), string(PackageTypeDeb), string(PackageTypeDnf), string(PackageTypeRpm), string(PackageTypePip), string(PackageTypeYum):
*p = PackageType(value)
return nil
default:
return ErrInvalidPackageType
}
}
func (p *PackageType) UnmarshalJSON(data []byte) error {
var s string
if unmarshalRouteTypeErr := json.Unmarshal(data, &s); unmarshalRouteTypeErr != nil {
return unmarshalRouteTypeErr
}
return p.UnmarshalValue(s)
}
func (p *PackageType) UnmarshalYAML(value *yaml.Node) error {
var s string
if err := value.Decode(&s); err != nil {
return err
}
return p.UnmarshalValue(s)
}
func NewApkCreateCommand() *command.Command {
c := command.NewCommand()
c.Path = "apk"
c.Args = []command.CommandArg{
command.CommandArg("add"),
command.CommandArg("{{ .Name }}{{ .Required }}"),
}
return c
}
func NewApkReadCommand() *command.Command {
c := command.NewCommand()
c.Path = "apk"
c.Args = []command.CommandArg{
command.CommandArg("info"),
command.CommandArg("-ev"),
command.CommandArg("{{ .Name }}"),
}
c.Extractor = func(out []byte, target any) error {
p := target.(*Package)
pkg := strings.Split(string(out), "-")
if pkg[0] == p.Name {
p.Name = pkg[0]
p.Version = pkg[1]
p.State = "present"
} else {
p.State = "absent"
}
return nil
}
return c
}
func NewApkUpdateCommand() *command.Command {
c := command.NewCommand()
c.Path = "apk"
c.Args = []command.CommandArg{
command.CommandArg("add"),
command.CommandArg("{{ .Name }}{{ .Required }}"),
}
return c
}
func NewApkDeleteCommand() *command.Command {
c := command.NewCommand()
c.Path = "apk"
c.Args = []command.CommandArg{
command.CommandArg("del"),
command.CommandArg("{{ .Name }}"),
}
return c
}
func NewApkReadPackagesCommand() *command.Command {
c := command.NewCommand()
c.Path = "apk"
c.Args = []command.CommandArg{
command.CommandArg("list"),
command.CommandArg("--installed"),
}
c.Extractor = func(out []byte, target any) error {
Packages := target.(*[]*Package)
numberOfPackages := len(*Packages)
lines := strings.Split(strings.TrimSpace(string(out)), "\n")
numberOfLines := len(lines)
diff := (numberOfLines - 1) - numberOfPackages
if diff > 0 {
for i := 0; i < diff; i++ {
*Packages = append(*Packages, NewPackage())
}
}
for lineIndex, line := range lines {
p := (*Packages)[lineIndex]
installedPackage := strings.Fields(strings.TrimSpace(line))
packageFields := strings.Split(installedPackage[0], "-")
numberOfFields := len(packageFields)
if numberOfFields > 2 {
packageName := strings.Join(packageFields[:numberOfFields - 3], "-")
packageVersion := strings.Join(packageFields[numberOfFields - 2:numberOfFields - 1], "-")
p.Name = packageName
p.State = "present"
p.Version = packageVersion
}
}
return nil
}
return c
}
func NewAptCreateCommand() *command.Command {
c := command.NewCommand()
c.Path = "apt-get"
c.Split = false
c.Args = []command.CommandArg{
command.CommandArg("satisfy"),
command.CommandArg("-y"),
command.CommandArg("{{ .Name }} ({{ if .Required }}{{ .Required }}{{ else }}>=0.0.0{{ end }})"),
}
return c
}
func NewAptReadCommand() *command.Command {
c := command.NewCommand()
c.Path = "dpkg"
c.Args = []command.CommandArg{
command.CommandArg("-s"),
command.CommandArg("{{ .Name }}"),
}
c.Extractor = func(out []byte, target any) error {
p := target.(*Package)
slog.Info("Extract()", "out", out)
pkginfo := strings.Split(string(out), "\n")
for _, infofield := range pkginfo {
if len(infofield) > 0 && infofield[0] != ' ' {
fieldKeyValue := strings.SplitN(infofield, ":", 2)
if len(fieldKeyValue) > 1 {
key := strings.TrimSpace(fieldKeyValue[0])
value := strings.TrimSpace(fieldKeyValue[1])
switch key {
case "Package":
if value != p.Name {
p.State = "absent"
return nil
}
case "Status":
statusFields := strings.SplitN(value, " ", 3)
if len(statusFields) > 1 {
if statusFields[2] == "installed" {
p.State = "present"
} else {
p.State = "absent"
}
}
case "Version":
p.Version = value
}
}
}
}
slog.Info("Extract()", "package", p)
return nil
}
return c
}
func NewAptUpdateCommand() *command.Command {
c := command.NewCommand()
c.Path = "apt-get"
c.Split = false
c.Args = []command.CommandArg{
command.CommandArg("satisfy"),
command.CommandArg("-y"),
command.CommandArg("{{ .Name }} ({{ if .Required }}{{ .Required }}{{ else }}>=0.0.0{{ end }})"),
}
return c
}
func NewAptDeleteCommand() *command.Command {
c := command.NewCommand()
c.Path = "apt"
c.FailOnError = false
c.Args = []command.CommandArg{
command.CommandArg("remove"),
command.CommandArg("{{ .Name }}"),
}
return c
}
func NewAptReadPackagesCommand() *command.Command {
c := command.NewCommand()
c.Path = "apt"
c.FailOnError = false
c.Args = []command.CommandArg{
command.CommandArg("list"),
command.CommandArg("--installed"),
}
c.Env = []string{ "DEBIAN_FRONTEND=noninteractive" }
c.Extractor = func(out []byte, target any) error {
Packages := target.(*[]*Package)
numberOfPackages := len(*Packages)
lines := strings.Split(strings.TrimSpace(string(out)), "\n")
numberOfLines := len(lines)
diff := (numberOfLines - 1) - numberOfPackages
if diff > 0 {
for i := 0; i < diff; i++ {
*Packages = append(*Packages, NewPackage())
}
}
for lineIndex, line := range lines[1:] {
p := (*Packages)[lineIndex]
installedPackage := strings.Fields(strings.TrimSpace(line))
packageFields := strings.Split(installedPackage[0], "/")
packageName := packageFields[0]
packageVersion := installedPackage[1]
p.Name = packageName
p.State = "present"
p.Version = packageVersion
p.PackageType = PackageTypeApt
}
return nil
}
return c
}
func NewDebCreateCommand() *command.Command {
c := command.NewCommand()
c.Path = "dpkg"
c.Split = false
c.Args = []command.CommandArg{
command.CommandArg("-i"),
command.CommandArg("{{ .Source }}"),
}
return c
}
func NewDebReadCommand() *command.Command {
c := command.NewCommand()
c.Path = "dpkg"
c.Args = []command.CommandArg{
command.CommandArg("-s"),
command.CommandArg("{{ .Name }}"),
}
c.Extractor = func(out []byte, target any) error {
p := target.(*Package)
slog.Info("Extract()", "out", out)
pkginfo := strings.Split(string(out), "\n")
for _, infofield := range pkginfo {
if len(infofield) > 0 && infofield[0] != ' ' {
fieldKeyValue := strings.SplitN(infofield, ":", 2)
if len(fieldKeyValue) > 1 {
key := strings.TrimSpace(fieldKeyValue[0])
value := strings.TrimSpace(fieldKeyValue[1])
switch key {
case "Package":
if value != p.Name {
p.State = "absent"
return nil
}
case "Status":
statusFields := strings.SplitN(value, " ", 3)
if len(statusFields) > 1 {
if statusFields[2] == "installed" {
p.State = "present"
} else {
p.State = "absent"
}
}
case "Version":
p.Version = value
}
}
}
}
slog.Info("Extract()", "package", p)
return nil
}
return c
}
func NewDebUpdateCommand() *command.Command {
c := command.NewCommand()
c.Path = "dpkg"
c.Args = []command.CommandArg{
command.CommandArg("-i"),
command.CommandArg("{{ .Source }}"),
}
return c
}
func NewDebDeleteCommand() *command.Command {
c := command.NewCommand()
c.Path = "dpkg"
c.Args = []command.CommandArg{
command.CommandArg("-r"),
command.CommandArg("{{ .Name }}"),
}
return c
}
func NewDebReadPackagesCommand() *command.Command {
c := command.NewCommand()
c.Path = "dpkg-query"
c.FailOnError = false
c.Split = false
c.Args = []command.CommandArg{
command.CommandArg("-W"),
command.CommandArg("-f"),
command.CommandArg("${binary:Package} ${Version} ${Status}\n"),
}
c.Env = []string{ "DEBIAN_FRONTEND=noninteractive" }
c.Extractor = func(out []byte, target any) error {
Packages := target.(*[]*Package)
lines := strings.Split(strings.TrimSpace(string(out)), "\n")
lineIndex := 0
for _, line := range lines {
installedPackage := strings.Fields(strings.TrimSpace(line))
status := strings.Join(installedPackage[2:], " ")
if status == "install ok installed" {
if len(*Packages) <= lineIndex + 1 {
*Packages = append(*Packages, NewPackage())
}
p := (*Packages)[lineIndex]
packageNameFields := strings.Split(installedPackage[0], ":")
packageName := packageNameFields[0]
packageVersionFields := strings.Split(installedPackage[1], ":")
if len(packageVersionFields) > 1 {
p.Version = packageVersionFields[1]
} else {
p.Version = packageVersionFields[0]
}
p.Name = packageName
p.State = "present"
p.PackageType = PackageTypeDeb
lineIndex++
}
}
return nil
}
return c
}
func NewDnfCreateCommand() *command.Command {
c := command.NewCommand()
c.Path = "dnf"
c.Split = false
c.Args = []command.CommandArg{
command.CommandArg("install"),
command.CommandArg("-q"),
command.CommandArg("-y"),
command.CommandArg("{{ .Name }}{{ if .Required }}{{ .Required }}{{ else }}>=0.0.0{{ end }}"),
}
return c
}
func NewDnfReadCommand() *command.Command {
c := command.NewCommand()
c.Path = "dnf"
c.Args = []command.CommandArg{
command.CommandArg("-q"),
command.CommandArg("list"),
command.CommandArg("installed"),
command.CommandArg("{{ .Name }}"),
}
c.Extractor = func(out []byte, target any) error {
p := target.(*Package)
slog.Info("Extract()", "out", out)
pkginfo := strings.Split(string(out), "\n")
for _, packageLines := range pkginfo {
fields := strings.Fields(packageLines)
packageNameField := strings.Split(fields[0], ".")
packageName := strings.TrimSpace(packageNameField[0])
//packageArch := strings.TrimSpace(packageNameField[1])
if packageName == p.Name {
p.State = "present"
packageVersionField := strings.Split(fields[1], ":")
//packageEpoch := strings.TrimSpace(packageVersionField[0])
packageVersion := strings.TrimSpace(packageVersionField[1])
p.Version = packageVersion
return nil
}
}
p.State = "absent"
slog.Info("Extract()", "package", p)
return nil
}
return c
}
func NewDnfUpdateCommand() *command.Command {
c := command.NewCommand()
c.Path = "dnf"
c.Args = []command.CommandArg{
command.CommandArg("-q"),
command.CommandArg("-y"),
command.CommandArg("install"),
command.CommandArg("{{ .Name }}"),
}
return c
}
func NewDnfDeleteCommand() *command.Command {
c := command.NewCommand()
c.Path = "dnf"
c.Args = []command.CommandArg{
command.CommandArg("-q"),
command.CommandArg("-y"),
command.CommandArg("remove"),
command.CommandArg("{{ .Name }}"),
}
return c
}
func NewRpmCreateCommand() *command.Command {
c := command.NewCommand()
c.Path = "rpm"
c.Split = false
c.Args = []command.CommandArg{
command.CommandArg("-i"),
command.CommandArg("{{ .Source }}"),
}
return c
}
func NewRpmReadCommand() *command.Command {
c := command.NewCommand()
c.Path = "rpm"
c.Args = []command.CommandArg{
command.CommandArg("-q"),
command.CommandArg("{{ .Name }}"),
}
c.Extractor = func(out []byte, target any) error {
p := target.(*Package)
slog.Info("Extract()", "out", out)
pkginfo := strings.Split(string(out), "\n")
for _, packageLine := range pkginfo {
packageFields := strings.Split(packageLine, "-")
numberOfFields := len(packageFields)
if numberOfFields > 2 {
packageName := strings.Join(packageFields[:numberOfFields - 3], "-")
packageVersion := strings.Join(packageFields[numberOfFields - 2:numberOfFields - 1], "-")
if packageName == p.Name {
p.State = "present"
p.Version = packageVersion
return nil
}
}
}
p.State = "absent"
slog.Info("Extract()", "package", p)
return nil
}
return c
}
func NewRpmUpdateCommand() *command.Command {
c := command.NewCommand()
c.Path = "rpm"
c.Args = []command.CommandArg{
command.CommandArg("-i"),
command.CommandArg("{{ .Source }}"),
}
return c
}
func NewRpmDeleteCommand() *command.Command {
c := command.NewCommand()
c.Path = "rpm"
c.Args = []command.CommandArg{
command.CommandArg("-e"),
command.CommandArg("{{ .Name }}"),
}
return c
}
func NewPipCreateCommand() *command.Command {
c := command.NewCommand()
c.Path = "pip"
c.Split = false
c.Args = []command.CommandArg{
command.CommandArg("install"),
command.CommandArg("{{ .Name }}{{ if .Required }}{{ .Required }}{{ else }}>=0.0.0{{ end }}"),
}
return c
}
func NewPipReadCommand() *command.Command {
c := command.NewCommand()
c.Path = "pip"
c.Args = []command.CommandArg{
command.CommandArg("list"),
}
c.Extractor = func(out []byte, target any) error {
p := target.(*Package)
pkginfo := strings.Split(string(out), "\n")
for _, packageLine := range pkginfo[2:] {
packageFields := strings.Fields(packageLine)
numberOfFields := len(packageFields)
if numberOfFields == 2 {
packageName := packageFields[0]
packageVersion := packageFields[1]
if packageName == p.Name {
p.State = "present"
p.Version = packageVersion
return nil
}
}
}
p.State = "absent"
slog.Info("Extract()", "package", p)
return nil
}
return c
}
func NewPipUpdateCommand() *command.Command {
c := command.NewCommand()
c.Path = "pip"
c.Args = []command.CommandArg{
command.CommandArg("install"),
command.CommandArg("{{ .Name }}{{ if .Required }}{{ .Required }}{{ else }}>=0.0.0{{ end }}"),
}
return c
}
func NewPipDeleteCommand() *command.Command {
c := command.NewCommand()
c.Path = "pip"
c.Args = []command.CommandArg{
command.CommandArg("uninstall"),
command.CommandArg("{{ .Name }}"),
}
return c
}
func NewYumCreateCommand() *command.Command {
c := command.NewCommand()
c.Path = "yum"
c.Split = false
c.Args = []command.CommandArg{
command.CommandArg("install"),
command.CommandArg("-q"),
command.CommandArg("-y"),
command.CommandArg("{{ .Name }}{{ if .Required }}{{ .Required }}{{ else }}>=0.0.0{{ end }}"),
}
return c
}
func NewYumReadCommand() *command.Command {
c := command.NewCommand()
c.Path = "yum"
c.Args = []command.CommandArg{
command.CommandArg("-q"),
command.CommandArg("list"),
command.CommandArg("installed"),
command.CommandArg("{{ .Name }}"),
}
c.Extractor = func(out []byte, target any) error {
p := target.(*Package)
slog.Info("Extract()", "out", out)
pkginfo := strings.Split(string(out), "\n")
for _, packageLines := range pkginfo {
fields := strings.Fields(packageLines)
packageNameField := strings.Split(fields[0], ".")
packageName := strings.TrimSpace(packageNameField[0])
//packageArch := strings.TrimSpace(packageNameField[1])
if packageName == p.Name {
p.State = "present"
packageVersionField := strings.Split(fields[1], ":")
//packageEpoch := strings.TrimSpace(packageVersionField[0])
packageVersion := strings.TrimSpace(packageVersionField[1])
p.Version = packageVersion
return nil
}
}
p.State = "absent"
slog.Info("Extract()", "package", p)
return nil
}
return c
}
func NewYumUpdateCommand() *command.Command {
c := command.NewCommand()
c.Path = "yum"
c.Args = []command.CommandArg{
command.CommandArg("-q"),
command.CommandArg("-y"),
command.CommandArg("install"),
command.CommandArg("{{ .Name }}"),
}
return c
}
func NewYumDeleteCommand() *command.Command {
c := command.NewCommand()
c.Path = "yum"
c.Args = []command.CommandArg{
command.CommandArg("-q"),
command.CommandArg("-y"),
command.CommandArg("remove"),
command.CommandArg("{{ .Name }}"),
}
return c
}