Repository: osbuild/osbuild-composer
Pull request #1054 (2020-10-26 12:29:13)

CGImport metadata fixes
This PR brings in some fixes for our Koji integration - more specifically, the CGImport metadata. All changes were proposed by the maintainers of Brew.

Here's a snapshot of metadata osbuild-composer sends with this patch applied:

```json
{
  "metadata_version": 0,
  "build": {
    "build_id": 1,
    "task_id": 1,
    "name": "name",
    "version": "version",
    "release": "release",
    "source": "",
    "start_time": 1603702833,
    "end_time": 1603703024,
    "extra": {
      "typeinfo": {
        "image": {}
      }
    }
  },
  "buildroots": [
    {
      "id": 1,
      "host": {
        "os": "Fedora release 32 (Thirty Two)",
        "arch": "x86_64"
      },
      "content_generator": {
        "name": "osbuild",
        "version": "1"
      },
      "container": {
        "type": "none",
        "arch": "x86_64"
      },
      "tools": [],
      "components": [
        {
          "type": "rpm",
          "name": "libgcc",
          "version": "10.0.1",
          "release": "0.11.fc32",
          "arch": "x86_64",
          "sigmd5": "84fc907a5047aeebaf8da1642925a417",
          "signature": null
        }
      ],
      "output": [
        {
          "buildroot_id": 1,
          "filename": "name-version-release.x86_64.qcow2",
          "filesize": 514392064,
          "arch": "x86_64",
          "checksum_type": "md5",
          "checksum": "84a28613e512005b9b31271dcc485beb",
          "type": "image",
          "components": [
            {
              "type": "rpm",
              "name": "python3-pyserial",
              "version": "3.4",
              "release": "7.fc32",
              "arch": "noarch",
              "sigmd5": "378cb32f9f850b275ac4e04d21e8144b",
              "signature": null
            }
          ],
          "extra": {
            "image": {
              "arch": "noarch"
            }
          }
        }
      ]
    }
  ]
}
```

ondrejbudai: Pushed a new version that also adds the signatures to RPMs.
ondrejbudai: > Very nice! Thanks for going through all this. Only very minor nitpicks. We don't yet know the osbuild version, should we leave that at `0` with a TODO for now?

Sure, will do it.
ondrejbudai: I force-pushed a new version. All commits are now giving credits to Lubomír and Tomáš from the Brew team. The osbuild version was changed o `"0"` with a TODO added. I don't see any action items so I think this is ready to be merged.

