From 0ddabcc84b6d478e9303333145da8d5569752bd5 Mon Sep 17 00:00:00 2001 From: Michael Tremer Date: Sat, 8 Feb 2025 17:50:17 +0000 Subject: [PATCH] oci: Add basic OCI metadata 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 --- src/pakfire/oci.c | 380 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 369 insertions(+), 11 deletions(-) diff --git a/src/pakfire/oci.c b/src/pakfire/oci.c index b1e69710..4059bc53 100644 --- a/src/pakfire/oci.c +++ b/src/pakfire/oci.c @@ -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); -- 2.39.5