ostree/summary: Generate an ostree-metadata ref when updating summary
authorPhilip Withnall <withnall@endlessm.com>
Mon, 11 Sep 2017 13:42:52 +0000 (14:42 +0100)
committerAtomic Bot <atomic-devel@projectatomic.io>
Mon, 2 Oct 2017 13:39:41 +0000 (13:39 +0000)
This is the new way of publishing repository metadata, rather than as
additional-metadata in the summary file. The use of an ostree-metadata
ref means that the metadata from multiple upstream collections is not
conflated when doing P2P mirroring of many repositories.

The new ref is only generated if the repository has a collection ID set.
The old summary file continues to be generated for backwards
compatibility (and because it continues to be the canonical ref →
checksum map for the repository).

The new code is only used if configured with --enable-experimental-api.

Includes unit tests.

Signed-off-by: Philip Withnall <withnall@endlessm.com>
Closes: #1158
Approved by: cgwalters

man/ostree-summary.xml
src/ostree/ot-builtin-summary.c
tests/test-summary-update.sh

index dd72a6ea702fa1c61ea5a771c27e56e2c278c41c..815d2b5a3071606409d2f0a2da45de70c101bf7b 100644 (file)
@@ -83,7 +83,16 @@ Boston, MA 02111-1307, USA.
                   your organisation or repository using a dot prefix. The values
                   must be in GVariant text format. For example,
                   <command>exampleos.end-of-life "@t 1445385600"</command>.
-                </para></listitem>
+                </para>
+
+                <!-- FIXME: Uncomment this when collection ID support becomes non-experimental.
+                <para>If the repository has a collection ID configured, the
+                  <filename>ostree-metadata</filename> branch for that collection ID
+                  will also be updated with a new commit containing the given metadata,
+                  which will be signed if the summary file is signed.</para>
+                -->
+
+                </listitem>
             </varlistentry>
 
             <varlistentry>
index 9d04753d91b3e41b474abc09eb03b993fef3529b..abd1f86cc6899b6d9296ba8c8ff4f2082a7a81df 100644 (file)
@@ -106,6 +106,97 @@ ostree_builtin_summary (int argc, char **argv, GCancellable *cancellable, GError
             return FALSE;
         }
 
