]> git.ipfire.org Git - pakfire.git/commitdiff
oci: Add basic OCI metadata
authorMichael Tremer <michael.tremer@ipfire.org>
Sat, 8 Feb 2025 17:50:17 +0000 (17:50 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Sat, 8 Feb 2025 17:50:17 +0000 (17:50 +0000)
This patch also revers back to SHA-2 256 since the tools that I have
been using to test this image with usually don't support SHA-2 512.

Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
src/pakfire/oci.c

index b1e697102470c528e90d4a70c55279f9f4423b4b..4059bc5347f81fd19134b052124142eca0c054fd 100644 (file)
@@ -45,14 +45,13 @@ struct pakfire_oci_writer {
        // Pakfire
        struct pakfire* pakfire;
 
-       // Root
-       const char* root;
-
        // Image writer
        struct pakfire_archive_writer* image;
 
-       // Hexdigest of the layer
-       char* layer_hexdigest;
+       // JSON Objects
+       struct json_object* config;
+       struct json_object* layers;
+       struct json_object* manifests;
 };
 
 static const char* pakfire_oci_arch(struct pakfire* pakfire) {
@@ -102,12 +101,288 @@ ERROR:
        return r;
 }
 
+static int pakfire_oci_writer_write_json_blob(
+               struct pakfire_oci_writer* self, struct json_object* json, char** hexdigest, size_t* size) {
+       struct pakfire_hashes hashes = {};
+       const char* buffer = NULL;
+       char* hd = NULL;
+       char path[PATH_MAX];
+       int r;
+
+       // Serialize the JSON object
+       buffer = json_object_to_json_string_ext(json, 0);
+       if (!buffer) {
+               r = -errno;
+               goto ERROR;
+       }
+
+       // Hash the serialized JSON object
+       r = pakfire_hash_buffer(self->ctx, buffer, strlen(buffer), PAKFIRE_HASH_SHA2_256, &hashes);
+       if (r < 0)
+               goto ERROR;
+
+       // Fetch the hexdigest
+       r = pakfire_hashes_get_hex(&hashes, PAKFIRE_HASH_SHA2_256, &hd);
+       if (r < 0)
+               goto ERROR;
+
+       // Make path
+       r = pakfire_path_format(path, "blobs/sha256/%s", hd);
+       if (r < 0)
+               goto ERROR;
+
+       // Write the file
+       r = pakfire_archive_writer_create_file(self->image, path, 0644, buffer, strlen(buffer));
+       if (r < 0) {
+               ERROR(self->ctx, "Failed to write JSON blob %s: %s\n", path, strerror(-r));
+               goto ERROR;
+       }
+
+       // If requested return the hexdigest
+       if (hexdigest)
+               *hexdigest = strdup(hd);
+
+       // If requested return the size
+       if (size)
+               *size = strlen(buffer);
+
+ERROR:
+       if (hd)
+               free(hd);
+
+       return r;
+}
+
+static int pakfire_oci_writer_write_index(struct pakfire_oci_writer* self) {
+       struct json_object* o = NULL;
+       int r;
+
+       // Make a new object
+       o = pakfire_json_new_object();
+       if (!o) {
+               r = -errno;
+               goto ERROR;
+       }
+
+       // Set schema version
+       r = pakfire_json_add_int64(o, "schemaVersion", 2);
+       if (r < 0)
+               goto ERROR;
+
+       // Set media type
+       r = pakfire_json_add_string(o, "mediaType", "application/vnd.oci.image.index.v1+json");
+       if (r < 0)
+               goto ERROR;
+
+       // Add manifests
+       r = json_object_object_add(o, "manifests", json_object_get(self->manifests));
+       if (r < 0)
+               goto ERROR;
+
+       // Write the file
+       r = pakfire_archive_writer_create_file_from_json(self->image, "index.json", 0644, o);
+       if (r < 0)
+               goto ERROR;
+
+ERROR:
+       if (o)
+               json_object_put(o);
+
+       return r;
+}
+
+static int pakfire_oci_writer_write_config(struct pakfire_oci_writer* self) {
+       struct json_object* o = NULL;
+       char* hexdigest = NULL;
+       char created[64];
+       size_t size;
+       int r;
+
+       // Make a new object
+       o = pakfire_json_new_object();
+       if (!o) {
+               r = -errno;
+               goto ERROR;
+       }
+
+       // Format creation timestamp
+       r = pakfire_strftime_now(created, "%Y-%m-%dT%H:%M:%SZ");
+       if (r < 0)
+               goto ERROR;
+
+       // Add the creation timestamp
+       r = pakfire_json_add_string(o, "created", created);
+       if (r < 0)
+               goto ERROR;
+
+       // Add author
+       const char* vendor = pakfire_get_distro_vendor(self->pakfire);
+       if (vendor) {
+               r = pakfire_json_add_string(o, "author", vendor);
+               if (r < 0)
+                       goto ERROR;
+       }
+
+       // Add OS
+       r = pakfire_json_add_string(o, "os", "linux");
+       if (r < 0)
+               goto ERROR;
+
+       // Fetch OS version
+       const char* version_id = pakfire_get_distro_version_id(self->pakfire);
+
+       // Add OS version
+       if (version_id) {
+               r = pakfire_json_add_string(o, "os.version", version_id);
+               if (r < 0)
+                       goto ERROR;
+       }
+
+       // Fetch arch
+       const char* arch = pakfire_oci_arch(self->pakfire);
+
+       // Add arch
+       r = pakfire_json_add_string(o, "architecture", arch);
+       if (r < 0)
+               goto ERROR;
+
+       // Write as blob
+       r = pakfire_oci_writer_write_json_blob(self, o, &hexdigest, &size);
+       if (r < 0)
+               goto ERROR;
+
+       // Add media type
+       r = pakfire_json_add_string(self->config, "mediaType", "application/vnd.oci.image.config.v1+json");
+       if (r < 0)
+               goto ERROR;
+
+       // Add digest
+       r = pakfire_json_add_stringf(self->config, "digest", "sha256:%s", hexdigest);
+       if (r < 0)
+               goto ERROR;
+
+       // Add size
+       r = pakfire_json_add_int64(self->config, "size", size);
+       if (r < 0)
+               goto ERROR;
+
+ERROR:
+       if (hexdigest)
+               free(hexdigest);
+       if (o)
+               json_object_put(o);
+
+       return r;
+}
+
+static int pakfire_oci_writer_write_manifest(struct pakfire_oci_writer* self) {
+       struct json_object* manifest = NULL;
+       struct json_object* platform = NULL;
+       struct json_object* o = NULL;
+       char* hexdigest = NULL;
+       size_t size;
+       int r;
+
+       // Make a new object
+       o = pakfire_json_new_object();
+       if (!o) {
+               r = -errno;
+               goto ERROR;
+       }
+
+       // Set schema version
+       r = pakfire_json_add_int64(o, "schemaVersion", 2);
+       if (r < 0)
+               goto ERROR;
+
+       // Set media type
+       r = pakfire_json_add_string(o, "mediaType", "application/vnd.oci.image.manifest.v1+json");
+       if (r < 0)
+               goto ERROR;
+
+       // Add config
+       r = json_object_object_add(o, "config", json_object_get(self->config));
+       if (r < 0)
+               goto ERROR;
+
+       // Add layer to the layers array
+       r = json_object_object_add(o, "layers", json_object_get(self->layers));
+       if (r < 0)
+               goto ERROR;
+
+       // Write as blob
+       r = pakfire_oci_writer_write_json_blob(self, o, &hexdigest, &size);
+       if (r < 0)
+               goto ERROR;
+
+       // Make another new object
+       manifest = pakfire_json_new_object();
+       if (!manifest) {
+               r = -errno;
+               goto ERROR;
+       }
+
+       // Add media type
+       r = pakfire_json_add_string(manifest, "mediaType", "application/vnd.oci.image.manifest.v1+json");
+       if (r < 0)
+               goto ERROR;
+
+       // Add size
+       r = pakfire_json_add_int64(manifest, "size", size);
+       if (r < 0)
+               goto ERROR;
+
+       // Add digest
+       r = pakfire_json_add_stringf(manifest, "digest", "sha256:%s", hexdigest);
+       if (r < 0)
+               goto ERROR;
+
+       // Add platform
+       r = pakfire_json_add_object(manifest, "platform", &platform);
+       if (r < 0)
+               goto ERROR;
+
+       // Fetch architecture
+       const char* arch = pakfire_oci_arch(self->pakfire);
+
+       // Add architecture
+       r = pakfire_json_add_string(platform, "architecture", arch);
+       if (r < 0)
+               goto ERROR;
+
+       // Add OS
+       r = pakfire_json_add_string(platform, "os", "linux");
+       if (r < 0)
+               goto ERROR;
+
+       // Add the manifest to the other manifests
+       r = json_object_array_add(self->manifests, json_object_get(manifest));
+       if (r < 0) {
+               ERROR(self->ctx, "Failed to add the manifest to the metadata: %m\n");
+               r = -errno;
+               goto ERROR;
+       }
+
+ERROR:
+       if (manifest)
+               json_object_put(manifest);
+       if (o)
+               json_object_put(o);
+       if (hexdigest)
+               free(hexdigest);
+
+       return r;
+}
+
 static int pakfire_oci_writer_make_layer(struct pakfire_oci_writer* self) {
        char path[PATH_MAX] = PAKFIRE_TMP_DIR "/pakfire-oci-layer.XXXXXX";
        struct pakfire_archive_writer* layer = NULL;
        struct pakfire_hashes hashes = {};
+       struct json_object* json = NULL;
        char filename[PATH_MAX];
+       char* hexdigest = NULL;
        FILE* f = NULL;
+       size_t size;
        int r;
 
        // Fetch the pakfire root
@@ -139,26 +414,59 @@ static int pakfire_oci_writer_make_layer(struct pakfire_oci_writer* self) {
        if (r < 0)
                goto ERROR;
 
+       // Fetch the size
+       size = ftell(f);
+
        // Rewind
        r = pakfire_rewind(f);
        if (r < 0)
                goto ERROR;
 
        // Compute hashes of the file
-       r = pakfire_hash_file(self->ctx, f, PAKFIRE_HASH_SHA2_512, &hashes);
+       r = pakfire_hash_file(self->ctx, f, PAKFIRE_HASH_SHA2_256, &hashes);
        if (r < 0)
                goto ERROR;
 
        // Read the hexdigest of the file
-       r = pakfire_hashes_get_hex(&hashes, PAKFIRE_HASH_SHA2_512, &self->layer_hexdigest);
+       r = pakfire_hashes_get_hex(&hashes, PAKFIRE_HASH_SHA2_256, &hexdigest);
        if (r < 0)
                goto ERROR;
 
        // Make the filename of the layer
-       r = pakfire_path_format(filename, "blobs/sha512/%s", self->layer_hexdigest);
+       r = pakfire_path_format(filename, "blobs/sha256/%s", hexdigest);
        if (r < 0)
                goto ERROR;
 
+       // Create a new JSON object
+       json = pakfire_json_new_object();
+       if (!json) {
+               r = -errno;
+               goto ERROR;
+       }
+
+       // Add the media type
+       r = pakfire_json_add_string(json, "mediaType", "application/vnd.oci.image.layer.v1.tar+gzip");
+       if (r < 0)
+               goto ERROR;
+
+       // Add the digest
+       r = pakfire_json_add_stringf(json, "digest", "sha256:%s", hexdigest);
+       if (r < 0)
+               goto ERROR;
+
+       // Add the size
+       r = pakfire_json_add_int64(json, "size", size);
+       if (r < 0)
+               goto ERROR;
+
+       // Add the layer to the other layers
+       r = json_object_array_add(self->layers, json_object_get(json));
+       if (r < 0) {
+               ERROR(self->ctx, "Failed to add the layer to the metadata: %m\n");
+               r = -errno;
+               goto ERROR;
+       }
+
        // Rewind
        r = pakfire_rewind(f);
        if (r < 0)
@@ -174,6 +482,8 @@ static int pakfire_oci_writer_make_layer(struct pakfire_oci_writer* self) {
 ERROR:
        if (layer)
                pakfire_archive_writer_unref(layer);
+       if (json)
+               json_object_put(json);
        if (f)
                fclose(f);
 
@@ -187,6 +497,27 @@ int pakfire_oci_mkimage(struct pakfire* pakfire, FILE* f) {
        };
        int r;
 
+       // Reference to the config
+       writer.config = json_object_new_object();
+       if (!writer.config) {
+               r = -errno;
+               goto ERROR;
+       }
+
+       // To store all layers
+       writer.layers = json_object_new_array();
+       if (!writer.layers) {
+               r = -errno;
+               goto ERROR;
+       }
+
+       // To store all manifests
+       writer.manifests = json_object_new_array();
+       if (!writer.manifests) {
+               r = -errno;
+               goto ERROR;
+       }
+
        // Make a new archive writer
        r = pakfire_archive_writer_create(&writer.image, pakfire, PAKFIRE_FORMAT_OCI, f);
        if (r < 0)
@@ -201,14 +532,41 @@ int pakfire_oci_mkimage(struct pakfire* pakfire, FILE* f) {
 
        // Make the layer
        r = pakfire_oci_writer_make_layer(&writer);
-       if (r < 0)
+       if (r < 0) {
+               ERROR(writer.ctx, "Failed to write the layer: %s\n", strerror(-r));
                goto ERROR;
+       }
+
+       // Write the config
+       r = pakfire_oci_writer_write_config(&writer);
+       if (r < 0) {
+               ERROR(writer.ctx, "Failed to write the config: %s\n", strerror(-r));
+               goto ERROR;
+       }
+
+       // Write the manifest
+       r = pakfire_oci_writer_write_manifest(&writer);
+       if (r < 0) {
+               ERROR(writer.ctx, "Failed to write the manifest: %s\n", strerror(-r));
+               goto ERROR;
+       }
+
+       // Write the index
+       r = pakfire_oci_writer_write_index(&writer);
+       if (r < 0) {
+               ERROR(writer.ctx, "Failed to write the index: %s\n", strerror(-r));
+               goto ERROR;
+       }
 
 ERROR:
+       if (writer.manifests)
+               json_object_put(writer.manifests);
+       if (writer.layers)
+               json_object_put(writer.layers);
+       if (writer.config)
+               json_object_put(writer.config);
        if (writer.image)
                pakfire_archive_writer_unref(writer.image);
-       if (writer.layer_hexdigest)
-               free(writer.layer_hexdigest);
        if (writer.ctx)
                pakfire_ctx_unref(writer.ctx);