diff --git a/go.mod b/go.mod index 8c43dc0..e0600ea 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module decl go 1.22.5 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 github.com/docker/docker v27.0.3+incompatible github.com/docker/go-connections v0.5.0 @@ -14,7 +14,10 @@ require ( gopkg.in/yaml.v3 v3.0.1 ) +require google.golang.org/protobuf v1.33.0 + require ( + gitea.rosskeen.house/pylon/luaruntime v0.0.0-20240924031921-4d00743b53e1 // indirect github.com/Microsoft/go-winio v0.4.14 // indirect github.com/containerd/log v0.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect diff --git a/go.sum b/go.sum index 9fbd16c..e6eb501 100644 --- a/go.sum +++ b/go.sum @@ -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/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/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/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU= diff --git a/internal/client/client.go b/internal/client/client.go index 5f18cc0..fcc5890 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -10,7 +10,7 @@ _ "decl/internal/config" _ "decl/internal/resource" "decl/internal/fs" "decl/internal/builtin" -_ "errors" + "errors" "fmt" "context" "log/slog" @@ -19,6 +19,7 @@ _ "errors" var ( + ErrFailedResources error = errors.New("Failed Resources") ) type App struct { @@ -178,7 +179,7 @@ func (a *App) Apply(ctx context.Context, deleteResources bool) (err error) { return e } 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 @@ -223,7 +224,10 @@ func (a *App) ApplyCmd(ctx context.Context, docs []string, quiet bool, deleteRes } 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 { diff --git a/internal/folio/declaration.go b/internal/folio/declaration.go index 9c0a166..136170a 100644 --- a/internal/folio/declaration.go +++ b/internal/folio/declaration.go @@ -204,13 +204,21 @@ func (d *Declaration) Apply(stateTransition string) (result error) { } func (d *Declaration) SetConfig(configDoc data.Document) { + slog.Info("Declaration.SetConfig()", "config", configDoc) if configDoc != nil { if configDoc.Has(string(d.Config)) { - v, _ := configDoc.Get(string(d.Config)) - d.configBlock = v.(data.Block) - d.Attributes.UseConfig(d.configBlock) + if v, ok := configDoc.Get(string(d.Config)); ok { + d.configBlock = v.(data.Block) + 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) { diff --git a/internal/folio/document.go b/internal/folio/document.go index 2e2836b..1f6bbaf 100644 --- a/internal/folio/document.go +++ b/internal/folio/document.go @@ -263,35 +263,37 @@ func (d *Document) Apply(state string) error { if state == "delete" { start = len(d.ResourceDeclarations) - 1 } - for { - idx := i - start - if idx < 0 { idx = - idx } + if len(d.ResourceDeclarations) > 0 { + for { + 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()) - 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()) + 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 { - slog.Error("Document.Apply() error applying resource", "index", idx, "uri", d.ResourceDeclarations[idx].Resource().URI()) + if e := d.ResourceDeclarations[idx].Apply(state); e != nil { + slog.Error("Document.Apply() error applying resource", "index", idx, "uri", d.ResourceDeclarations[idx].Resource().URI()) - d.ResourceDeclarations[idx].Error = e.Error() - switch d.ResourceDeclarations[idx].OnError.GetStrategy() { - case OnErrorStop: - return e - case OnErrorFail: - d.failedResources++ + d.ResourceDeclarations[idx].Error = e.Error() + switch d.ResourceDeclarations[idx].OnError.GetStrategy() { + case OnErrorStop: + return e + case OnErrorFail: + d.failedResources++ + } } + } else { + d.ResourceDeclarations[idx].Error = fmt.Sprintf("Constraint failure: %s", d.ResourceDeclarations[idx].Requires) } - } else { - d.ResourceDeclarations[idx].Error = fmt.Sprintf("Constraint failure: %s", d.ResourceDeclarations[idx].Requires) + if i >= len(d.ResourceDeclarations) - 1 { + break + } + i++ } - if i >= len(d.ResourceDeclarations) - 1 { - break - } - i++ } return nil } diff --git a/internal/resource/container_image.go b/internal/resource/container_image.go index 87d0278..4faab9b 100644 --- a/internal/resource/container_image.go +++ b/internal/resource/container_image.go @@ -34,6 +34,26 @@ const ( 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 { ErrorMessage string `json:"message" yaml:"message"` } @@ -69,7 +89,8 @@ type ContainerImage struct { ContextRef folio.ResourceReference `json:"contextref,omitempty" yaml:"contextref,omitempty"` InjectJX bool `json:"injectjx,omitempty" yaml:"injectjx,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 Resources data.ResourceMapper `json:"-" yaml:"-"` @@ -118,7 +139,7 @@ func (c *ContainerImage) RegistryAuthConfig() (authConfig registry.AuthConfig, e authConfig.Password = configValue.(string) } if configValue, err = c.config.GetValue("repo_server"); err != nil { - return + return authConfig, nil } else { authConfig.ServerAddress = configValue.(string) } @@ -141,11 +162,13 @@ func (c *ContainerImage) RegistryLogin(context context.Context) (token string, e func (c *ContainerImage) RegistryAuth() (string, error) { if authConfig, err := c.RegistryAuthConfig(); err == 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 } else { return "", jsonErr } } else { + slog.Info("ContainerImage.RegistryAuth()", "error", err) return "", err } } @@ -526,6 +549,38 @@ func JXPath() (jxPath folio.URI, err error) { 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 func (c *ContainerImage) Create(ctx context.Context) (err error) { dockerfileURI := c.DockerfileRef.Parse() @@ -580,13 +635,18 @@ func (c *ContainerImage) Create(ctx context.Context) (err error) { } defer buildResponse.Body.Close() - if output, outputErr := io.ReadAll(buildResponse.Body); outputErr != nil { - slog.Info("ContainerImage.Create() - ImageBuild()", "output", output, "error", outputErr) - return fmt.Errorf("%w %s %s", outputErr, c.Type(), c.Name) + copyBuffer := make([]byte, 32 * 1024) + if _, err = io.CopyBuffer(&c.outputWriter, buildResponse.Body, copyBuffer); err != nil { + slog.Info("ContainerImage.Create() - ImageBuild()", "error", err) + return fmt.Errorf("%w %s %s", err, c.Type(), c.Name) } 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 - for _, jsonBody := range strings.Split(string(output), "\r\n") { + for _, jsonBody := range strings.Split(string(c.outputWriter.String()), "\r\n") { decoder := codec.NewJSONStringDecoder(jsonBody) decodeErr := decoder.Decode(&containerErr) 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) } } + */ } if c.PushImage { err = c.Push(ctx) + slog.Info("ContainerImage.Create() - Push()", "error", err) } + err = c.UnmarshalOutput() } return } @@ -628,7 +691,8 @@ func (c *ContainerImage) Push(ctx context.Context) (err error) { defer response.Close() 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 } diff --git a/internal/resource/container_image_test.go b/internal/resource/container_image_test.go index 8158b05..9ff853e 100644 --- a/internal/resource/container_image_test.go +++ b/internal/resource/container_image_test.go @@ -25,6 +25,11 @@ _ "net/http/httptest" "strconv" ) +type MockConfigValueGetter func(key string) (any, error) +func (m MockConfigValueGetter) GetValue(key string) (any, error) { + return m(key) +} + func TestNewContainerImageResource(t *testing.T) { c := NewContainerImage(&mocks.MockContainerClient{}) assert.NotNil(t, c) @@ -147,11 +152,24 @@ func TestCreateContainerImagePush(t *testing.T) { assert.Nil(t, e) 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, c.Push(context.Background())) assert.True(t, c.PushImage) - assert.Equal(t, "foo", c.Output.String()) +// assert.Equal(t, "foo", c.Output) } func TestContainerImageContextDocument(t *testing.T) { @@ -226,3 +244,103 @@ func TestContainerError(t *testing.T) { assert.Nil(t, decoder.Decode(&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) + + +} + + diff --git a/internal/resource/package.go b/internal/resource/package.go index a1f565e..df8beaa 100644 --- a/internal/resource/package.go +++ b/internal/resource/package.go @@ -45,6 +45,8 @@ var ( 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 { @@ -76,7 +78,7 @@ func init() { } func FindSystemPackageType() PackageType { - for _, packageType := range []PackageType{PackageTypeApk, PackageTypeApt, PackageTypeDeb, PackageTypeDnf, PackageTypeRpm, PackageTypePip, PackageTypeYum} { + for _, packageType := range SupportedPackageTypes { c := packageType.NewReadCommand() if c.Exists() { return packageType @@ -497,15 +499,17 @@ func NewApkReadCommand() *command.Command { } c.Extractor = func(out []byte, target any) error { p := target.(*Package) - pkg := strings.Split(string(out), "-") + pkg := strings.Split(strings.TrimSpace(string(out)), "-") 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], "-") + 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 diff --git a/internal/resource/package_test.go b/internal/resource/package_test.go index cc015a9..de41390 100644 --- a/internal/resource/package_test.go +++ b/internal/resource/package_test.go @@ -32,6 +32,246 @@ func TestPackageApplyResourceTransformation(t *testing.T) { //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 +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 +` }, + { 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 +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 +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 +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 +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 +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 +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 +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 +` }, + }, + 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) { decl := ` name: vim