From dc56bda820ac0cd2a8c18f9f9aee3ec9a9ba571e Mon Sep 17 00:00:00 2001 From: "Owen W. Taylor" Date: Wed, 18 Dec 2024 00:46:33 +0100 Subject: [PATCH] image-source: Add flatpak_image_source_new_for_location Which allows one to create an image source from a container location. It also adds a new FlatpakDockerReference to access different parts of a docker reference and changes to FlatpakOciIndex to get a manifest for a specific architecture. This will become useful in the next commit when we're going to add support for installing OCI images. --- common/flatpak-docker-reference-private.h | 20 ++++ common/flatpak-docker-reference.c | 140 ++++++++++++++++++++++ common/flatpak-image-source-private.h | 3 + common/flatpak-image-source.c | 123 +++++++++++++++++++ common/flatpak-json-oci-private.h | 4 +- common/flatpak-json-oci.c | 21 ++++ common/meson.build | 1 + 7 files changed, 311 insertions(+), 1 deletion(-) create mode 100644 common/flatpak-docker-reference-private.h create mode 100644 common/flatpak-docker-reference.c diff --git a/common/flatpak-docker-reference-private.h b/common/flatpak-docker-reference-private.h new file mode 100644 index 00000000..8205064c --- /dev/null +++ b/common/flatpak-docker-reference-private.h @@ -0,0 +1,20 @@ +#ifndef __FLATPAK_DOCKER_REFERENCE_H__ +#define __FLATPAK_DOCKER_REFERENCE_H__ + +#include + +typedef struct _FlatpakDockerReference FlatpakDockerReference; + +FlatpakDockerReference *flatpak_docker_reference_parse (const char *reference_str, + GError **error); + +const char *flatpak_docker_reference_get_uri (FlatpakDockerReference *reference); +const char *flatpak_docker_reference_get_repository (FlatpakDockerReference *reference); +const char *flatpak_docker_reference_get_tag (FlatpakDockerReference *reference); +const char *flatpak_docker_reference_get_digest (FlatpakDockerReference *reference); + +void flatpak_docker_reference_free (FlatpakDockerReference *reference); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(FlatpakDockerReference, flatpak_docker_reference_free); + +#endif /* __FLATPAK_DOCKER_REFERENCE_H__ */ diff --git a/common/flatpak-docker-reference.c b/common/flatpak-docker-reference.c new file mode 100644 index 00000000..6df3011f --- /dev/null +++ b/common/flatpak-docker-reference.c @@ -0,0 +1,140 @@ +/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s: + * Copyright © 2024 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: + * Owen Taylor + */ + +#include "flatpak-docker-reference-private.h" +#include "flatpak-utils-private.h" + +struct _FlatpakDockerReference +{ + char *uri; + char *repository; + char *tag; + char *digest; +}; + +/* + * Parsing here is loosely based off: + * + * https://github.com/containers/image/tree/main/docker/reference + * + * The major simplification is that we require a domain component, and + * don't have any default domain. This removes ambiguity between domains and paths + * and makes parsing much simpler. We also don't normalize single component + * paths (e.g. ubuntu => library/ubuntu.) + */ + +#define TAG "[0-9A-Za-z_][0-9A-Za-z_-]{0,127}" +#define DIGEST "[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][[:xdigit:]]{32,}" +#define REMAINDER_TAG_AND_DIGEST_RE "^(.*?)(:" TAG ")?" "(@" DIGEST ")?$" + +static GRegex * +get_remainder_tag_and_digest_regex (void) +{ + static gsize regex = 0; + + if (g_once_init_enter (®ex)) + { + g_autoptr(GRegex) compiled = g_regex_new (REMAINDER_TAG_AND_DIGEST_RE, + G_REGEX_DEFAULT, + G_REGEX_MATCH_DEFAULT, + NULL); + g_once_init_leave (®ex, (gsize)g_steal_pointer (&compiled)); + } + + return (GRegex *)regex; +} + +FlatpakDockerReference * +flatpak_docker_reference_parse (const char *reference_str, + GError **error) +{ + g_autoptr(FlatpakDockerReference) reference = g_new0 (FlatpakDockerReference, 1); + GRegex *regex = get_remainder_tag_and_digest_regex (); + g_autoptr(GMatchInfo) match_info = NULL; + g_autofree char *tag_match = NULL; + g_autofree char *digest_match = NULL; + g_autofree char *remainder = NULL; + g_autofree char *domain = NULL; + gboolean matched; + const char *slash; + + matched = g_regex_match (regex, reference_str, G_REGEX_MATCH_DEFAULT, &match_info); + g_assert (matched); + + tag_match = g_match_info_fetch (match_info, 2); + if (tag_match[0] == '\0') + reference->tag = NULL; + else + reference->tag = g_strdup (tag_match + 1); + + digest_match = g_match_info_fetch (match_info, 3); + if (digest_match[0] == '\0') + reference->digest = NULL; + else + reference->digest = g_strdup (digest_match + 1); + + remainder = g_match_info_fetch (match_info, 1); + slash = strchr (remainder, '/'); + if (slash == NULL || slash == reference_str || *slash == '\0') + { + flatpak_fail(error, "Can't parse %s into /", remainder); + return NULL; + } + + domain = g_strndup (remainder, slash - remainder); + reference->uri = g_strconcat ("https://", domain, NULL); + reference->repository = g_strdup (slash + 1); + + return g_steal_pointer (&reference); +} + +const char * +flatpak_docker_reference_get_uri (FlatpakDockerReference *reference) +{ + return reference->uri; +} + +const char * +flatpak_docker_reference_get_repository (FlatpakDockerReference *reference) +{ + return reference->repository; +} + +const char * +flatpak_docker_reference_get_tag (FlatpakDockerReference *reference) +{ + return reference->tag; +} + +const char * +flatpak_docker_reference_get_digest (FlatpakDockerReference *reference) +{ + return reference->digest; +} + +void +flatpak_docker_reference_free (FlatpakDockerReference *reference) +{ + g_clear_pointer (&reference->uri, g_free); + g_clear_pointer (&reference->repository, g_free); + g_clear_pointer (&reference->tag, g_free); + g_clear_pointer (&reference->digest, g_free); + g_free (reference); +} diff --git a/common/flatpak-image-source-private.h b/common/flatpak-image-source-private.h index fe8f02ba..b1e5d2fa 100644 --- a/common/flatpak-image-source-private.h +++ b/common/flatpak-image-source-private.h @@ -41,6 +41,9 @@ FlatpakImageSource *flatpak_image_source_new_remote (const char *uri, const char *digest, GCancellable *cancellable, GError **error); +FlatpakImageSource *flatpak_image_source_new_for_location (const char *location, + GCancellable *cancellable, + GError **error); void flatpak_image_source_set_token (FlatpakImageSource *self, const char *token); diff --git a/common/flatpak-image-source.c b/common/flatpak-image-source.c index 42325501..9ccb284f 100644 --- a/common/flatpak-image-source.c +++ b/common/flatpak-image-source.c @@ -20,6 +20,7 @@ #include +#include "flatpak-docker-reference-private.h" #include "flatpak-image-source-private.h" #include "flatpak-oci-registry-private.h" @@ -178,6 +179,128 @@ flatpak_image_source_new_remote (const char *uri, return flatpak_image_source_new (registry, oci_repository, digest, cancellable, error); } +/* Parse an oci: or oci-archive: image location into a path + * and an optional reference + */ +static void +get_path_and_reference (const char *image_location, + GFile **path, + char **reference) +{ + g_autofree char *path_str = NULL; + const char *bare; + const char *colon; + + colon = strchr (image_location, ':'); + g_assert (colon != NULL); + + bare = colon + 1; + colon = strchr (bare, ':'); + + if (colon) + { + path_str = g_strndup (bare, colon - bare); + *reference = g_strdup (colon + 1); + } + else + { + path_str = g_strdup (bare); + *reference = NULL; + } + + *path = g_file_new_for_path (path_str); +} + +FlatpakImageSource * +flatpak_image_source_new_for_location (const char *location, + GCancellable *cancellable, + GError **error) +{ + if (g_str_has_prefix (location, "oci:")) + { + g_autoptr(GFile) path = NULL; + g_autofree char *reference = NULL; + + get_path_and_reference (location, &path, &reference); + + return flatpak_image_source_new_local (path, reference, cancellable, error); + } + else if (g_str_has_prefix (location, "docker:")) + { + g_autoptr(FlatpakOciRegistry) registry = NULL; + g_autoptr(FlatpakDockerReference) docker_reference = NULL; + g_autofree char *local_digest = NULL; + const char *repository = NULL; + + if (!g_str_has_prefix (location, "docker://")) + { + flatpak_fail (error, "docker: location must start docker://"); + return NULL; + } + + docker_reference = flatpak_docker_reference_parse (location + 9, error); + if (docker_reference == NULL) + return NULL; + + registry = flatpak_oci_registry_new (flatpak_docker_reference_get_uri (docker_reference), + FALSE, -1, cancellable, error); + if (registry == NULL) + return NULL; + + repository = flatpak_docker_reference_get_repository (docker_reference); + + local_digest = g_strdup (flatpak_docker_reference_get_digest (docker_reference)); + if (local_digest == NULL) + { + g_autoptr(GBytes) bytes = NULL; + g_autoptr(FlatpakOciVersioned) versioned = NULL; + const char *tag = flatpak_docker_reference_get_tag (docker_reference); + + if (tag == NULL) + tag = "latest"; + + bytes = flatpak_oci_registry_load_blob (registry, repository, TRUE, tag, + NULL, NULL, cancellable, error); + if (!bytes) + return NULL; + + versioned = flatpak_oci_versioned_from_json (bytes, NULL, error); + if (!versioned) + return NULL; + + if (FLATPAK_IS_OCI_MANIFEST (versioned)) + { + g_autofree char *checksum = NULL; + + checksum = g_compute_checksum_for_bytes (G_CHECKSUM_SHA256, bytes); + local_digest = g_strconcat ("sha256:", checksum, NULL); + } + else if (FLATPAK_IS_OCI_INDEX (versioned)) + { + const char *oci_arch = flatpak_arch_to_oci_arch (flatpak_get_arch ()); + FlatpakOciManifestDescriptor *descriptor; + + descriptor = flatpak_oci_index_get_manifest_for_arch (FLATPAK_OCI_INDEX (versioned), oci_arch); + if (descriptor == NULL) + { + flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, + "Can't find manifest for %s in image index", oci_arch); + return NULL; + } + + local_digest = g_strdup (descriptor->parent.digest); + } + } + + return flatpak_image_source_new (registry, repository, local_digest, cancellable, error); + } + else + { + flatpak_fail (error, "unsupported image location: %s", location); + return NULL; + } +} + void flatpak_image_source_set_token (FlatpakImageSource *self, const char *token) diff --git a/common/flatpak-json-oci-private.h b/common/flatpak-json-oci-private.h index 87573c17..e8924ee4 100644 --- a/common/flatpak-json-oci-private.h +++ b/common/flatpak-json-oci-private.h @@ -166,8 +166,10 @@ gboolean flatpak_oci_index_remove_manifest (FlatpakOciIndex FlatpakOciManifestDescriptor *flatpak_oci_index_get_manifest (FlatpakOciIndex *self, const char *ref); FlatpakOciManifestDescriptor *flatpak_oci_index_get_only_manifest (FlatpakOciIndex *self); -int flatpak_oci_index_get_n_manifests (FlatpakOciIndex *self); +FlatpakOciManifestDescriptor *flatpak_oci_index_get_manifest_for_arch (FlatpakOciIndex *self, + const char *oci_arch); +int flatpak_oci_index_get_n_manifests (FlatpakOciIndex *self); /* Only useful for delta index */ FlatpakOciDescriptor *flatpak_oci_index_find_delta_for (FlatpakOciIndex *delta_index, const char *for_digest); diff --git a/common/flatpak-json-oci.c b/common/flatpak-json-oci.c index 2b55b9de..3a339a02 100644 --- a/common/flatpak-json-oci.c +++ b/common/flatpak-json-oci.c @@ -577,6 +577,27 @@ flatpak_oci_index_get_only_manifest (FlatpakOciIndex *self) return NULL; } +FlatpakOciManifestDescriptor * +flatpak_oci_index_get_manifest_for_arch (FlatpakOciIndex *self, + const char *oci_arch) +{ + int i, found = -1; + + if (self->manifests == NULL) + return NULL; + + for (i = 0; self->manifests[i] != NULL; i++) + { + if (strcmp (self->manifests[i]->platform.architecture, oci_arch) == 0) + return self->manifests[i]; + } + + if (found >= 0) + return self->manifests[found]; + + return NULL; +} + gboolean flatpak_oci_index_remove_manifest (FlatpakOciIndex *self, const char *ref) diff --git a/common/meson.build b/common/meson.build index 67627538..5aee3cd9 100644 --- a/common/meson.build +++ b/common/meson.build @@ -186,6 +186,7 @@ sources = [ 'flatpak-context.c', 'flatpak-dir.c', 'flatpak-dir-utils.c', + 'flatpak-docker-reference.c', 'flatpak-error.c', 'flatpak-exports.c', 'flatpak-glib-backports.c',