Patch:
diff --git a/cmd/osbuild-worker/main.go b/cmd/osbuild-worker/main.go
index 9aaf4287e..0bd0f6305 100644
--- a/cmd/osbuild-worker/main.go
+++ b/cmd/osbuild-worker/main.go
@@ -19,6 +19,7 @@ import (
 	"github.com/google/uuid"
 
 	"github.com/osbuild/osbuild-composer/internal/common"
+	"github.com/osbuild/osbuild-composer/internal/distro"
 	"github.com/osbuild/osbuild-composer/internal/osbuild"
 	"github.com/osbuild/osbuild-composer/internal/target"
 	"github.com/osbuild/osbuild-composer/internal/upload/awsupload"
@@ -73,6 +74,15 @@ func (e *TargetsError) Error() string {
 	return errString
 }
 
+func packageMetadataToSignature(pkg osbuild.RPMPackageMetadata) *string {
+	if pkg.SigGPG != "" {
+		return &pkg.SigGPG
+	} else if pkg.SigPGP != "" {
+		return &pkg.SigPGP
+	}
+	return nil
+}
+
 func osbuildStagesToRPMs(stages []osbuild.StageResult) []koji.RPM {
 	rpms := make([]koji.RPM, 0)
 	for _, stage := range stages {
@@ -80,13 +90,14 @@ func osbuildStagesToRPMs(stages []osbuild.StageResult) []koji.RPM {
 		case *osbuild.RPMStageMetadata:
 			for _, pkg := range metadata.Packages {
 				rpms = append(rpms, koji.RPM{
-					Type:    "rpm",
-					Name:    pkg.Name,
-					Epoch:   pkg.Epoch,
-					Version: pkg.Version,
-					Release: pkg.Release,
-					Arch:    pkg.Arch,
-					Sigmd5:  pkg.SigMD5,
+					Type:      "rpm",
+					Name:      pkg.Name,
+					Epoch:     pkg.Epoch,
+					Version:   pkg.Version,
+					Release:   pkg.Release,
+					Arch:      pkg.Arch,
+					Sigmd5:    pkg.SigMD5,
+					Signature: packageMetadataToSignature(pkg),
 				})
 			}
 		default:
@@ -230,7 +241,13 @@ func RunJob(job worker.Job, store string, kojiServers map[string]koji.GSSAPICred
 				continue
 			}
 
-			hash, filesize, err := k.Upload(f, options.UploadDirectory, options.Filename)
+			hash, filesize, err := k.Upload(f, options.UploadDirectory, options.KojiFilename)
+			if err != nil {
+				r = append(r, err)
+				continue
+			}
+
+			hostOS, err := distro.GetRedHatRelease()
 			if err != nil {
 				r = append(r, err)
 				continue
@@ -249,16 +266,16 @@ func RunJob(job worker.Job, store string, kojiServers map[string]koji.GSSAPICred
 				{
 					ID: 1,
 					Host: koji.Host{
-						Os:   "RHEL8",
-						Arch: "noarch",
+						Os:   hostOS,
+						Arch: common.CurrentArch(),
 					},
 					ContentGenerator: koji.ContentGenerator{
 						Name:    "osbuild",
-						Version: "1",
+						Version: "0", // TODO: put the correct version here
 					},
 					Container: koji.Container{
-						Type: "nspawn",
-						Arch: "noarch",
+						Type: "none",
+						Arch: common.CurrentArch(),
 					},
 					Tools: []koji.Tool{},
 					RPMs:  osbuildStagesToRPMs(result.Build.Stages),
@@ -267,9 +284,9 @@ func RunJob(job worker.Job, store string, kojiServers map[string]koji.GSSAPICred
 			output := []koji.Image{
 				{
 					BuildRootID:  1,
-					Filename:     options.Filename,
+					Filename:     options.KojiFilename,
 					FileSize:     uint64(filesize),
-					Arch:         "noarch",
+					Arch:         common.CurrentArch(),
 					ChecksumType: "md5",
 					MD5:          hash,
 					Type:         "image",
diff --git a/internal/distro/distro.go b/internal/distro/distro.go
index d5ec11b13..81a185e34 100644
--- a/internal/distro/distro.go
+++ b/internal/distro/distro.go
@@ -6,6 +6,7 @@ import (
 	"errors"
 	"fmt"
 	"io"
+	"io/ioutil"
 	"os"
 	"sort"
 	"strings"
@@ -193,6 +194,20 @@ func GetHostDistroName() (string, bool, error) {
 	return name, beta, nil
 }
 
+// GetRedHatRelease returns the content of /etc/redhat-release
+// without the trailing new-line.
+func GetRedHatRelease() (string, error) {
+	raw, err := ioutil.ReadFile("/etc/redhat-release")
+	if err != nil {
+		return "", fmt.Errorf("cannot read /etc/redhat-release: %v", err)
+	}
+
+	//Remove the trailing new-line.
+	redHatRelease := strings.TrimSpace(string(raw))
+
+	return redHatRelease, nil
+}
+
 func readOSRelease(r io.Reader) (map[string]string, error) {
 	osrelease := make(map[string]string)
 	scanner := bufio.NewScanner(r)
diff --git a/internal/kojiapi/server.go b/internal/kojiapi/server.go
index ec3b927d6..8666c3672 100644
--- a/internal/kojiapi/server.go
+++ b/internal/kojiapi/server.go
@@ -8,9 +8,11 @@ import (
 	"log"
 	"net/http"
 	"net/url"
+	"strings"
 
 	"github.com/google/uuid"
 	"github.com/labstack/echo/v4"
+
 	"github.com/osbuild/osbuild-composer/internal/blueprint"
 	"github.com/osbuild/osbuild-composer/internal/common"
 	"github.com/osbuild/osbuild-composer/internal/distro"
@@ -85,9 +87,10 @@ func (h *apiHandlers) PostCompose(ctx echo.Context) error {
 	}
 
 	type imageRequest struct {
-		manifest distro.Manifest
-		arch     string
-		filename string
+		manifest     distro.Manifest
+		arch         string
+		filename     string
+		kojiFilename string
 	}
 	imageRequests := make([]imageRequest, len(request.ImageRequests))
 
@@ -131,6 +134,17 @@ func (h *apiHandlers) PostCompose(ctx echo.Context) error {
 		imageRequests[i].manifest = manifest
 		imageRequests[i].arch = arch.Name()
 		imageRequests[i].filename = imageType.Filename()
+
+		filename := fmt.Sprintf(
+			"%s-%s-%s.%s%s",
+			request.Name,
+			request.Version,
+			request.Release,
+			ir.Architecture,
+			splitExtension(imageType.Filename()),
+		)
+
+		imageRequests[i].kojiFilename = filename
 	}
 
 	var ir imageRequest
@@ -176,6 +190,7 @@ func (h *apiHandlers) PostCompose(ctx echo.Context) error {
 			Filename:        ir.filename,
 			UploadDirectory: "osbuild-composer-koji-" + uuid.New().String(),
 			Server:          request.Koji.Server,
+			KojiFilename:    ir.kojiFilename,
 		}),
 	})
 	if err != nil {
@@ -189,6 +204,25 @@ func (h *apiHandlers) PostCompose(ctx echo.Context) error {
 	})
 }
 
+// splitExtension returns the extension of the given file. If there's
+// a multipart extension (e.g. file.tar.gz), it returns all parts (e.g.
+// .tar.gz). If there's no extension in the input, it returns an empty
+// string. If the filename starts with dot, the part before the second dot
+// is not considered as an extension.
+func splitExtension(filename string) string {
+	filenameParts := strings.Split(filename, ".")
+
+	if len(filenameParts) > 0 && filenameParts[0] == "" {
+		filenameParts = filenameParts[1:]
+	}
+
+	if len(filenameParts) <= 1 {
+		return ""
+	}
+
+	return "." + strings.Join(filenameParts[1:], ".")
+}
+
 func composeStateToStatus(state common.ComposeState) string {
 	switch state {
 	case common.CFailed:
diff --git a/internal/osbuild/rpm_stage.go b/internal/osbuild/rpm_stage.go
index b4979cf4d..0ed0a70af 100644
--- a/internal/osbuild/rpm_stage.go
+++ b/internal/osbuild/rpm_stage.go
@@ -42,6 +42,8 @@ type RPMPackageMetadata struct {
 	Epoch   *string `json:"epoch"`
 	Arch    string  `json:"arch"`
 	SigMD5  string  `json:"sigmd5"`
+	SigPGP  string  `json:"sigpgp"`
+	SigGPG  string  `json:"siggpg"`
 }
 
 func (RPMStageMetadata) isStageMetadata() {}
diff --git a/internal/target/koji_target.go b/internal/target/koji_target.go
index 6b059e3ab..de93f0d66 100644
--- a/internal/target/koji_target.go
+++ b/internal/target/koji_target.go
@@ -10,6 +10,7 @@ type KojiTargetOptions struct {
 	Filename        string `json:"filename"`
 	UploadDirectory string `json:"upload_directory"`
 	Server          string `json:"server"`
+	KojiFilename    string `json:"koji_filename"`
 }
 
 func (KojiTargetOptions) isTargetOptions() {}
diff --git a/internal/upload/koji/koji.go b/internal/upload/koji/koji.go
index 7584eedc3..f2256571f 100644
--- a/internal/upload/koji/koji.go
+++ b/internal/upload/koji/koji.go
@@ -23,8 +23,12 @@ type Koji struct {
 	transport http.RoundTripper
 }
 
+type TypeInfo struct {
+	Image struct{} `json:"image"`
+}
+
 type ImageBuildExtra struct {
-	Image interface{} `json:"image"` // No extra info tracked at build level.
+	TypeInfo TypeInfo `json:"typeinfo"`
 }
 
 type ImageBuild struct {
diff --git a/osbuild b/osbuild
index 7f249afe0..afafd4639 160000
--- a/osbuild
+++ b/osbuild
@@ -1,1 +1,1 @@
-Subproject commit 7f249afe07d6ae0cda08fbf439359ec99e78dd37
+Subproject commit afafd46393e1c106e79c2412dbb5c4e45a05eda8
diff --git a/osbuild-composer.spec b/osbuild-composer.spec
index 1e1e01e27..1668efd1f 100644
--- a/osbuild-composer.spec
+++ b/osbuild-composer.spec
@@ -56,8 +56,8 @@ BuildRequires:  golang(github.com/vmware/govmomi)
 
 Requires: %{name}-worker = %{version}-%{release}
 Requires: systemd
-Requires: osbuild >= 18
-Requires: osbuild-ostree >= 18
+Requires: osbuild >= 23
+Requires: osbuild-ostree >= 23
 Requires: qemu-img
 
 Provides: weldr


Test patch:
diff --git a/cmd/osbuild-worker/main_test.go b/cmd/osbuild-worker/main_test.go
index 23d6cd394..de21571f7 100644
--- a/cmd/osbuild-worker/main_test.go
+++ b/cmd/osbuild-worker/main_test.go
@@ -43,7 +43,9 @@ func Test_osbuildStagesToRPMs(t *testing.T) {
           "release": "7.fc32",
           "epoch": null,
           "arch": "noarch",
-          "sigmd5": "378cb32f9f850b275ac4e04d21e8144b"
+          "sigmd5": "378cb32f9f850b275ac4e04d21e8144b",
+          "sigpgp": "89023304000108001d162104963a2beb02009608fe67ea4249fd77499570ff3105025f5a272b000a091049fd77499570ff31ccdb0ffe38b95a55ebf3c021526b3cd4f2358c7e23f7767d1f5ce4b7cccef7b33653c6a96a23022313a818fbaf7abeb41837910f0d3ac15664e02838d5939d38ff459aa0076e248728a032d3ae09ddfaec955f941601081a2e3f9bbd49586fd65c1bc1b31685aeb0405687d1791471eab7359ccf00d5584ddef680e99ebc8a4846316391b9baa68ac8ed8ad696ee16fd625d847f8edd92517df3ea6920a46b77b4f119715a0f619f38835d25e0bd0eb5cfad08cd9c796eace6a2b28f4d3dee552e6068255d9748dc2a1906c951e0ba8aed9922ab24e1f659413a06083f8a0bfea56cfff14bddef23bced449f36bcd369da72f90ddf0512e7b0801ba5a0c8eaa8eb0582c630815e992192042cfb0a7c7239f76219197c2fdf18b6553260c105280806d4f037d7b04bdf3da9fd7e9a207db5c71f7e548f4288928f047c989c4cb9cbb8088eec7bd2fa5c252e693f51a3cfc660f666af6a255a5ca0fd2216d5ccd66cbd9c11afa61067d7f615ec8d0dc0c879b5fe633d8c9443f97285da597e4da8a3993af36f0be06acfa9b8058ec70bbc78b876e4c6c5d2108fb05c15a74ba48a3d7ded697cbc1748c228d77d1e0794a41fd5240fa67c3ed745fe47555a47c3d6163d8ce95fd6c2d0d6fa48f8e5b411e571e442109b1cb200d9a8117ee08bfe645f96aca34f7b7559622bbab75143dcad59f126ae0d319e6668ebba417e725638c4febf2e",
+          "siggpg": "883f0305005f2310139ec3e4c0f7e257e611023e11009f639c5fe64abaa76224dab3a9f70c2714a84c63bd009d1cc184fb4b428dfcd7c3556f4a5f860cc0187740"
         },
         {
           "name": "libgcc",
@@ -51,7 +53,19 @@ func Test_osbuildStagesToRPMs(t *testing.T) {
           "release": "0.11.fc32",
           "epoch": null,
           "arch": "x86_64",
-          "sigmd5": "84fc907a5047aeebaf8da1642925a417"
+          "sigmd5": "84fc907a5047aeebaf8da1642925a417",
+          "sigpgp": "89023304000108001d162104963a2beb02009608fe67ea4249fd77499570ff3105025f5a272b000a091049fd77499570ff31ccdb0ffe38b95a55ebf3c021526b3cd4f2358c7e23f7767d1f5ce4b7cccef7b33653c6a96a23022313a818fbaf7abeb41837910f0d3ac15664e02838d5939d38ff459aa0076e248728a032d3ae09ddfaec955f941601081a2e3f9bbd49586fd65c1bc1b31685aeb0405687d1791471eab7359ccf00d5584ddef680e99ebc8a4846316391b9baa68ac8ed8ad696ee16fd625d847f8edd92517df3ea6920a46b77b4f119715a0f619f38835d25e0bd0eb5cfad08cd9c796eace6a2b28f4d3dee552e6068255d9748dc2a1906c951e0ba8aed9922ab24e1f659413a06083f8a0bfea56cfff14bddef23bced449f36bcd369da72f90ddf0512e7b0801ba5a0c8eaa8eb0582c630815e992192042cfb0a7c7239f76219197c2fdf18b6553260c105280806d4f037d7b04bdf3da9fd7e9a207db5c71f7e548f4288928f047c989c4cb9cbb8088eec7bd2fa5c252e693f51a3cfc660f666af6a255a5ca0fd2216d5ccd66cbd9c11afa61067d7f615ec8d0dc0c879b5fe633d8c9443f97285da597e4da8a3993af36f0be06acfa9b8058ec70bbc78b876e4c6c5d2108fb05c15a74ba48a3d7ded697cbc1748c228d77d1e0794a41fd5240fa67c3ed745fe47555a47c3d6163d8ce95fd6c2d0d6fa48f8e5b411e571e442109b1cb200d9a8117ee08bfe645f96aca34f7b7559622bbab75143dcad59f126ae0d319e6668ebba417e725638c4febf2e",
+          "siggpg": null
+        },
+        {
+          "name": "libgcc-madeup",
+          "version": "10.0.1",
+          "release": "0.11.fc32",
+          "epoch": null,
+          "arch": "x86_64",
+          "sigmd5": "84fc907a5047aeebaf8da1642925a418",
+          "sigpgp": null,
+          "siggpg": null
         }
       ]
     }
@@ -64,8 +78,9 @@ func Test_osbuildStagesToRPMs(t *testing.T) {
 
 	rpms := osbuildStagesToRPMs(stageResults)
 
-	require.Len(t, rpms, 2)
+	require.Len(t, rpms, 3)
 
+	signature1 := "89023304000108001d162104963a2beb02009608fe67ea4249fd77499570ff3105025f5a272b000a091049fd77499570ff31ccdb0ffe38b95a55ebf3c021526b3cd4f2358c7e23f7767d1f5ce4b7cccef7b33653c6a96a23022313a818fbaf7abeb41837910f0d3ac15664e02838d5939d38ff459aa0076e248728a032d3ae09ddfaec955f941601081a2e3f9bbd49586fd65c1bc1b31685aeb0405687d1791471eab7359ccf00d5584ddef680e99ebc8a4846316391b9baa68ac8ed8ad696ee16fd625d847f8edd92517df3ea6920a46b77b4f119715a0f619f38835d25e0bd0eb5cfad08cd9c796eace6a2b28f4d3dee552e6068255d9748dc2a1906c951e0ba8aed9922ab24e1f659413a06083f8a0bfea56cfff14bddef23bced449f36bcd369da72f90ddf0512e7b0801ba5a0c8eaa8eb0582c630815e992192042cfb0a7c7239f76219197c2fdf18b6553260c105280806d4f037d7b04bdf3da9fd7e9a207db5c71f7e548f4288928f047c989c4cb9cbb8088eec7bd2fa5c252e693f51a3cfc660f666af6a255a5ca0fd2216d5ccd66cbd9c11afa61067d7f615ec8d0dc0c879b5fe633d8c9443f97285da597e4da8a3993af36f0be06acfa9b8058ec70bbc78b876e4c6c5d2108fb05c15a74ba48a3d7ded697cbc1748c228d77d1e0794a41fd5240fa67c3ed745fe47555a47c3d6163d8ce95fd6c2d0d6fa48f8e5b411e571e442109b1cb200d9a8117ee08bfe645f96aca34f7b7559622bbab75143dcad59f126ae0d319e6668ebba417e725638c4febf2e"
 	require.Equal(t, koji.RPM{
 		Type:      "rpm",
 		Name:      "libgcc",
@@ -74,6 +89,13 @@ func Test_osbuildStagesToRPMs(t *testing.T) {
 		Epoch:     nil,
 		Arch:      "x86_64",
 		Sigmd5:    "84fc907a5047aeebaf8da1642925a417",
-		Signature: nil,
+		Signature: &signature1,
 	}, rpms[1])
+
+	// GPG has a priority over PGP
+	signature0 := "883f0305005f2310139ec3e4c0f7e257e611023e11009f639c5fe64abaa76224dab3a9f70c2714a84c63bd009d1cc184fb4b428dfcd7c3556f4a5f860cc0187740"
+	require.Equal(t, signature0, *rpms[0].Signature)
+
+	// if neither GPG nor PGP is set, the signature is nil
+	require.Nil(t, rpms[2].Signature)
 }
diff --git a/internal/kojiapi/server_internal_test.go b/internal/kojiapi/server_internal_test.go
new file mode 100644
index 000000000..1dc5f5aa7
--- /dev/null
+++ b/internal/kojiapi/server_internal_test.go
@@ -0,0 +1,24 @@
+package kojiapi
+
+import (
+	"github.com/stretchr/testify/require"
+	"testing"
+)
+
+func TestSplitExtension(t *testing.T) {
+	tests := []struct {
+		filename  string
+		extension string
+	}{
+		{filename: "image.qcow2", extension: ".qcow2"},
+		{filename: "image.tar.gz", extension: ".tar.gz"},
+		{filename: "", extension: ""},
+		{filename: ".htaccess", extension: ""},
+		{filename: ".weirdfile.txt", extension: ".txt"},
+	}
+	for _, tt := range tests {
+		t.Run(tt.filename, func(t *testing.T) {
+			require.Equal(t, tt.extension, splitExtension(tt.filename))
+		})
+	}
+}
diff --git a/internal/osbuild/result_test.go b/internal/osbuild/result_test.go
index 98fed411b..b64da6e3c 100644
--- a/internal/osbuild/result_test.go
+++ b/internal/osbuild/result_test.go
@@ -8,7 +8,7 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-func TestUnmarshall(t *testing.T) {
+func TestUnmarshal(t *testing.T) {
 	resultRaw := `{
 		"success": true,
 		"build": {
@@ -27,7 +27,9 @@ func TestUnmarshall(t *testing.T) {
 					"release": "0.11.fc32",
 					"epoch": null,
 					"arch": "x86_64",
-					"sigmd5": "84fc907a5047aeebaf8da1642925a417"
+					"sigmd5": "84fc907a5047aeebaf8da1642925a417",
+					"sigpgp": null,
+					"siggpg": "883f0305005f2310139ec3e4c0f7e257e611023e11009f639c5fe64abaa76224dab3a9f70c2714a84c63bd009d1cc184fb4b428dfcd7c3556f4a5f860cc0187740"
 				  },
 				  {
 					"name": "whois-nls",
@@ -35,7 +37,9 @@ func TestUnmarshall(t *testing.T) {
 					"release": "1.fc32",
 					"epoch": null,
 					"arch": "noarch",
-					"sigmd5": "f868cd02046630c8ce3a9c48820e2437"
+					"sigmd5": "f868cd02046630c8ce3a9c48820e2437",
+					"sigpgp": "89023304000108001d162104963a2beb02009608fe67ea4249fd77499570ff3105025f5a272b000a091049fd77499570ff31ccdb0ffe38b95a55ebf3c021526b3cd4f2358c7e23f7767d1f5ce4b7cccef7b33653c6a96a23022313a818fbaf7abeb41837910f0d3ac15664e02838d5939d38ff459aa0076e248728a032d3ae09ddfaec955f941601081a2e3f9bbd49586fd65c1bc1b31685aeb0405687d1791471eab7359ccf00d5584ddef680e99ebc8a4846316391b9baa68ac8ed8ad696ee16fd625d847f8edd92517df3ea6920a46b77b4f119715a0f619f38835d25e0bd0eb5cfad08cd9c796eace6a2b28f4d3dee552e6068255d9748dc2a1906c951e0ba8aed9922ab24e1f659413a06083f8a0bfea56cfff14bddef23bced449f36bcd369da72f90ddf0512e7b0801ba5a0c8eaa8eb0582c630815e992192042cfb0a7c7239f76219197c2fdf18b6553260c105280806d4f037d7b04bdf3da9fd7e9a207db5c71f7e548f4288928f047c989c4cb9cbb8088eec7bd2fa5c252e693f51a3cfc660f666af6a255a5ca0fd2216d5ccd66cbd9c11afa61067d7f615ec8d0dc0c879b5fe633d8c9443f97285da597e4da8a3993af36f0be06acfa9b8058ec70bbc78b876e4c6c5d2108fb05c15a74ba48a3d7ded697cbc1748c228d77d1e0794a41fd5240fa67c3ed745fe47555a47c3d6163d8ce95fd6c2d0d6fa48f8e5b411e571e442109b1cb200d9a8117ee08bfe645f96aca34f7b7559622bbab75143dcad59f126ae0d319e6668ebba417e725638c4febf2e",
+					"siggpg": null
 				  }
 				]
 			  }
@@ -58,6 +62,12 @@ func TestUnmarshall(t *testing.T) {
 	assert.Equal(t, package1.Release, "0.11.fc32")
 	assert.Equal(t, package1.Arch, "x86_64")
 	assert.Equal(t, package1.SigMD5, "84fc907a5047aeebaf8da1642925a417")
+	assert.Empty(t, package1.SigPGP)
+	assert.Equal(t, package1.SigGPG, "883f0305005f2310139ec3e4c0f7e257e611023e11009f639c5fe64abaa76224dab3a9f70c2714a84c63bd009d1cc184fb4b428dfcd7c3556f4a5f860cc0187740")
+
+	package2 := metadata.Packages[1]
+	assert.Equal(t, package2.SigPGP, "89023304000108001d162104963a2beb02009608fe67ea4249fd77499570ff3105025f5a272b000a091049fd77499570ff31ccdb0ffe38b95a55ebf3c021526b3cd4f2358c7e23f7767d1f5ce4b7cccef7b33653c6a96a23022313a818fbaf7abeb41837910f0d3ac15664e02838d5939d38ff459aa0076e248728a032d3ae09ddfaec955f941601081a2e3f9bbd49586fd65c1bc1b31685aeb0405687d1791471eab7359ccf00d5584ddef680e99ebc8a4846316391b9baa68ac8ed8ad696ee16fd625d847f8edd92517df3ea6920a46b77b4f119715a0f619f38835d25e0bd0eb5cfad08cd9c796eace6a2b28f4d3dee552e6068255d9748dc2a1906c951e0ba8aed9922ab24e1f659413a06083f8a0bfea56cfff14bddef23bced449f36bcd369da72f90ddf0512e7b0801ba5a0c8eaa8eb0582c630815e992192042cfb0a7c7239f76219197c2fdf18b6553260c105280806d4f037d7b04bdf3da9fd7e9a207db5c71f7e548f4288928f047c989c4cb9cbb8088eec7bd2fa5c252e693f51a3cfc660f666af6a255a5ca0fd2216d5ccd66cbd9c11afa61067d7f615ec8d0dc0c879b5fe633d8c9443f97285da597e4da8a3993af36f0be06acfa9b8058ec70bbc78b876e4c6c5d2108fb05c15a74ba48a3d7ded697cbc1748c228d77d1e0794a41fd5240fa67c3ed745fe47555a47c3d6163d8ce95fd6c2d0d6fa48f8e5b411e571e442109b1cb200d9a8117ee08bfe645f96aca34f7b7559622bbab75143dcad59f126ae0d319e6668ebba417e725638c4febf2e")
+	assert.Empty(t, package2.SigGPG)
 }
 
 func TestWriteFull(t *testing.T) {
diff --git a/test/data/ansible/check_ostree.yaml b/test/data/ansible/check_ostree.yaml
index 5d984dd2c..274739329 100644
--- a/test/data/ansible/check_ostree.yaml
+++ b/test/data/ansible/check_ostree.yaml
@@ -294,6 +294,226 @@
           set_fact:
             failed_counter: "{{ failed_counter | int + 1 }}"
 
+    # case: check dnf package and it should not be installed
+    # https://github.com/osbuild/osbuild-composer/blob/master/internal/distro/rhel8/distro.go#L642
+    - name: dnf should not be installed
+      block:
+        - name: dnf should not be installed
+          shell: rpm -qa | grep dnf || echo -n PASS
+          register: result_dnf
+
+        - assert:
+            that:
+              - result_dnf.stdout == "PASS"
+            fail_msg: "dnf is installed"
+            success_msg: "No dnf installed"
+      always:
+        - set_fact:
+            total_counter: "{{ total_counter | int + 1 }}"
+      rescue:
+        - name: failed count + 1
+          set_fact:
+            failed_counter: "{{ failed_counter | int + 1 }}"
+
+    # case: check installed greenboot packages
+    # https://github.com/osbuild/osbuild-composer/blob/master/internal/distro/rhel8/distro.go#L634
+    - name: greenboot and it's related packages should be installed
+      block:
+        - name: greenboot and it's related packages should be installed
+          shell: rpm -qa | grep greenboot
+          register: result_greenboot_packages
+
+        - assert:
+            that:
+              - "'greenboot-0' in result_greenboot_packages.stdout"
+              - "'greenboot-grub2' in result_greenboot_packages.stdout"
+              - "'greenboot-rpm-ostree-grub2' in result_greenboot_packages.stdout"
+              - "'greenboot-reboot' in result_greenboot_packages.stdout"
+              - "'greenboot-status' in result_greenboot_packages.stdout"
+            fail_msg: "Some of greenboot and its related packages are not installed"
+            success_msg: "All greenboot and its related packages are installed"
+      always:
+        - set_fact:
+            total_counter: "{{ total_counter | int + 1 }}"
+      rescue:
+        - name: failed count + 1
+          set_fact:
+            failed_counter: "{{ failed_counter | int + 1 }}"
+
+    # case: check greenboot* services
+    - name: a list of greenboot* service should be enabled
+      block:
+        - name: a list of greenboot* service should be enabled
+          command: systemctl is-enabled greenboot-grub2-set-counter greenboot-grub2-set-success greenboot-healthcheck greenboot-rpm-ostree-grub2-check-fallback greenboot-status greenboot-task-runner redboot-auto-reboot redboot-task-runner
+          register: result_greenboot_service
+
+        - assert:
+            that:
+              - result_greenboot_service.stdout == 'enabled\nenabled\nenabled\nenabled\nenabled\nenabled\nenabled\nenabled'
+            fail_msg: "Some of greenboot* services are not enabled"
+            success_msg: "All greenboot* services are enabled"
+      always:
+        - set_fact:
+            total_counter: "{{ total_counter | int + 1 }}"
+      rescue:
+        - name: failed count + 1
+          set_fact:
+            failed_counter: "{{ failed_counter | int + 1 }}"
+
+    # case: check greenboot* services log
+    - name: all greenboot* service should run without error
+      block:
+        - name: all greenboot* service should run without error
+          command: journalctl -b -0 -u boot-complete.target -u greenboot -u greenboot-healthcheck -u greenboot-rpm-ostree-grub2-check-fallback -u greenboot-grub2-set-counter -u greenboot-grub2-set-success -u greenboot-status -u redboot -u redboot-auto-reboot -u redboot.target
+          register: result_greenboot_log
+
+        - assert:
+            that:
+              - "'Script \\'00_required_scripts_start.sh\\' SUCCESS' in result_greenboot_log.stdout"
+              - "'Script \\'00_wanted_scripts_start.sh\\' SUCCESS' in result_greenboot_log.stdout"
+              - "'greenboot Health Checks Runner' in result_greenboot_log.stdout"
+              - "'Reached target Boot Completion Check' in result_greenboot_log.stdout"
+              - "'Mark boot as successful in grubenv' in result_greenboot_log.stdout"
+              - "'Boot Status is GREEN - Health Check SUCCESS' in result_greenboot_log.stdout"
+              - "'greenboot MotD Generator' in result_greenboot_log.stdout"
+            fail_msg: "Some errors happened in service boot"
+            success_msg: "All greenboot services booted success"
+      always:
+        - set_fact:
+            total_counter: "{{ total_counter | int + 1 }}"
+      rescue:
+        - name: failed count + 1
+          set_fact:
+            failed_counter: "{{ failed_counter | int + 1 }}"
+
+    # case: check grubenv variables
+    - name: grubenv variables should contain boot_success=1
+      block:
+        - name: grubenv variables should contain boot_success=1
+          command: grub2-editenv list
+          register: result_grubenv
+          become: yes
+
+        - assert:
+            that:
+              - "'boot_success=1' in result_grubenv.stdout"
+            fail_msg: "Not found boot_success=1"
+            success_msg: "Found boot_success=1"
+      always:
+        - set_fact:
+            total_counter: "{{ total_counter | int + 1 }}"
+      rescue:
+        - name: failed count + 1
+          set_fact:
+            failed_counter: "{{ failed_counter | int + 1 }}"
+
+    # case: check rollback function if boot error found
+    - name: install sanely failing health check unit to test red boot status behavior
+      block:
+        - name: install sanely failing health check unit to test red boot status behavior
+          command: rpm-ostree install http://file-server-virt-qe-3rd.cloud.paas.psi.redhat.com/greenboot-failing-unit-1.0-1.el8.noarch.rpm
+          become: yes
+
+        - name: reboot to deploy new ostree commit
+          reboot:
+          become: yes
+
+        - name: waits until instance is reachable
+          wait_for:
+            host: "192.168.100.50"
+            port: 22
+            search_regex: OpenSSH
+            delay: 10
+          register: result_rollback
+
+        - assert:
+            that:
+              - result_rollback is succeeded
+            fail_msg: "Rollback failed"
+            success_msg: "Rollback success"
+      always:
+        - set_fact:
+            total_counter: "{{ total_counter | int + 1 }}"
+      rescue:
+        - name: failed count + 1
+          set_fact:
+            failed_counter: "{{ failed_counter | int + 1 }}"
+
+    # case: check ostree commit after rollback
+    - name: check ostree commit after rollback
+      block:
+        - name: check ostree commit after rollback
+          command: rpm-ostree status --json
+          register: result_commit
+
+        - name: make a json result
+          set_fact:
+            deploy_commit: "{{ result_commit.stdout | from_json | json_query('deployments[0].checksum') }}"
+
+        - assert:
+            that:
+              - deploy_commit == ostree_commit
+            fail_msg: "Not rollback to last commit"
+            success_msg: "Rollback success"
+      always:
+        - set_fact:
+            total_counter: "{{ total_counter | int + 1 }}"
+      rescue:
+        - name: failed count + 1
+          set_fact:
+            failed_counter: "{{ failed_counter | int + 1 }}"
+      when: result_rollback is succeeded
+
+    # case: check greenboot* services log again
+    - name: fallback log should be found here
+      block:
+        - name: fallback log should be found here
+          command: journalctl -b -0 -u boot-complete.target -u greenboot -u greenboot-healthcheck -u greenboot-rpm-ostree-grub2-check-fallback -u greenboot-grub2-set-counter -u greenboot-grub2-set-success -u greenboot-status -u redboot -u redboot-auto-reboot -u redboot.target
+          register: result_greenboot_log
+
+        - assert:
+            that:
+              - "'FALLBACK BOOT DETECTED! Default rpm-ostree deployment has been rolled back' in result_greenboot_log.stdout"
+              - "'Script \\'00_required_scripts_start.sh\\' SUCCESS' in result_greenboot_log.stdout"
+              - "'Script \\'00_wanted_scripts_start.sh\\' SUCCESS' in result_greenboot_log.stdout"
+              - "'greenboot Health Checks Runner' in result_greenboot_log.stdout"
+              - "'Reached target Boot Completion Check' in result_greenboot_log.stdout"
+              - "'Mark boot as successful in grubenv' in result_greenboot_log.stdout"
+              - "'Boot Status is GREEN - Health Check SUCCESS' in result_greenboot_log.stdout"
+              - "'greenboot MotD Generator' in result_greenboot_log.stdout"
+            fail_msg: "Fallback log not found"
+            success_msg: "Found fallback log"
+      always:
+        - set_fact:
+            total_counter: "{{ total_counter | int + 1 }}"
+      rescue:
+        - name: failed count + 1
+          set_fact:
+            failed_counter: "{{ failed_counter | int + 1 }}"
+      when: result_rollback is succeeded
+
+    # case: check grubenv variables again
+    - name: grubenv variables should contain boot_success=1
+      block:
+        - name: grubenv variables should contain boot_success=1
+          command: grub2-editenv list
+          register: result_grubenv
+          become: yes
+
+        - assert:
+            that:
+              - "'boot_success=1' in result_grubenv.stdout"
+            fail_msg: "Not found boot_success=1"
+            success_msg: "Found boot_success=1"
+      always:
+        - set_fact:
+            total_counter: "{{ total_counter | int + 1 }}"
+      rescue:
+        - name: failed count + 1
+          set_fact:
+            failed_counter: "{{ failed_counter | int + 1 }}"
+      when: result_rollback is succeeded
+
     - assert:
         that:
           - failed_counter == "0"