{"id":111,"date":"2019-06-20T13:39:35","date_gmt":"2019-06-20T13:39:35","guid":{"rendered":"https:\/\/blog.devopsabcs.com\/?p=111"},"modified":"2023-09-23T03:10:03","modified_gmt":"2023-09-23T03:10:03","slug":"one-project-to-rule-them-all-2","status":"publish","type":"post","link":"https:\/\/blog-dev-001-bcdr.devopsabcs.com\/index.php\/2019\/06\/20\/one-project-to-rule-them-all-2\/","title":{"rendered":"One Project To Rule Them All"},"content":{"rendered":"\n<h3 class=\"wp-block-heading\">Part 2: The Migration Tool<\/h3>\n\n\n\n<p>In our previous <a href=\"https:\/\/blog.devopsabcs.com\/index.php\/2019\/06\/12\/one-project-to-rule-them-all\/\">post<\/a> (first showcased on <a href=\"https:\/\/devblogs.microsoft.com\/premier-developer\/one-project-to-rule-them-all\/\">Microsoft&#8217;s Premier Developer Blog<\/a>), we discussed the business value of taking a single organization, single project approach for all of your enterprise or organization&#8217;s IT <em>projects<\/em>. Emphasis on this last word is placed because the word <em>project<\/em> is used in the business sense. In the language of Azure DevOps, a business project&#8217;s <a href=\"https:\/\/docs.microsoft.com\/en-us\/azure\/devops\/boards\/work-items\/about-work-items?view=azure-devops\">Work Items<\/a> are fenced under an <a href=\"https:\/\/docs.microsoft.com\/en-us\/azure\/devops\/organizations\/settings\/about-areas-iterations?view=azure-devops\">Area<\/a> in the context of a single host or parent <a href=\"https:\/\/docs.microsoft.com\/en-us\/azure\/devops\/organizations\/projects\/about-projects?view=azure-devops\">Azure DevOps Project<\/a>.<\/p>\n\n\n\n<p>In this post, we focus on fundamental techniques used in the <strong>Migration Tool<\/strong> which allows us to clone and more importantly merge multiple Azure DevOps projects into a single project.<\/p>\n\n\n\n<p>The <strong>Migration Tool<\/strong> goes through 4 stages:<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li><strong>Project Export aka Template Generation<\/strong>: In this stage, various configurations of the source project are exported into JSON format and at times templatized or tokenized. For instance, build and release definitions are exported in JSON format and parsed so that the referenced code repositories as well as service connections are tokenized. In doing so, we can later import the pipeline definitions into the target project by replacing the tokens with the target repositories and service endpoints. This stage consists of Azure DevOps REST API calls using the GET Method.<\/li><li><strong>Project Import<\/strong>: Everything but Work Items and Queries get imported at this stage. This stage relies mainly on Azure DevOps REST API calls using the POST and PUT Methods. The main items migrated are:<ul><li>Install required extensions<\/li><li>Project Area and Iteration Hierarchies<\/li><li>Teams, Team Settings, Team Boards<\/li><li>Service Connections aka Service Endpoints<\/li><li>Git repositories along with commit history, pull requests, branches, and tags<\/li><li>Build Definitions<\/li><li>Release Definitions<\/li><li>Security Template Generation based on the above migrated items<\/li><\/ul><\/li><li><strong>Migration of Work Items and Shared Queries<\/strong>: This stage leverages <a href=\"https:\/\/github.com\/nkdAgility\/azure-devops-migration-tools\">Martin Hinshelwood&#8217;s migration tool<\/a>. The only modification brought is the ability to add an arbitrary <em>PrefixPath<\/em> to the area and iteration paths of migrated work items in order to merge projects into a common Portfolio and\/or Program and\/or Product hierarchy. Note that by default, work item history is preserved but one can choose to only migrate the latest work items or even a subset thereof.<\/li><li><strong>Security Configuration<\/strong>: This is the last stage and possibly the one that has the greatest variance in terms of enterprises&#8217; requirements. Currently, the tool adds a default layer of security by fencing members of the default migrated team in such a way that they have Contributor rights to migrated items of the source project only. In other words, if member A is only part of the default team of Project X and member B is only part of the default team of Project Y, then, once Projects X and Y are merged into a single host project, member A only has access to Project X items and member B only has access to Project Y items. Security will be discussed in further detail in <a href=\"https:\/\/blog.devopsabcs.com\/index.php\/2019\/06\/24\/one-project-to-rule-them-all-3\/\">Part 3 of this Blog series<\/a>.<\/li><\/ol>\n\n\n\n<p>As mentioned previously, the Migration Tool heavily depends on the <a href=\"https:\/\/docs.microsoft.com\/en-us\/rest\/api\/azure\/devops\/?view=azure-devops-rest-5.0\">Azure DevOps Services REST API<\/a>. As of the time of this writing, the REST API is at version 5.0 with version <a href=\"https:\/\/docs.microsoft.com\/en-us\/rest\/api\/azure\/devops\/?view=azure-devops-rest-5.1\">5.1 in preview<\/a>. Here&#8217;s a sample REST call to list all projects in an organization:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><a href=\"https:\/\/blog.devopsabcs.com\/wp-content\/uploads\/2019\/06\/rest-api-v5.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"658\" src=\"https:\/\/blog.devopsabcs.com\/wp-content\/uploads\/2019\/06\/rest-api-v5-1024x658.png\" alt=\"\" class=\"wp-image-115\" srcset=\"https:\/\/blog-dev-001-bcdr.devopsabcs.com\/wp-content\/uploads\/2019\/06\/rest-api-v5-1024x658.png 1024w, https:\/\/blog-dev-001-bcdr.devopsabcs.com\/wp-content\/uploads\/2019\/06\/rest-api-v5-300x193.png 300w, https:\/\/blog-dev-001-bcdr.devopsabcs.com\/wp-content\/uploads\/2019\/06\/rest-api-v5-768x494.png 768w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><figcaption>Projects &#8211; List<\/figcaption><\/figure>\n\n\n\n<p>The Microsoft Documentation provides an example <a href=\"https:\/\/docs.microsoft.com\/en-us\/azure\/devops\/integrate\/get-started\/rest\/samples?view=azure-devops#rest-api\">getting a list of projects for your organization<\/a>. Since <a href=\"https:\/\/nodogmablog.bryanhogan.net\/2017\/10\/httpcontent-readasasync-with-net-core-2\/\">.NET Core 2 no longer has an HttpContent.ReadAsAsync<\/a> method, we have modified the code slightly:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>using Newtonsoft.Json;\nusing System;\nusing System.Diagnostics;\nusing System.Net.Http;\nusing System.Net.Http.Headers;\n\nnamespace SampleRestAPI\n{\n    class Program\n    {\n        private static readonly string personalAccessToken = \"&lt;YOUR PAT TOKEN>\";\n        private static readonly string OrgName = \"&lt;YOUR ORGANIZATION>\";\n\n        static void Main(string[] args)\n        {\n\n            \/\/encode your personal access token                   \n            string credentials = Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(string.Format(\"{0}:{1}\", \"\", personalAccessToken)));\n\n            ListOfProjectsResponse.Projects viewModel = null;\n\n            \/\/use the httpclient\n            using (var client = new HttpClient())\n            {\n                client.BaseAddress = new Uri($\"https:\/\/dev.azure.com\/{OrgName}\");  \/\/url of your organization\n                client.DefaultRequestHeaders.Accept.Clear();\n                client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue(\"application\/json\"));\n                client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Basic\", credentials);\n\n                \/\/connect to the REST endpoint            \n                HttpResponseMessage response = client.GetAsync($\"{client.BaseAddress}\/_apis\/projects?stateFilter=All&amp;api-version=5.0\").Result;\n\n                \/\/check to see if we have a successful response\n                if (response.IsSuccessStatusCode)\n                {\n                    \/\/set the viewmodel from the content in the response\n                    string json = response.Content.ReadAsStringAsync().Result;\n                    viewModel = JsonConvert.DeserializeObject&lt;ListOfProjectsResponse.Projects>(json);\n                }\n                else\n                {\n                    \/\/process error message\n                    var errorMessage = response.Content.ReadAsStringAsync().Result;\n                    Trace.TraceError(errorMessage);\n                }\n            }\n        }\n    }\n}\n<\/code><\/pre>\n\n\n\n<p>If you paste this code in a .NET Core Console Application, then you will also need to define a class <code>ListOfProjectsResponse.Projects<\/code> from the JSON response. There are <a href=\"https:\/\/stackoverflow.com\/questions\/21611674\/how-to-auto-generate-a-c-sharp-class-file-from-a-json-object-string\">multiple ways of doing this<\/a>.<\/p>\n\n\n\n<p>In order to obtain a JSON response in the first place, you can use a tool such as <a href=\"https:\/\/www.getpostman.com\/\">Postman<\/a>. <\/p>\n\n\n\n<figure class=\"wp-block-image\"><a href=\"https:\/\/blog.devopsabcs.com\/wp-content\/uploads\/2019\/06\/postman.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"657\" src=\"https:\/\/blog.devopsabcs.com\/wp-content\/uploads\/2019\/06\/postman-1024x657.png\" alt=\"\" class=\"wp-image-124\" srcset=\"https:\/\/blog-dev-001-bcdr.devopsabcs.com\/wp-content\/uploads\/2019\/06\/postman-1024x657.png 1024w, https:\/\/blog-dev-001-bcdr.devopsabcs.com\/wp-content\/uploads\/2019\/06\/postman-300x193.png 300w, https:\/\/blog-dev-001-bcdr.devopsabcs.com\/wp-content\/uploads\/2019\/06\/postman-768x493.png 768w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><figcaption>Project List fetched using Postman<\/figcaption><\/figure>\n\n\n\n<p>There is a useful <a href=\"https:\/\/github.com\/hkamel\/azuredevops-postman-collections\">Azure DevOps Postman collection<\/a> that can get you up and running quickly. Combining these tools together, we obtain the following C# class:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>using System;\n\nnamespace ListOfProjectsResponse\n{\n    public class Projects\n    {\n        public int count { get; set; }\n        public Value[] value { get; set; }\n    }\n\n    public class Value\n    {\n        public string id { get; set; }\n        public string name { get; set; }\n        public string description { get; set; }\n        public string url { get; set; }\n        public string state { get; set; }\n        public int revision { get; set; }\n        public string visibility { get; set; }\n        public DateTime lastUpdateTime { get; set; }\n    }\n\n}<\/code><\/pre>\n\n\n\n<p>Some useful safeguards and tricks when using automatic class generation tools:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Ensure that you are generating classes from as complete and diversified JSON responses as you can. If not, you may need to complete or fix the classes manually using the <a href=\"https:\/\/docs.microsoft.com\/en-us\/rest\/api\/azure\/devops\/core\/projects\/list?view=azure-devops-rest-5.0#teamprojectreference\">REST API documentation<\/a>. These fixes may also include changing the property type (e.g. from <code>int<\/code> to <code>float<\/code>).<\/li><li>If the JSON properties contain characters not permitted in property names then you may need to use attributes such as:<\/li><\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>        public class Fields\n        {\n            [JsonProperty(PropertyName = \"System.AreaPath\")]\n            public string SystemAreaPath { get; set; }            \n            [JsonProperty(PropertyName = \"System.TeamProject\")]\n            public string SystemTeamProject { get; set; }\n            [JsonProperty(PropertyName = \"System.IterationPath\")]\n            public string SystemIterationPath { get; set; }\n            [JsonProperty(PropertyName = \"System.WorkItemType\")]\n            public string SystemWorkItemType { get; set; }\n            [JsonProperty(PropertyName = \"System.State\")]\n            public string SystemState { get; set; }\n            [JsonProperty(PropertyName = \"System.Reason\")]\n            public string SystemReason { get; set; }\n            [JsonProperty(PropertyName = \"System.AssignedTo\")]\n            public SystemAssignedto SystemAssignedTo { get; set; }\n            [JsonProperty(PropertyName = \"System.CreatedDate\")]\n            public DateTime SystemCreatedDate { get; set; }\n            [JsonProperty(PropertyName = \"System.CreatedBy\")]\n            public SystemCreatedby SystemCreatedBy { get; set; }\n            [JsonProperty(PropertyName = \"System.ChangedDate\")]\n            public DateTime SystemChangedDate { get; set; }\n            [JsonProperty(PropertyName = \"System.ChangedBy\")]\n            public SystemChangedby SystemChangedBy { get; set; }\n            [JsonProperty(PropertyName = \"System.CommentCount\")]\n            public int SystemCommentCount { get; set; }\n            [JsonProperty(PropertyName = \"System.Title\")]\n            public string SystemTitle { get; set; }\n            [JsonProperty(PropertyName = \"System.BoardColumn\")]\n            public string SystemBoardColumn { get; set; }\n            [JsonProperty(PropertyName = \"System.BoardColumnDone\")]\n            public bool SystemBoardColumnDone { get; set; }\n            [JsonProperty(PropertyName = \"Microsoft.VSTS.Scheduling.RemainingWork\")]\n            public float MicrosoftVSTSSchedulingRemainingWork { get; set; }\n\t    [JsonProperty(PropertyName = \"Microsoft.VSTS.Common.StateChangeDate\")]\n            public DateTime MicrosoftVSTSCommonStateChangeDate { get; set; }\n\t    [JsonProperty(PropertyName = \"Microsoft.VSTS.Common.Priority\")]\n            public float MicrosoftVSTSCommonPriority { get; set; }\n\t    [JsonProperty(PropertyName = \"Microsoft.VSTS.Common.ValueArea\")]\n            public string MicrosoftVSTSCommonValueArea { get; set; }\n\t    [JsonProperty(PropertyName = \"Microsoft.VSTS.Scheduling.Effort\")]\n            public float MicrosoftVSTSSchedulingEffort { get; set; }\n            \/\/public string WEF_1CF533FEA38947A28C3BFCE684354680_KanbanColumn { get; set; }\n            \/\/public bool WEF_1CF533FEA38947A28C3BFCE684354680_KanbanColumnDone { get; set; }\n\t    [JsonProperty(PropertyName = \"System.Description\")]\n            public string SystemDescription { get; set; }\n        }<\/code><\/pre>\n\n\n\n<ul class=\"wp-block-list\"><li>Certain JSON responses have different variants possible. For instance, when you <a href=\"https:\/\/docs.microsoft.com\/en-us\/rest\/api\/azure\/devops\/serviceendpoint\/endpoints\/get%20service%20endpoints?view=azure-devops-rest-5.0\">get service endpoints from a project<\/a>, then, depending on the service endpoint type, you will end up with different <code>data<\/code> as well as different <a href=\"https:\/\/docs.microsoft.com\/en-us\/rest\/api\/azure\/devops\/serviceendpoint\/endpoints\/get%20service%20endpoints?view=azure-devops-rest-5.0#endpointauthorization\"><code>EndpointAuthorization<\/code><\/a>. An example is in order. A <code>git<\/code> service endpoint with <code>authorization scheme<\/code> &#8220;UsernamePassword&#8221; may have JSON like this:<\/li><\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>        {\n            \"data\": {},\n            \"id\": \"a660b887-8c0a-4052-bff1-0b624548d741\",\n            \"name\": \"DG_DevOpsDemoGenerator-code\",\n            \"type\": \"git\",\n            \"url\": \"https:\/\/dev.azure.com\/devopsabcs\/DevOpsDemoGenerator\/_git\/DevOpsDemoGenerator\",\n            \"createdBy\": {\n                \"displayName\": \"manu k\",\n                \"id\": \"56c9c9f2-2c3b-663e-9543-57e7324a46bb\",\n                \"uniqueName\": \"emmanuel@devopsabcs.com\"\n            },\n            \"authorization\": {\n                \"parameters\": {\n                    \"username\": \"tokenOrAnyThingYouWant\"\n                },\n                \"scheme\": \"UsernamePassword\"\n            },\n            \"isShared\": false,\n            \"isReady\": true,\n            \"owner\": \"Library\"\n        }<\/code><\/pre>\n\n\n\n<p>On the other hand, an <code>azurerm<\/code> service endpoint with <code>authorization scheme<\/code> &#8220;ServicePrincipal&#8221; can look like:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>        {\n            \"data\": {\n                \"subscriptionId\": \"630c0928-1c45-42d3-ba99-d0e2e92bd1f1\",\n                \"subscriptionName\": \"Visual Studio Premium with MSDN\",\n                \"environment\": \"AzureCloud\",\n                \"scopeLevel\": \"Subscription\",\n                \"creationMode\": \"Automatic\",\n                \"azureSpnRoleAssignmentId\": \"1d7c9fba-7e43-42c0-841a-539c022583d0\",\n                \"azureSpnPermissions\": \"[{\\\"roleAssignmentId\\\":\\\"8e82494b-cc9d-4a9f-9be2-c391a2939853\\\",\\\"resourceProvider\\\":\\\"Microsoft.RoleAssignment\\\",\\\"provisioned\\\":true},{\\\"roleAssignmentId\\\":\\\"1d7c9fba-7e43-42c0-841a-539c022583d0\\\",\\\"resourceProvider\\\":\\\"Microsoft.RoleAssignment\\\",\\\"provisioned\\\":true}]\",\n                \"spnObjectId\": \"10c81ad9-9151-433e-b51a-956ae8e1915a\",\n                \"appObjectId\": \"836a59a5-603a-4e03-a8df-161afa31e423\"\n            },\n            \"id\": \"13c6d211-cc14-4ab3-80f9-37b0bf9205b6\",\n            \"name\": \"HOL_DevTestResources\",\n            \"type\": \"azurerm\",\n            \"url\": \"https:\/\/management.azure.com\/\",\n            \"createdBy\": {\n                \"displayName\": \"manu k\",\n                \"id\": \"56c9c9f2-2c3b-663e-9543-57e7324a46bb\",\n                \"uniqueName\": \"emmanuel@devopsabcs.com\"\n            },\n            \"authorization\": {\n                \"parameters\": {\n                    \"tenantid\": \"a34c69c7-8959-474a-9690-e98bfb0b55c6\",\n                    \"serviceprincipalid\": \"c5c64fa5-85d6-46ec-9b1c-fb915d7599d0\",\n                    \"authenticationType\": \"spnKey\",\n                    \"scope\": \"\/subscriptions\/630c0928-1c45-42d3-ba99-d0e2e92bd1f1\/resourcegroups\/holDevTestRG\"\n                },\n                \"scheme\": \"ServicePrincipal\"\n            },\n            \"isShared\": false,\n            \"isReady\": true,\n            \"operationStatus\": {\n                \"state\": \"Ready\",\n                \"statusMessage\": \"\"\n            },\n            \"owner\": \"Library\"\n        }<\/code><\/pre>\n\n\n\n<p>From the JSON, we see that both types would generate different C# classes especially in relation to the <code>data<\/code> and <code>authorization<\/code> properties. One way to deal with this is to loosen the class structure by using the <code>Newtonsoft.Json.Linq.JObject<\/code> type as is done here:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>    public class ServiceEndpointModel\n    {\n        public JObject data { get; set; }\n        public string id { get; set; }\n        public string name { get; set; }\n        public string type { get; set; }\n        public string url { get; set; }\n        public CreatedBy createdBy { get; set; }\n        public JObject authorization { get; set; }\n        public bool isReady { get; set; }\n    }<\/code><\/pre>\n\n\n\n<ul class=\"wp-block-list\"><li>Autogeneration is not always clever enough to reuse children classes and often creates separate children classes even though they may in fact be the same class. In this case, you should manually de-dupe and reuse classes whenever possible in order to increase maintainability of the code.<\/li><\/ul>\n\n\n\n<p>The Azure DevOps REST API is a powerful and versatile interface empowering us to automate almost anything involving Azure DevOps. The Migration Tool described here leverages the API to allow us to clone or even merge multiple projects into &#8220;One Project To Rule Them All&#8221;.<\/p>\n\n\n\n<p>In this Blog post, we&#8217;ve learned fundamental techniques enabling us to make the most out of the Azure DevOps REST API. In the <a href=\"https:\/\/blog.devopsabcs.com\/index.php\/2019\/06\/24\/one-project-to-rule-them-all-3\/\">third and final part of this Blog series<\/a>, we dive into security concepts and principles necessary in locking down a single project housing multiple merged projects.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Additional Exploration<\/h3>\n\n\n\n<ul class=\"wp-block-list\"><li><a href=\"https:\/\/docs.microsoft.com\/en-us\/rest\/api\/azure\/devops\/?view=azure-devops-rest-5.0\">Azure DevOps Services REST API Reference<\/a><\/li><\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Part 2: The Migration Tool In our previous post (first showcased on Microsoft&#8217;s Premier Developer Blog), we discussed the business value of taking a single organization, single project approach for all of your enterprise or organization&#8217;s IT projects. Emphasis on this last word is placed because the word project is used in the business sense.<\/p>\n<p><a class=\"readmore\" href=\"https:\/\/blog-dev-001-bcdr.devopsabcs.com\/index.php\/2019\/06\/20\/one-project-to-rule-them-all-2\/\"><span class=\"arrow-right icon\"><\/span>Read More<\/a><\/p>\n","protected":false},"author":1,"featured_media":115,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4,7],"tags":[12,10,8,11,9],"class_list":["post-111","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops","category-project-management","tag-agile-at-scale","tag-azure-devops","tag-devops","tag-migrator-tool","tag-project-management"],"_links":{"self":[{"href":"https:\/\/blog-dev-001-bcdr.devopsabcs.com\/index.php\/wp-json\/wp\/v2\/posts\/111","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog-dev-001-bcdr.devopsabcs.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog-dev-001-bcdr.devopsabcs.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog-dev-001-bcdr.devopsabcs.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog-dev-001-bcdr.devopsabcs.com\/index.php\/wp-json\/wp\/v2\/comments?post=111"}],"version-history":[{"count":29,"href":"https:\/\/blog-dev-001-bcdr.devopsabcs.com\/index.php\/wp-json\/wp\/v2\/posts\/111\/revisions"}],"predecessor-version":[{"id":412,"href":"https:\/\/blog-dev-001-bcdr.devopsabcs.com\/index.php\/wp-json\/wp\/v2\/posts\/111\/revisions\/412"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog-dev-001-bcdr.devopsabcs.com\/index.php\/wp-json\/wp\/v2\/media\/115"}],"wp:attachment":[{"href":"https:\/\/blog-dev-001-bcdr.devopsabcs.com\/index.php\/wp-json\/wp\/v2\/media?parent=111"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog-dev-001-bcdr.devopsabcs.com\/index.php\/wp-json\/wp\/v2\/categories?post=111"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog-dev-001-bcdr.devopsabcs.com\/index.php\/wp-json\/wp\/v2\/tags?post=111"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}