fixed push authentication for container_image resource
Some checks failed
Lint / golangci-lint (push) Failing after 10m58s
Declarative Tests / test (push) Failing after 15s
Declarative Tests / build-fedora (push) Failing after 1m40s
Declarative Tests / build-ubuntu-focal (push) Failing after 1m14s

This commit is contained in:
Matthew Rich 2024-09-26 06:46:16 +00:00
parent 2bee7f6bea
commit b08d025567
9 changed files with 485 additions and 42 deletions

5
go.mod
View File

@ -3,7 +3,7 @@ module decl
go 1.22.5 go 1.22.5
require ( require (
// gitea.rosskeen.house/pylon/luaruntime v0.0.0-20240513200425-f413d8adf7b3 // gitea.rosskeen.house/pylon/luaruntime v0.0.0-20240513200425-f413d8adf7b3
gitea.rosskeen.house/rosskeen.house/machine v0.0.0-20240520193117-1835255b6d02 gitea.rosskeen.house/rosskeen.house/machine v0.0.0-20240520193117-1835255b6d02
github.com/docker/docker v27.0.3+incompatible github.com/docker/docker v27.0.3+incompatible
github.com/docker/go-connections v0.5.0 github.com/docker/go-connections v0.5.0
@ -14,7 +14,10 @@ require (
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
require google.golang.org/protobuf v1.33.0
require ( require (
gitea.rosskeen.house/pylon/luaruntime v0.0.0-20240924031921-4d00743b53e1 // indirect
github.com/Microsoft/go-winio v0.4.14 // indirect github.com/Microsoft/go-winio v0.4.14 // indirect
github.com/containerd/log v0.1.0 // indirect github.com/containerd/log v0.1.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect

4
go.sum
View File

@ -1,9 +1,9 @@
gitea.rosskeen.house/pylon/luaruntime v0.0.0-20240513200425-f413d8adf7b3 h1:ge74Hmzxp+bqVwSK9hOOBlZB9KeL3xuwMIXAYLPHBxA= gitea.rosskeen.house/pylon/luaruntime v0.0.0-20240513200425-f413d8adf7b3 h1:ge74Hmzxp+bqVwSK9hOOBlZB9KeL3xuwMIXAYLPHBxA=
gitea.rosskeen.house/pylon/luaruntime v0.0.0-20240513200425-f413d8adf7b3/go.mod h1:9sKIXsGDcf1uBnHhY29wi38Vll8dpVNUOxkXphN2KEk= gitea.rosskeen.house/pylon/luaruntime v0.0.0-20240513200425-f413d8adf7b3/go.mod h1:9sKIXsGDcf1uBnHhY29wi38Vll8dpVNUOxkXphN2KEk=
gitea.rosskeen.house/pylon/luaruntime v0.0.0-20240924031921-4d00743b53e1 h1:UT79l0TvkYjlAbJrsFIm6R0tL+Rl/814ThKbjOgrTPo=
gitea.rosskeen.house/pylon/luaruntime v0.0.0-20240924031921-4d00743b53e1/go.mod h1:9sKIXsGDcf1uBnHhY29wi38Vll8dpVNUOxkXphN2KEk=
gitea.rosskeen.house/rosskeen.house/machine v0.0.0-20240520193117-1835255b6d02 h1:FLRmUvu0mz8Ac+/VZf/P4yuv2e6++SSkKOcEIHSlpAI= gitea.rosskeen.house/rosskeen.house/machine v0.0.0-20240520193117-1835255b6d02 h1:FLRmUvu0mz8Ac+/VZf/P4yuv2e6++SSkKOcEIHSlpAI=
gitea.rosskeen.house/rosskeen.house/machine v0.0.0-20240520193117-1835255b6d02/go.mod h1:5J2OFjFIBaCfsjcC9kSyycbIL8g/qAJH2A8BnbIig+Y= gitea.rosskeen.house/rosskeen.house/machine v0.0.0-20240520193117-1835255b6d02/go.mod h1:5J2OFjFIBaCfsjcC9kSyycbIL8g/qAJH2A8BnbIig+Y=
gitea.rosskeen.house/rosskeen.house/testing v0.0.0-20240509163950-64f2fc3e00d5 h1:1TUeKrJ12K6+Iobc8rpL/gUaGPFBmTqKjJnkT+2B5nM=
gitea.rosskeen.house/rosskeen.house/testing v0.0.0-20240509163950-64f2fc3e00d5/go.mod h1:gbxopbzqpz0ZMAcsPu2XqtprOoFdxwTGz45p06zuI0A=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU= github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=

View File

@ -10,7 +10,7 @@ _ "decl/internal/config"
_ "decl/internal/resource" _ "decl/internal/resource"
"decl/internal/fs" "decl/internal/fs"
"decl/internal/builtin" "decl/internal/builtin"
_ "errors" "errors"
"fmt" "fmt"
"context" "context"
"log/slog" "log/slog"
@ -19,6 +19,7 @@ _ "errors"
var ( var (
ErrFailedResources error = errors.New("Failed Resources")
) )
type App struct { type App struct {
@ -178,7 +179,7 @@ func (a *App) Apply(ctx context.Context, deleteResources bool) (err error) {
return e return e
} }
if d.Failures() > 0 { if d.Failures() > 0 {
err = fmt.Errorf("Failed resources: %d, %w", d.Failures(), err) err = fmt.Errorf("%w: %d, %w", ErrFailedResources, d.Failures(), err)
} }
} }
return return
@ -223,7 +224,10 @@ func (a *App) ApplyCmd(ctx context.Context, docs []string, quiet bool, deleteRes
} }
if err = a.Apply(ctx, deleteResources); err != nil { if err = a.Apply(ctx, deleteResources); err != nil {
return slog.Info("Client.ApplyCmd()", "client", a, "error", err)
if ! errors.Is(err, ErrFailedResources) {
return
}
} }
if quiet { if quiet {

View File

@ -204,13 +204,21 @@ func (d *Declaration) Apply(stateTransition string) (result error) {
} }
func (d *Declaration) SetConfig(configDoc data.Document) { func (d *Declaration) SetConfig(configDoc data.Document) {
slog.Info("Declaration.SetConfig()", "config", configDoc)
if configDoc != nil { if configDoc != nil {
if configDoc.Has(string(d.Config)) { if configDoc.Has(string(d.Config)) {
v, _ := configDoc.Get(string(d.Config)) if v, ok := configDoc.Get(string(d.Config)); ok {
d.configBlock = v.(data.Block) d.configBlock = v.(data.Block)
d.Attributes.UseConfig(d.configBlock) d.Attributes.UseConfig(d.configBlock)
return
}
} }
} }
if v, ok := DocumentRegistry.ConfigNameMap.Get(string(d.Config)); ok {
d.configBlock = v
d.Attributes.UseConfig(d.configBlock)
}
} }
func (d *Declaration) SetURI(uri string) (err error) { func (d *Declaration) SetURI(uri string) (err error) {

View File

@ -263,35 +263,37 @@ func (d *Document) Apply(state string) error {
if state == "delete" { if state == "delete" {
start = len(d.ResourceDeclarations) - 1 start = len(d.ResourceDeclarations) - 1
} }
for { if len(d.ResourceDeclarations) > 0 {
idx := i - start for {
if idx < 0 { idx = - idx } idx := i - start
if idx < 0 { idx = - idx }
slog.Info("Document.Apply() applying resource", "index", idx, "uri", d.ResourceDeclarations[idx].Resource().URI(), "resource", d.ResourceDeclarations[idx].Resource()) slog.Info("Document.Apply() applying resource", "index", idx, "uri", d.ResourceDeclarations[idx].Resource().URI(), "resource", d.ResourceDeclarations[idx].Resource())
d.ResourceDeclarations[idx].SetConfig(d.config) d.ResourceDeclarations[idx].SetConfig(d.config)
slog.Info("Document.Apply() applying resource", "index", idx, "uri", d.ResourceDeclarations[idx].Resource().URI(), "resource", d.ResourceDeclarations[idx].Resource()) slog.Info("Document.Apply() applying resource", "index", idx, "uri", d.ResourceDeclarations[idx].Resource().URI(), "resource", d.ResourceDeclarations[idx].Resource())
if d.ResourceDeclarations[idx].Requires.Check() { if d.ResourceDeclarations[idx].Requires.Check() {
if e := d.ResourceDeclarations[idx].Apply(state); e != nil { if e := d.ResourceDeclarations[idx].Apply(state); e != nil {
slog.Error("Document.Apply() error applying resource", "index", idx, "uri", d.ResourceDeclarations[idx].Resource().URI()) slog.Error("Document.Apply() error applying resource", "index", idx, "uri", d.ResourceDeclarations[idx].Resource().URI())
d.ResourceDeclarations[idx].Error = e.Error() d.ResourceDeclarations[idx].Error = e.Error()
switch d.ResourceDeclarations[idx].OnError.GetStrategy() { switch d.ResourceDeclarations[idx].OnError.GetStrategy() {
case OnErrorStop: case OnErrorStop:
return e return e
case OnErrorFail: case OnErrorFail:
d.failedResources++ d.failedResources++
}
} }
} else {
d.ResourceDeclarations[idx].Error = fmt.Sprintf("Constraint failure: %s", d.ResourceDeclarations[idx].Requires)
} }
} else { if i >= len(d.ResourceDeclarations) - 1 {
d.ResourceDeclarations[idx].Error = fmt.Sprintf("Constraint failure: %s", d.ResourceDeclarations[idx].Requires) break
}
i++
} }
if i >= len(d.ResourceDeclarations) - 1 {
break
}
i++
} }
return nil return nil
} }

View File

@ -34,6 +34,26 @@ const (
ContainerImageTypeName TypeName = "container-image" ContainerImageTypeName TypeName = "container-image"
) )
type ContainerProgressDetail struct {
ProgressMessage string `json:"message,omitempty" yaml:"message,omitempty"`
}
type ContainerLogStatus struct {
Status string `json:"status,omitempty" yaml:"status,omitempty"`
ProgressDetail ContainerProgressDetail `json:"progressDetail,omitempty" yaml:"progressDetail,omitempty"`
Id string `json:"id,omitempty" yaml:"id,omitempty"`
}
type ContainerLogStream struct {
Stream string `json:"stream,omitempty" yaml:"stream,omitempty"`
}
type ContainerLog struct {
*ContainerLogStream `json:",inline" yaml:",inline"`
*ContainerLogStatus `json:",inline" yaml:",inline"`
*ContainerError `json:",inline" yaml:",inline"`
}
type ContainerErrorDetail struct { type ContainerErrorDetail struct {
ErrorMessage string `json:"message" yaml:"message"` ErrorMessage string `json:"message" yaml:"message"`
} }
@ -69,7 +89,8 @@ type ContainerImage struct {
ContextRef folio.ResourceReference `json:"contextref,omitempty" yaml:"contextref,omitempty"` ContextRef folio.ResourceReference `json:"contextref,omitempty" yaml:"contextref,omitempty"`
InjectJX bool `json:"injectjx,omitempty" yaml:"injectjx,omitempty"` InjectJX bool `json:"injectjx,omitempty" yaml:"injectjx,omitempty"`
PushImage bool `json:"push,omitempty" yaml:"push,omitempty"` PushImage bool `json:"push,omitempty" yaml:"push,omitempty"`
Output strings.Builder `json:"output,omitempty" yaml:"output,omitempty"` Output []ContainerLog `json:"output,omitempty" yaml:"output,omitempty"`
outputWriter strings.Builder `json:"-" yaml:"-"`
apiClient ContainerImageClient apiClient ContainerImageClient
Resources data.ResourceMapper `json:"-" yaml:"-"` Resources data.ResourceMapper `json:"-" yaml:"-"`
@ -118,7 +139,7 @@ func (c *ContainerImage) RegistryAuthConfig() (authConfig registry.AuthConfig, e
authConfig.Password = configValue.(string) authConfig.Password = configValue.(string)
} }
if configValue, err = c.config.GetValue("repo_server"); err != nil { if configValue, err = c.config.GetValue("repo_server"); err != nil {
return return authConfig, nil
} else { } else {
authConfig.ServerAddress = configValue.(string) authConfig.ServerAddress = configValue.(string)
} }
@ -141,11 +162,13 @@ func (c *ContainerImage) RegistryLogin(context context.Context) (token string, e
func (c *ContainerImage) RegistryAuth() (string, error) { func (c *ContainerImage) RegistryAuth() (string, error) {
if authConfig, err := c.RegistryAuthConfig(); err == nil { if authConfig, err := c.RegistryAuthConfig(); err == nil {
if encodedJSON, jsonErr := json.Marshal(authConfig); jsonErr == nil { if encodedJSON, jsonErr := json.Marshal(authConfig); jsonErr == nil {
slog.Info("ContainerImage.RegistryAuth()", "auth", authConfig, "encoded", encodedJSON, "error", jsonErr)
return base64.URLEncoding.EncodeToString(encodedJSON), nil return base64.URLEncoding.EncodeToString(encodedJSON), nil
} else { } else {
return "", jsonErr return "", jsonErr
} }
} else { } else {
slog.Info("ContainerImage.RegistryAuth()", "error", err)
return "", err return "", err
} }
} }
@ -526,6 +549,38 @@ func JXPath() (jxPath folio.URI, err error) {
return return
} }
func (c *ContainerImage) UnmarshalOutput() (err error) {
var containerErr ContainerError
var jsonBody string = c.outputWriter.String()
slog.Info("ContainerImage.UnmarshalOutput()", "json", jsonBody, "error", err)
for _, v := range strings.Split(jsonBody, "\r\n") {
var containerLog ContainerLog
outputDecoder := codec.NewJSONStringDecoder(v)
if decodeErr := outputDecoder.Decode(&containerLog); decodeErr != nil {
if decodeErr == io.EOF {
break
}
slog.Info("ContainerImage.UnmarshalOutput()", "value", v, "error", decodeErr)
err = decodeErr
}
if containerLog.ContainerLogStream != nil || containerLog.ContainerLogStatus != nil {
c.Output = append(c.Output, containerLog)
}
if containerLog.ContainerError != nil {
containerErr = *containerLog.ContainerError
c.Output = append(c.Output, containerLog)
}
slog.Info("ContainerImage.UnmarshalOutput()", "value", v, "error", err)
}
if len(containerErr.Error) > 0 {
return fmt.Errorf("%s", containerErr.Error)
}
return
}
// The contextref can be a tar file or a directory or maybe a loaded document // The contextref can be a tar file or a directory or maybe a loaded document
func (c *ContainerImage) Create(ctx context.Context) (err error) { func (c *ContainerImage) Create(ctx context.Context) (err error) {
dockerfileURI := c.DockerfileRef.Parse() dockerfileURI := c.DockerfileRef.Parse()
@ -580,13 +635,18 @@ func (c *ContainerImage) Create(ctx context.Context) (err error) {
} }
defer buildResponse.Body.Close() defer buildResponse.Body.Close()
if output, outputErr := io.ReadAll(buildResponse.Body); outputErr != nil { copyBuffer := make([]byte, 32 * 1024)
slog.Info("ContainerImage.Create() - ImageBuild()", "output", output, "error", outputErr) if _, err = io.CopyBuffer(&c.outputWriter, buildResponse.Body, copyBuffer); err != nil {
return fmt.Errorf("%w %s %s", outputErr, c.Type(), c.Name) slog.Info("ContainerImage.Create() - ImageBuild()", "error", err)
return fmt.Errorf("%w %s %s", err, c.Type(), c.Name)
} else { } else {
slog.Info("ContainerImage.Create() - ImageBuild()", "output", output, "error", outputErr) if err = c.UnmarshalOutput(); err != nil {
return
}
/*
slog.Info("ContainerImage.Create() - ImageBuild()", "error", err)
var containerErr ContainerError var containerErr ContainerError
for _, jsonBody := range strings.Split(string(output), "\r\n") { for _, jsonBody := range strings.Split(string(c.outputWriter.String()), "\r\n") {
decoder := codec.NewJSONStringDecoder(jsonBody) decoder := codec.NewJSONStringDecoder(jsonBody)
decodeErr := decoder.Decode(&containerErr) decodeErr := decoder.Decode(&containerErr)
slog.Info("ContainerImage.Create() - ImageBuild()", "output", jsonBody, "error", containerErr, "decodeErr", decodeErr) slog.Info("ContainerImage.Create() - ImageBuild()", "output", jsonBody, "error", containerErr, "decodeErr", decodeErr)
@ -594,11 +654,14 @@ func (c *ContainerImage) Create(ctx context.Context) (err error) {
return fmt.Errorf("%s", containerErr.Error) return fmt.Errorf("%s", containerErr.Error)
} }
} }
*/
} }
if c.PushImage { if c.PushImage {
err = c.Push(ctx) err = c.Push(ctx)
slog.Info("ContainerImage.Create() - Push()", "error", err)
} }
err = c.UnmarshalOutput()
} }
return return
} }
@ -628,7 +691,8 @@ func (c *ContainerImage) Push(ctx context.Context) (err error) {
defer response.Close() defer response.Close()
copyBuffer := make([]byte, 32 * 1024) copyBuffer := make([]byte, 32 * 1024)
_, err = io.CopyBuffer(&c.Output, response, copyBuffer) _, err = io.CopyBuffer(&c.outputWriter, response, copyBuffer)
//c.Output = c.outputWriter.String()
return return
} }

