osbase/oci: add package
This adds the oci package, which contains types and tools for working
with OCI images.
Change-Id: Ie2a1d82c7ac007f5d1ad47666880dbf8a8bd931d
Reviewed-on: https://review.monogon.dev/c/monogon/+/4085
Tested-by: Jenkins CI
Reviewed-by: Lorenz Brun <lorenz@monogon.tech>
diff --git a/osbase/oci/oci_test.go b/osbase/oci/oci_test.go
new file mode 100644
index 0000000..93976ed
--- /dev/null
+++ b/osbase/oci/oci_test.go
@@ -0,0 +1,96 @@
+// Copyright The Monogon Project Authors.
+// SPDX-License-Identifier: Apache-2.0
+
+package oci
+
+import (
+ "fmt"
+ "strings"
+ "testing"
+)
+
+func TestEmbeddedContent(t *testing.T) {
+ manifest := `{
+ "schemaVersion": 2,
+ "mediaType": "application/vnd.oci.image.manifest.v1+json",
+ "config": {
+ "mediaType": "application/vnd.oci.empty.v1+json",
+ "digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a",
+ "size": 2,
+ "data": "e30="
+ },
+ "layers": [
+ {
+ "digest": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "size": 0
+ },
+ {
+ "digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff80",
+ "size": 2,
+ "data": "e30="
+ }
+ ]
+}`
+ // Pass nil for blobs, which means reading can only work if it uses the
+ // embedded content.
+ image, err := NewImage([]byte(manifest), "", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ configBytes, err := image.ReadBlobVerified(&image.Manifest.Config)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if got, want := string(configBytes), "{}"; got != want {
+ t.Errorf("Got config %q, expected %q", got, want)
+ }
+ layerBytes, err := image.ReadBlobVerified(&image.Manifest.Layers[0])
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(layerBytes) != 0 {
+ t.Errorf("Got layer %q, expected to be empty", layerBytes)
+ }
+ // Layer 1 has a wrong digest.
+ _, err = image.ReadBlobVerified(&image.Manifest.Layers[1])
+ if !strings.Contains(fmt.Sprintf("%v", err), "failed verification") {
+ t.Errorf("Expected failed verification, got %v", err)
+ }
+}
+
+func TestParseDigest(t *testing.T) {
+ testCases := []struct {
+ input string
+ algorithm string
+ encoded string
+ err string
+ }{
+ {input: "", err: `invalid digest`},
+ {input: "1234", err: `invalid digest`},
+ {input: "x:y", err: `unknown digest algorithm "x"`},
+ {input: "sha256:1234", err: `invalid sha256 digest length`},
+ {input: "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b8550", err: `invalid sha256 digest length`},
+ {input: "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b85x", err: `invalid character in sha256 digest`},
+ {
+ input: "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ algorithm: "sha256",
+ encoded: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ },
+ }
+ for _, tC := range testCases {
+ algorithm, encoded, err := ParseDigest(tC.input)
+ if algorithm != tC.algorithm {
+ t.Errorf("ParseDigest(%q): algorithm = %q, expected %q", tC.input, algorithm, tC.algorithm)
+ }
+ if encoded != tC.encoded {
+ t.Errorf("ParseDigest(%q): encoded = %q, expected %q", tC.input, encoded, tC.encoded)
+ }
+ errStr := ""
+ if err != nil {
+ errStr = err.Error()
+ }
+ if errStr != tC.err {
+ t.Errorf("ParseDigest(%q): err = %q, expected %q", tC.input, errStr, tC.err)
+ }
+ }
+}