1128 lines
29 KiB
Go
1128 lines
29 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"
|
|
"decl/internal/data"
|
|
"decl/internal/folio"
|
|
"decl/internal/tempdir"
|
|
)
|
|
|
|
var (
|
|
PackageTempDir tempdir.Path = "jx_package_resource"
|
|
)
|
|
|
|
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")
|
|
ErrInvalidPackageType error = errors.New("invalid PackageType value")
|
|
ErrRpmPackageInstalled error = errors.New("is already installed")
|
|
)
|
|
|
|
var SupportedPackageTypes []PackageType = []PackageType{PackageTypeApk, PackageTypeApt, PackageTypeDeb, PackageTypeDnf, PackageTypeRpm, PackageTypePip, PackageTypeYum}
|
|
|
|
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"`
|
|
SourceRef folio.ResourceReference `yaml:"sourceref,omitempty" json:"sourceref,omitempty"`
|
|
|
|
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 data.ConfigurationValueGetter
|
|
Resources data.ResourceMapper `yaml:"-" json:"-"`
|
|
}
|
|
|
|
func init() {
|
|
folio.DocumentRegistry.ResourceTypes.Register([]string{"package", string(PackageTypeApk), string(PackageTypeApt), string(PackageTypeDeb), string(PackageTypeDnf), string(PackageTypeRpm), string(PackageTypePip), string(PackageTypeYum)}, func(u *url.URL) data.Resource {
|
|
p := NewPackage()
|
|
e := p.SetParsedURI(u)
|
|
slog.Info("PackageFactory SetParsedURI()", "error", e)
|
|
return p
|
|
})
|
|
}
|
|
|
|
func FindSystemPackageType() PackageType {
|
|
for _, packageType := range SupportedPackageTypes {
|
|
c := packageType.NewReadCommand()
|
|
if c.Exists() {
|
|
return packageType
|
|
}
|
|
}
|
|
return PackageTypeApk
|
|
}
|
|
|
|
func NewPackage() *Package {
|
|
return &Package{ PackageType: SystemPackageType }
|
|
}
|
|
|
|
func (p *Package) SetResourceMapper(resources data.ResourceMapper) {
|
|
p.Resources = resources
|
|
}
|
|
|
|
func (p *Package) Clone() data.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_stat":
|
|
if statErr := p.ReadStat(ctx); statErr == nil {
|
|
if triggerErr := p.StateMachine().Trigger("exists"); triggerErr == nil {
|
|
return
|
|
}
|
|
} else {
|
|
if triggerErr := p.StateMachine().Trigger("notexists"); triggerErr == nil {
|
|
return
|
|
}
|
|
}
|
|
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
|
|
}
|
|
} else {
|
|
panic(e)
|
|
}
|
|
// 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) ReadStat(ctx context.Context) (err error) {
|
|
if p.ReadCommand.Exists() {
|
|
_, err = p.ReadCommand.Execute(p)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (p *Package) URI() string {
|
|
return fmt.Sprintf("package://%s?version=%s&type=%s", p.Name, url.QueryEscape(p.Version), p.PackageType)
|
|
}
|
|
|
|
|
|
func (p *Package) SetURI(uri string) error {
|
|
resourceUri, e := url.Parse(uri)
|
|
if e == nil {
|
|
e = p.SetParsedURI(resourceUri)
|
|
}
|
|
return e
|
|
}
|
|
|
|
func (p *Package) SetParsedURI(uri *url.URL) (err error) {
|
|
if uri.Scheme == "package" {
|
|
p.Name = filepath.Join(uri.Hostname(), uri.Path)
|
|
p.Version = uri.Query().Get("version")
|
|
if p.Version == "" {
|
|
p.Version = "latest"
|
|
}
|
|
indicatedPackageType := PackageType(uri.Query().Get("type"))
|
|
if indicatedPackageType.Validate() != nil {
|
|
p.PackageType = SystemPackageType
|
|
}
|
|
} else {
|
|
err = fmt.Errorf("%w: %s is not a package resource ", ErrInvalidResourceURI, uri.String())
|
|
}
|
|
p.CreateCommand, p.ReadCommand, p.UpdateCommand, p.DeleteCommand = p.PackageType.NewCRUD()
|
|
return
|
|
}
|
|
|
|
func (p *Package) UseConfig(config data.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())
|
|
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))
|
|
}
|
|
}
|
|
}
|
|
return p.Name
|
|
}
|
|
|
|
func (p *Package) Create(ctx context.Context) (err error) {
|
|
if p.Version == "latest" {
|
|
p.Version = ""
|
|
}
|
|
|
|
slog.Info("Package.Create()")
|
|
if source := p.SourceRef.Lookup(p.Resources); source != nil {
|
|
r, _ := source.ContentReaderStream()
|
|
if p.CreateCommand.StdinAvailable {
|
|
p.CreateCommand.SetStdinReader(r)
|
|
} else {
|
|
if err = PackageTempDir.Create(); err != nil {
|
|
return
|
|
}
|
|
defer PackageTempDir.Remove()
|
|
defer func() { p.Source = "" }()
|
|
if p.Source, err = PackageTempDir.CreateFileFromReader(fmt.Sprintf("%s.rpm", p.Name), r); err != nil {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
if _, err = p.CreateCommand.Execute(p); err != nil {
|
|
slog.Info("Package.Create() ERROR", "error", err)
|
|
msg := err.Error()
|
|
lenMsg := len(msg) - 1
|
|
lenErr := len(ErrRpmPackageInstalled.Error())
|
|
if msg[lenMsg - lenErr:lenMsg] != ErrRpmPackageInstalled.Error() {
|
|
return
|
|
}
|
|
}
|
|
_, err = p.Read(ctx)
|
|
slog.Info("Package.Create()", "package", p, "readerr", err)
|
|
return
|
|
}
|
|
|
|
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() (err error) {
|
|
if p.Version == "latest" {
|
|
p.Version = ""
|
|
}
|
|
if _, err = p.CreateCommand.Execute(p); err != nil {
|
|
return
|
|
}
|
|
|
|
_, err = p.Read(context.Background())
|
|
return
|
|
}
|
|
|
|
func (p *Package) Load(docData []byte, f codec.Format) (err error) {
|
|
err = f.StringDecoder(string(docData)).Decode(p)
|
|
return
|
|
}
|
|
|
|
func (p *Package) LoadReader(r io.ReadCloser, f codec.Format) (err error) {
|
|
err = f.Decoder(r).Decode(p)
|
|
return
|
|
}
|
|
|
|
func (p *Package) LoadString(docData string, f codec.Format) (err error) {
|
|
err = f.StringDecoder(docData).Decode(p)
|
|
return
|
|
}
|
|
|
|
func (p *Package) LoadDecl(yamlResourceDeclaration string) error {
|
|
return p.LoadString(yamlResourceDeclaration, codec.FormatYaml)
|
|
}
|
|
|
|
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)
|
|
}
|
|
slog.Info("Package.Read()", "package", p, "error", 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) Validate() error {
|
|
switch p {
|
|
case PackageTypeApk, PackageTypeApt, PackageTypeDeb, PackageTypeDnf, PackageTypeRpm, PackageTypePip, PackageTypeYum:
|
|
return nil
|
|
default:
|
|
return ErrInvalidPackageType
|
|
}
|
|
}
|
|
|
|
func (p *PackageType) UnmarshalValue(value string) (err error) {
|
|
if err = PackageType(value).Validate(); err == nil {
|
|
*p = PackageType(value)
|
|
}
|
|
return
|
|
}
|
|
|
|
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 (p PackageType) Exists() bool {
|
|
return p.NewReadCommand().Exists()
|
|
}
|
|
|
|
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(strings.TrimSpace(string(out)), "-")
|
|
numberOfFields := len(pkg)
|
|
packageName := strings.Join(pkg[0:numberOfFields - 2], "-")
|
|
packageVersion := strings.Join(pkg[numberOfFields - 2:numberOfFields - 1], "-")
|
|
|
|
if packageName == p.Name {
|
|
p.Name = packageName
|
|
p.Version = packageVersion
|
|
p.State = "present"
|
|
} else {
|
|
slog.Info("NewApkReadCommand().Extrctor() mismatch", "name", p.Name, "parsed", packageName)
|
|
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) - 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[0:numberOfFields - 2], "-")
|
|
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[1:] {
|
|
if len(packageLines) > 0 {
|
|
fields := strings.Fields(packageLines)
|
|
slog.Info("DnfReadCommaond.Extract()", "fields", fields, "package", p)
|
|
|
|
packageNameField := strings.Split(fields[0], ".")
|
|
lenName := len(packageNameField)
|
|
packageName := strings.TrimSpace(strings.Join(packageNameField[0:lenName - 1], "."))
|
|
|
|
if packageName == p.Name {
|
|
p.State = "present"
|
|
packageVersionField := strings.Split(fields[1], ":")
|
|
if len(packageVersionField) > 1 {
|
|
//packageEpoch := strings.TrimSpace(packageVersionField[0])
|
|
p.Version = strings.TrimSpace(packageVersionField[1])
|
|
} else {
|
|
p.Version = strings.TrimSpace(packageVersionField[0])
|
|
}
|
|
return nil
|
|
}
|
|
slog.Info("DnfReadCommaond.Extract()", "package", packageName, "package", p)
|
|
}
|
|
}
|
|
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 NewDnfReadPackagesCommand() *command.Command {
|
|
c := command.NewCommand()
|
|
c.Path = "dnf"
|
|
c.FailOnError = false
|
|
c.Split = false
|
|
c.Args = []command.CommandArg{
|
|
command.CommandArg("-q"),
|
|
command.CommandArg("list"),
|
|
command.CommandArg("installed"),
|
|
}
|
|
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[1:] {
|
|
installedPackage := strings.Fields(strings.TrimSpace(line))
|
|
if len(*Packages) <= lineIndex + 1 {
|
|
*Packages = append(*Packages, NewPackage())
|
|
}
|
|
p := (*Packages)[lineIndex]
|
|
packageNameFields := strings.Split(installedPackage[0], ".")
|
|
packageName := packageNameFields[0]
|
|
//packageArch := packageNameFields[1]
|
|
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 = PackageTypeDnf
|
|
lineIndex++
|
|
}
|
|
return nil
|
|
}
|
|
return c
|
|
}
|
|
|
|
|
|
func NewRpmCreateCommand() *command.Command {
|
|
c := command.NewCommand()
|
|
c.Path = "rpm"
|
|
c.Split = false
|
|
c.StdinAvailable = 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 {
|
|
// package-name-ver-rel.arch
|
|
packageFields := strings.Split(packageLine, "-")
|
|
numberOfFields := len(packageFields)
|
|
if numberOfFields > 2 {
|
|
packageName := strings.Join(packageFields[:numberOfFields - 2], "-")
|
|
packageVersion := strings.Join(packageFields[numberOfFields - 2:numberOfFields - 1], "-")
|
|
slog.Info("Package[RPM].Extract()", "name", packageName, "version", packageVersion, "package", p)
|
|
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 NewRpmReadPackagesCommand() *command.Command {
|
|
return nil
|
|
}
|
|
|
|
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 NewPipReadPackagesCommand() *command.Command {
|
|
return nil
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
func NewYumReadPackagesCommand() *command.Command {
|
|
return nil
|
|
}
|