View File

@ -25,6 +25,11 @@ _ "net/http/httptest"
"strconv" "strconv"
) )
type MockConfigValueGetter func(key string) (any, error)
func (m MockConfigValueGetter) GetValue(key string) (any, error) {
return m(key)
}
func TestNewContainerImageResource(t *testing.T) { func TestNewContainerImageResource(t *testing.T) {
c := NewContainerImage(&mocks.MockContainerClient{}) c := NewContainerImage(&mocks.MockContainerClient{})
assert.NotNil(t, c) assert.NotNil(t, c)
@ -147,11 +152,24 @@ func TestCreateContainerImagePush(t *testing.T) {
assert.Nil(t, e) assert.Nil(t, e)
assert.Equal(t, "testcontainerimage", c.Name) assert.Equal(t, "testcontainerimage", c.Name)
c.UseConfig(MockConfigValueGetter(func(key string) (any, error) {
switch key {
case "repo_password":
return "bar", nil
case "repo_username":
return "foo", nil
case "repo_server":
return "", nil
}
return nil, fmt.Errorf("%w: %s", data.ErrUnknownConfigurationKey, key)
}))
assert.Nil(t, stater.Trigger("create")) assert.Nil(t, stater.Trigger("create"))
assert.Nil(t, c.Push(context.Background())) assert.Nil(t, c.Push(context.Background()))
assert.True(t, c.PushImage) assert.True(t, c.PushImage)
assert.Equal(t, "foo", c.Output.String()) // assert.Equal(t, "foo", c.Output)
} }
func TestContainerImageContextDocument(t *testing.T) { func TestContainerImageContextDocument(t *testing.T) {
@ -226,3 +244,103 @@ func TestContainerError(t *testing.T) {
assert.Nil(t, decoder.Decode(&err)) assert.Nil(t, decoder.Decode(&err))
assert.Equal(t, expected, err) assert.Equal(t, expected, err)
} }
func TestContainerLogUnmarshal(t *testing.T) {
/*
m := &mocks.MockContainerClient{
InjectImageBuild: func(ctx context.Context, buildContext io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) {
return types.ImageBuildResponse{Body: io.NopCloser(strings.NewReader("image built")) }, nil
},
}
c := NewContainerImage(m)
assert.NotNil(t, c)
*/
buildOutput := `"{\"stream\":\"Step 1/6 : ARG DIST\"}\r\n{\"stream\":\"\\n\"}\r\n{\"stream\":\"Step 2/6 : FROM golang:${DIST}\"}\r\n{\"stream\":\"\\n\"}\r\n{\"errorDetail\":{\"message\":\"invalid reference format\"},\"error\":\"invalid reference format\"}\r\n"`
unqBuildOutput, buildOutputErr := strconv.Unquote(buildOutput)
assert.Nil(t, buildOutputErr)
for _, v := range strings.Split(unqBuildOutput, "\r\n") {
var clog ContainerLog
outputDecoder := codec.NewJSONStringDecoder(v)
err := outputDecoder.Decode(&clog)
if err == io.EOF {
break
}
if clog.ContainerLogStream != nil {
assert.Greater(t, len(clog.ContainerLogStream.Stream), 0)
}
}
//assert.Equal(t, "Step 1/6 : ARG DIST", clog[0].ContainerLogStream.Stream)
}
func TestContainerImageRegistryAuthConfig(t *testing.T) {
m := &mocks.MockContainerClient{
InjectImageBuild: func(ctx context.Context, buildContext io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) {
return types.ImageBuildResponse{Body: io.NopCloser(strings.NewReader("image built")) }, nil
},
}
c := NewContainerImage(m)
assert.NotNil(t, c)
c.UseConfig(MockConfigValueGetter(func(key string) (any, error) {
switch key {
case "repo_password":
return "bar", nil
case "repo_username":
return "foo", nil
case "repo_server":
return "", nil
}
return nil, fmt.Errorf("%w: %s", data.ErrUnknownConfigurationKey, key)
}))
authConfig, err := c.RegistryAuthConfig()
assert.NotNil(t, authConfig)
assert.Nil(t, err)
assert.Equal(t, "bar", authConfig.Password)
assert.Equal(t, "foo", authConfig.Username)
}
func TestContainerImageRegistryAuth(t *testing.T) {
m := &mocks.MockContainerClient{
InjectImageBuild: func(ctx context.Context, buildContext io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) {
return types.ImageBuildResponse{Body: io.NopCloser(strings.NewReader("image built")) }, nil
},
}
c := NewContainerImage(m)
assert.NotNil(t, c)
c.UseConfig(MockConfigValueGetter(func(key string) (any, error) {
switch key {
case "repo_password":
return "bar", nil
case "repo_username":
return "foo", nil
case "repo_server":
return "", nil
}
return nil, fmt.Errorf("%w: %s", data.ErrUnknownConfigurationKey, key)
}))
auth, err := c.RegistryAuth()
assert.NotNil(t, auth)
assert.Nil(t, err)
assert.Greater(t, len(auth), 1)
}

View File

@ -45,6 +45,8 @@ var (
ErrRpmPackageInstalled error = errors.New("is already installed") ErrRpmPackageInstalled error = errors.New("is already installed")
) )
var SupportedPackageTypes []PackageType = []PackageType{PackageTypeApk, PackageTypeApt, PackageTypeDeb, PackageTypeDnf, PackageTypeRpm, PackageTypePip, PackageTypeYum}
var SystemPackageType PackageType = FindSystemPackageType() var SystemPackageType PackageType = FindSystemPackageType()
type Package struct { type Package struct {
@ -76,7 +78,7 @@ func init() {
} }
func FindSystemPackageType() PackageType { func FindSystemPackageType() PackageType {
for _, packageType := range []PackageType{PackageTypeApk, PackageTypeApt, PackageTypeDeb, PackageTypeDnf, PackageTypeRpm, PackageTypePip, PackageTypeYum} { for _, packageType := range SupportedPackageTypes {
c := packageType.NewReadCommand() c := packageType.NewReadCommand()
if c.Exists() { if c.Exists() {
return packageType return packageType
@ -497,15 +499,17 @@ func NewApkReadCommand() *command.Command {
} }
c.Extractor = func(out []byte, target any) error { c.Extractor = func(out []byte, target any) error {
p := target.(*Package) p := target.(*Package)
pkg := strings.Split(string(out), "-") pkg := strings.Split(strings.TrimSpace(string(out)), "-")
numberOfFields := len(pkg) numberOfFields := len(pkg)
packageName := strings.Join(pkg[0:numberOfFields - 3], "-") packageName := strings.Join(pkg[0:numberOfFields - 2], "-")
packageVersion := strings.Join(pkg[numberOfFields - 2:numberOfFields - 1], "-") packageVersion := strings.Join(pkg[numberOfFields - 2:numberOfFields - 1], "-")
if packageName == p.Name { if packageName == p.Name {
p.Name = packageName p.Name = packageName
p.Version = packageVersion p.Version = packageVersion
p.State = "present" p.State = "present"
} else { } else {
slog.Info("NewApkReadCommand().Extrctor() mismatch", "name", p.Name, "parsed", packageName)
p.State = "absent" p.State = "absent"
} }
return nil return nil

View File

@ -32,6 +32,246 @@ func TestPackageApplyResourceTransformation(t *testing.T) {
//assert.Equal(t, nil, e) //assert.Equal(t, nil, e)
} }
func TestCommandExtractor(t *testing.T) {
packages := map[PackageType][]struct{ Name string; Version string; Input string } {
PackageTypeApk: []struct{ Name string; Version string; Input string }{
/*
{ Name: "alpine-baselayout", Version: "3.6.5-r0", Input: `alpine-baselayout-3.6.5-r0 x86_64 {alpine-baselayout} (GPL-2.0-only) [installed]` },
{ Name: "alpine-baselayout-data", Version: "3.6.5-r0", Input: `alpine-baselayout-data-3.6.5-r0 x86_64 {alpine-baselayout} (GPL-2.0-only) [installed]` },
{ Name: "alpine-keys", Version: "2.4-r1", Input: `alpine-keys-2.4-r1 x86_64 {alpine-keys} (MIT) [installed]` },
{ Name: "apk-tools", Version: "2.14.4-r0", Input: `apk-tools-2.14.4-r0 x86_64 {apk-tools} (GPL-2.0-only) [installed]` },
{ Name: "busybox", Version: "1.36.1-r29", Input: `busybox-1.36.1-r29 x86_64 {busybox} (GPL-2.0-only) [installed]` },
{ Name: "busybox-binsh", Version: "1.36.1-r20", Input: `busybox-binsh-1.36.1-r29 x86_64 {busybox} (GPL-2.0-only) [installed]` },
{ Name: "ca-certificates", Version: "20240705-r0", Input: `ca-certificates-20240705-r0 x86_64 {ca-certificates} (MPL-2.0 AND MIT) [installed]` },
{ Name: "ca-certificates-bundle", Version: "20240705-r0", Input: `ca-certificates-bundle-20240705-r0 x86_64 {ca-certificates} (MPL-2.0 AND MIT) [installed]` },
{ Name: "libcrypto3", Version: "3.3.1-r3", Input: `libcrypto3-3.3.1-r3 x86_64 {openssl} (Apache-2.0) [installed]` },
{ Name: "libssl3", Version: "3.3.1-r3", Input: `libssl3-3.3.1-r3 x86_64 {openssl} (Apache-2.0) [installed]` },
{ Name: "musl", Version: "1.2.5-r0", Input: `musl-1.2.5-r0 x86_64 {musl} (MIT) [installed]` },
{ Name: "musl-utils", Version: "1.2.5-r0", Input: `musl-utils-1.2.5-r0 x86_64 {musl} (MIT AND BSD-2-Clause AND GPL-2.0-or-later) [installed]` },
{ Name: "scanelf", Version: "1.3.7-r2", Input: `scanelf-1.3.7-r2 x86_64 {pax-utils} (GPL-2.0-only) [installed]` },
{ Name: "ssl_client", Version: "1.36.1-r29", Input: `ssl_client-1.36.1-r29 x86_64 {busybox} (GPL-2.0-only) [installed]` },
{ Name: "zlib", Version: "1.3.1-r1", Input: `zlib-1.3.1-r1 x86_64 {zlib} (Zlib) [installed]` },
*/
{ Name: "alpine-baselayout", Version: "3.6.5", Input: `alpine-baselayout-3.6.5-r0` },
{ Name: "alpine-baselayout-data", Version: "3.6.5", Input: `alpine-baselayout-data-3.6.5-r0` },
{ Name: "alpine-keys", Version: "2.4", Input: `alpine-keys-2.4-r1` },
{ Name: "apk-tools", Version: "2.14.4", Input: `apk-tools-2.14.4-r0` },
{ Name: "busybox", Version: "1.36.1", Input: `busybox-1.36.1-r29` },
{ Name: "busybox-binsh", Version: "1.36.1", Input: `busybox-binsh-1.36.1-r29` },
{ Name: "ca-certificates", Version: "20240705", Input: `ca-certificates-20240705-r0` },
{ Name: "ca-certificates-bundle", Version: "20240705", Input: `ca-certificates-bundle-20240705-r0` },
{ Name: "libcrypto3", Version: "3.3.1", Input: `libcrypto3-3.3.1-r3` },
{ Name: "libssl3", Version: "3.3.1", Input: `libssl3-3.3.1-r3` },
{ Name: "musl", Version: "1.2.5", Input: `musl-1.2.5-r0` },
{ Name: "musl-utils", Version: "1.2.5", Input: `musl-utils-1.2.5-r0` },
{ Name: "scanelf", Version: "1.3.7", Input: `scanelf-1.3.7-r2` },
{ Name: "ssl_client", Version: "1.36.1", Input: `ssl_client-1.36.1-r29` },
{ Name: "zlib", Version: "1.3.1", Input: `zlib-1.3.1-r1` },
},
PackageTypeApt: []struct{ Name string; Version string; Input string }{
{ Name: "bluez-meshd", Version: "5.64-0ubuntu1pop1~1674313994~22.04~579884f~dev", Input: `
Package: bluez-meshd
Status: install ok installed
Priority: optional
Section: admin
Installed-Size: 937
Maintainer: Ubuntu Bluetooth team <ubuntu-bluetooth@lists.ubuntu.com>
Architecture: amd64
Source: bluez
Version: 5.64-0ubuntu1pop1~1674313994~22.04~579884f~dev
Depends: libc6 (>= 2.34), libdbus-1-3 (>= 1.9.14), libglib2.0-0 (>= 2.28.0), libjson-c5 (>= 0.15), libreadline8 (>= 6.0)
Conffiles:
/etc/dbus-1/system.d/bluetooth-mesh.conf 95c2a66615065ad1195d5b647555c393
Description: bluetooth mesh daemon
The Bluetooth Mesh network is a new Bluetooth feature that extends "Bluetooth
Low Energy (BLE)".
.
This package provides daemon (meshd) and tools that provide Bluetooth mesh
functionality.
.
BlueZ is the official Linux Bluetooth protocol stack. It is an Open Source
project distributed under GNU General Public License (GPL).
Homepage: http://www.bluez.org
Original-Maintainer: Debian Bluetooth Maintainers <pkg-bluetooth-maintainers@lists.alioth.debian.org>
` },
{ Name: "dconf-gsettings-backend", Version: "0.40.0-3", Input: `
Package: dconf-gsettings-backend
Status: install ok installed
Priority: optional
Section: libs
Installed-Size: 83
Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
Architecture: amd64
Multi-Arch: same
Source: dconf
Version: 0.40.0-3
Provides: gsettings-backend
Depends: dconf-service (<< 0.40.0-3.1~), dconf-service (>= 0.40.0-3), libdconf1 (= 0.40.0-3), libc6 (>= 2.14), libglib2.0-0 (>= 2.55.2)
Description: simple configuration storage system - GSettings back-end
DConf is a low-level key/value database designed for storing desktop
environment settings.
.
This package contains a back-end for GSettings. It is needed by
applications accessing settings through GSettings to set custom values
and listen for changes.
Original-Maintainer: Debian GNOME Maintainers <pkg-gnome-maintainers@lists.alioth.debian.org>
Homepage: https://wiki.gnome.org/Projects/dconf
` },
{ Name: "cheese-common", Version: "41.1-1build1", Input: `
Package: cheese-common
Status: install ok installed
Priority: optional
Section: gnome
Installed-Size: 912
Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
Architecture: all
Multi-Arch: foreign
Source: cheese
Version: 41.1-1build1
Depends: dconf-gsettings-backend | gsettings-backend
Description: Common files for the Cheese tool to take pictures and videos
A webcam application that supports image and video capture. Makes
it easy to take photos and videos of you, your friends, pets or whatever
you want. Allows you to apply fancy visual effects, fine-control image
settings and has features such as Multi-Burst mode, Countdown timer
for photos.
.
This package contains the common files and translations.
Original-Maintainer: Debian GNOME Maintainers <pkg-gnome-maintainers@lists.alioth.debian.org>
Homepage: https://wiki.gnome.org/Apps/Cheese
` },
{ Name: "fonts-lohit-deva", Version: "2.95.4-4", Input: `
Package: fonts-lohit-deva
Status: install ok installed
Priority: optional
Section: fonts
Installed-Size: 193
Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
Architecture: all
Multi-Arch: foreign
Version: 2.95.4-4
Replaces: ttf-devanagari-fonts (<= 1:0.5.12)
Breaks: ttf-devanagari-fonts (<= 1:0.5.12)
Conffiles:
/etc/fonts/conf.avail/59-lohit-devanagari.conf 26750c27821421cef15176c5d6eb490e
/etc/fonts/conf.avail/66-lohit-devanagari.conf dfac9388b18ece1d53fed86785070f55
Description: Lohit TrueType font for Devanagari script
This package provides Lohit TrueType font for Devanagari script which
is used for writing Hindi, Kashmiri, Konkani, Marathi, Maithili, Nepali,
Sanskrit, and Sindhi languages.
Original-Maintainer: Debian Fonts Task Force <debian-fonts@lists.debian.org>
Homepage: https://pagure.io/lohit
` },
{ Name: "zstd", Version: "1.4.8+dfsg-3build1", Input: `
Package: zstd
Status: install ok installed
Priority: optional
Section: utils
Installed-Size: 1655
Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
Architecture: amd64
Source: libzstd
Version: 1.4.8+dfsg-3build1
Depends: libc6 (>= 2.34), libgcc-s1 (>= 3.3.1), liblz4-1 (>= 0.0~r127), liblzma5 (>= 5.1.1alpha+20120614), libstdc++6 (>= 12), zlib1g (>= 1:1.1.4)
Description: fast lossless compression algorithm -- CLI tool
Zstd, short for Zstandard, is a fast lossless compression algorithm, targeting
real-time compression scenarios at zlib-level compression ratio.
.
This package contains the CLI program implementing zstd.
Homepage: https://github.com/facebook/zstd
Original-Maintainer: Debian Med Packaging Team <debian-med-packaging@lists.alioth.debian.org>
` },
},
PackageTypeDnf: []struct{ Name string; Version string; Input string }{
{ Name: "man-pages-zh-CN", Version: "1.6.3.6-9.fc40", Input: `
man-pages-zh-CN.noarch 1.6.3.6-9.fc40 @fedora` },
{ Name: "memstrack", Version: "0.2.5-4.fc40", Input: `
memstrack.x86_64 0.2.5-4.fc40 @fedora` },
{ Name: "mercurial", Version: "6.7.4-3.fc40", Input: `
mercurial.x86_64 6.7.4-3.fc40 @updates` },
{ Name: "mesa-dri-drivers", Version: "24.1.7-1.fc40", Input: `
mesa-dri-drivers.x86_64 24.1.7-1.fc40 @updates` },
{ Name: "mesa-filesystem", Version: "24.1.7-1.fc40", Input: `
mesa-filesystem.x86_64 24.1.7-1.fc40 @updates` },
{ Name: "mesa-libEGL", Version: "24.1.7-1.fc40", Input: `
mesa-libEGL.x86_64 24.1.7-1.fc40 @updates` },
{ Name: "mesa-libGL", Version: "24.1.7-1.fc40", Input: `
mesa-libGL.x86_64 24.1.7-1.fc40 @updates` },
{ Name: "mesa-libgbm", Version: "24.1.7-1.fc40", Input: `
mesa-libgbm.x86_64 24.1.7-1.fc40 @updates` },
{ Name: "mesa-libglapi", Version: "24.1.7-1.fc40", Input: `
mesa-libglapi.x86_64 24.1.7-1.fc40 @updates` },
{ Name: "mesa-va-drivers", Version: "24.1.7-1.fc40", Input: `
mesa-va-drivers.x86_64 24.1.7-1.fc40 @updates` },
{ Name: "mkfontscale", Version: "1.2.2-6.fc40", Input: `
mkfontscale.x86_64 1.2.2-6.fc40 @fedora` },
{ Name: "moby-engine", Version: "24.0.5-4.fc40", Input: `
moby-engine.x86_64 24.0.5-4.fc40 @fedora` },
{ Name: "mpdecimal", Version: "2.5.1-9.fc40", Input: `
mpdecimal.x86_64 2.5.1-9.fc40 @2c98ee0d5281429683938d93eb2a9aa4` },
{ Name: "mpfr", Version: "4.2.1-3.fc40", Input: `
mpfr.x86_64 4.2.1-3.fc40 @2c98ee0d5281429683938d93eb2a9aa4` },
{ Name: "mpg123-libs", Version: "1.31.3-4.fc40", Input: `
mpg123-libs.x86_64 1.31.3-4.fc40 @fedora` },
{ Name: "ncurses", Version: "6.4-12.20240127.fc40", Input: `
ncurses.x86_64 6.4-12.20240127.fc40 @fedora` },
{ Name: "ncurses-base", Version: "6.4-12.20240127.fc40", Input: `
ncurses-base.noarch 6.4-12.20240127.fc40 @2c98ee0d5281429683938d93eb2a9aa4` },
{ Name: "ncurses-libs", Version: "6.4-12.20240127.fc40", Input: `
ncurses-libs.x86_64 6.4-12.20240127.fc40 @2c98ee0d5281429683938d93eb2a9aa4` },
{ Name: "net-tools", Version: "2.0-0.69.20160912git.fc40", Input: `
net-tools.x86_64 2.0-0.69.20160912git.fc40 @fedora` },
{ Name: "nettle", Version: "3.9.1-6.fc40", Input: `
nettle.x86_64 3.9.1-6.fc40 @2c98ee0d5281429683938d93eb2a9aa4` },
{ Name: "nftables", Version: "1.0.9-3.fc40", Input: `
nftables.x86_64 1:1.0.9-3.fc40 @fedora` },
{ Name: "npth", Version: "1.7-1.fc40", Input: `
npth.x86_64 1.7-1.fc40 @2c98ee0d5281429683938d93eb2a9aa4` },
{ Name: "nspr", Version: "4.35.0-28.fc40", Input: `
nspr.x86_64 4.35.0-28.fc40 @updates` },
{ Name: "nss", Version: "3.103.0-1.fc40", Input: `
nss.x86_64 3.103.0-1.fc40 @updates` },
{ Name: "nss-softokn", Version: "3.103.0-1.fc40", Input: `
nss-softokn.x86_64 3.103.0-1.fc40 @updates` },
{ Name: "nss-softokn-freebl", Version: "3.103.0-1.fc40", Input: `
nss-softokn-freebl.x86_64 3.103.0-1.fc40 @updates` },
{ Name: "nss-sysinit", Version: "3.103.0-1.fc40", Input: `
nss-sysinit.x86_64 3.103.0-1.fc40 @updates` },
{ Name: "nss-util", Version: "3.103.0-1.fc40", Input: `
nss-util.x86_64 3.103.0-1.fc40 @updates` },
{ Name: "openjpeg2", Version: "2.5.2-1.fc40", Input: `
openjpeg2.x86_64 2.5.2-1.fc40 @fedora` },
{ Name: "openldap", Version: "2.6.7-1.fc40", Input: `
openldap.x86_64 2.6.7-1.fc40 @2c98ee0d5281429683938d93eb2a9aa4` },
{ Name: "openssh", Version: "9.6p1-1.fc40.4", Input: `
openssh.x86_64 9.6p1-1.fc40.4 @updates` },
{ Name: "openssh-clients", Version: "9.6p1-1.fc40.4", Input: `
openssh-clients.x86_64 9.6p1-1.fc40.4 @updates` },
{ Name: "openssl", Version: "3.2.2-3.fc40", Input: `
openssl.x86_64 1:3.2.2-3.fc40 @updates` },
{ Name: "openssl-libs", Version: "3.2.2-3.fc40", Input: `
openssl-libs.x86_64 1:3.2.2-3.fc40 @updates` },
{ Name: "opus", Version: "1.5.1-1.fc40", Input: `
opus.x86_64 1.5.1-1.fc40 @fedora` },
{ Name: "orc", Version: "0.4.39-1.fc40", Input: `
orc.x86_64 0.4.39-1.fc40 @updates` },
},
}
for _, pt := range SupportedPackageTypes {
cmd := pt.NewReadCommand()
for _, v := range packages[pt] {
pkg := NewPackage()
pkg.Name = v.Name
assert.Nil(t, cmd.Extractor([]byte(v.Input), pkg))
slog.Info("TestCommandExtractor()", "packagetype", pt, "name", pkg.Name, "package", pkg)
assert.Equal(t, v.Name, pkg.Name)
assert.Equal(t, v.Version, pkg.Version)
assert.Equal(t, "present", pkg.State)
}
}
}
func TestReadPackage(t *testing.T) { func TestReadPackage(t *testing.T) {
decl := ` decl := `
name: vim name: vim