+#ifdef OSTREE_ENABLE_EXPERIMENTAL_API
+      const char *collection_id = ostree_repo_get_collection_id (repo);
+#else  /* if !OSTREE_ENABLE_EXPERIMENTAL_API */
+      const char *collection_id = NULL;
+#endif  /* OSTREE_ENABLE_EXPERIMENTAL_API */
+
+      /* Write out a new metadata commit for the repository. */
+      if (collection_id != NULL)
+        {
+#ifdef OSTREE_ENABLE_EXPERIMENTAL_API
+          OstreeCollectionRef collection_ref = { (gchar *) collection_id, (gchar *) OSTREE_REPO_METADATA_REF };
+          g_autofree char *old_ostree_metadata_checksum = NULL;
+          g_autofree gchar *new_ostree_metadata_checksum = NULL;
+          g_autoptr(OstreeMutableTree) mtree = NULL;
+          g_autoptr(OstreeRepoFile) repo_file = NULL;
+          g_autoptr(GVariantDict) new_summary_commit_dict = NULL;
+          g_autoptr(GVariant) new_summary_commit = NULL;
+
+          if (!ostree_repo_resolve_rev (repo, OSTREE_REPO_METADATA_REF,
+                                        TRUE, &old_ostree_metadata_checksum, error))
+            return FALSE;
+
+          /* Add bindings to the metadata. */
+          new_summary_commit_dict = g_variant_dict_new (additional_metadata);
+          g_variant_dict_insert (new_summary_commit_dict, OSTREE_COMMIT_META_KEY_COLLECTION_BINDING,
+                                 "s", collection_ref.collection_id);
+          g_variant_dict_insert_value (new_summary_commit_dict, OSTREE_COMMIT_META_KEY_REF_BINDING,
+                                       g_variant_new_strv ((const gchar * const *) &collection_ref.ref_name, 1));
+          new_summary_commit = g_variant_dict_end (new_summary_commit_dict);
+
+          if (!ostree_repo_prepare_transaction (repo, NULL, cancellable, error))
+            return FALSE;
+
+          /* Set up an empty mtree. */
+          mtree = ostree_mutable_tree_new ();
+
+          glnx_unref_object GFileInfo *fi = g_file_info_new ();
+          g_file_info_set_attribute_uint32 (fi, "unix::uid", 0);
+          g_file_info_set_attribute_uint32 (fi, "unix::gid", 0);
+          g_file_info_set_attribute_uint32 (fi, "unix::mode", (0755 | S_IFDIR));
+
+          g_autofree guchar *csum_raw = NULL;
+          g_autofree char *csum = NULL;
+
+          g_autoptr(GVariant) dirmeta = ostree_create_directory_metadata (fi, NULL /* xattrs */);
+
+          if (!ostree_repo_write_metadata (repo, OSTREE_OBJECT_TYPE_DIR_META, NULL,
+                                           dirmeta, &csum_raw, cancellable, error))
+            return FALSE;
+
+          csum = ostree_checksum_from_bytes (csum_raw);
+          ostree_mutable_tree_set_metadata_checksum (mtree, csum);
+
+          if (!ostree_repo_write_mtree (repo, mtree, (GFile **) &repo_file, NULL, error))
+            return FALSE;
+
+          if (!ostree_repo_write_commit (repo, old_ostree_metadata_checksum,
+                                         NULL  /* subject */, NULL  /* body */,
+                                         new_summary_commit, repo_file, &new_ostree_metadata_checksum,
+                                         NULL, error))
+            return FALSE;
+
+          if (opt_key_ids != NULL)
+            {
+              for (const char * const *iter = (const char * const *) opt_key_ids;
+                   iter != NULL && *iter != NULL; iter++)
+                {
+                  const char *key_id = *iter;
+
+                  if (!ostree_repo_sign_commit (repo,
+                                                new_ostree_metadata_checksum,
+                                                key_id,
+                                                opt_gpg_homedir,
+                                                cancellable,
+                                                error))
+                    return FALSE;
+                }
+            }
+
+          ostree_repo_transaction_set_collection_ref (repo, &collection_ref,
+                                                      new_ostree_metadata_checksum);
+
+          if (!ostree_repo_commit_transaction (repo, NULL, cancellable, error))
+            return FALSE;
+#else  /* if !OSTREE_ENABLE_EXPERIMENTAL_API */
+          g_assert_not_reached ();
+          return FALSE;
+#endif  /* OSTREE_ENABLE_EXPERIMENTAL_API */
+        }
+
+      /* Regenerate and sign the conventional summary file. */
       if (!ostree_repo_regenerate_summary (repo, additional_metadata, cancellable, error))
         return FALSE;
 
index 457debbe18140bee3ba4802e498250e649caec3d..60228114b0c822aa11a770d020ee934b35c8ba25 100755 (executable)
@@ -91,4 +91,23 @@ ${CMD_PREFIX} ostree --repo=repo summary --update --add-metadata=map='@a{sv} {}'
 ${CMD_PREFIX} ostree --repo=repo summary --view > summary
 assert_file_has_content summary "^map: {}$"
 
+# Check the ostree-metadata ref has also been created with the same content and appropriate bindings.
+${CMD_PREFIX} ostree --repo=repo refs --collections > refs
+assert_file_has_content refs "^(org.example.Collection1, ostree-metadata)$"
+
+${CMD_PREFIX} ostree --repo=repo show ostree-metadata --raw > metadata
+assert_file_has_content metadata "'map': <@a{sv} {}>"
+assert_file_has_content metadata "'ostree.ref-binding': <\['ostree-metadata'\]>"
+assert_file_has_content metadata "'ostree.collection-binding': <'org.example.Collection1'>"
+
+# There should be 5 commits in the ostree-metadata branch, since we’ve updated the summary 5 times.
+${CMD_PREFIX} ostree --repo=repo log ostree-metadata | grep 'commit ' | wc -l > commit-count
+assert_file_has_content commit-count "^5$"
+
+# The ostree-metadata commits should not contain any files
+${CMD_PREFIX} ostree --repo=repo ls ostree-metadata > files
+assert_file_has_content files " /$"
+cat files | wc -l > files-count
+assert_file_has_content files-count "^1$"
+
 echo "ok 2 update summary with collections"