♊️ GemiNews 🗞️
(dev)
🏡
📰 Articles
🏷️ Tags
🧠 Queries
📈 Graphs
☁️ Stats
💁🏻 Assistant
💬
🎙️
Demo 1: Embeddings + Recommendation
Demo 2: Bella RAGa
Demo 3: NewRetriever
Demo 4: Assistant function calling
Editing article
Title
Summary
Content
<p><em>Nel mezzo del cammin di nostra vita, <br>mi ritrovai per una selva oscura, <br>ché la diritta via era smarrita”</em></p><p><em>— </em>Dante Alighieri(*), <a href="https://en.wikipedia.org/wiki/Divine_Comedy">Divine Comedy</a></p><p><em>(*) the Italian version of Shakespeare, just better</em></p><p>Translated for non-🇮🇹: some day I was encouraged by some external entity to move a lot of projects from 5 of my organizations (<em>source</em>) to another organization (<em>destination</em>).</p><p><strong>TL;DR</strong> If you find this article too long and you want to jump to the code, click on <a href="https://gist.github.com/palladius/a99993feb7e6d78b7a2abea0a10c3242">this </a>🐙😺<a href="https://gist.github.com/palladius/a99993feb7e6d78b7a2abea0a10c3242"> gist</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/974/1*Hl8tr6N3VrV_AGItbPb25w.jpeg" /><figcaption>My two boys in front of <a href="https://en.wikipedia.org/wiki/The_Gates_of_Hell">The Gates of Hell</a>, Musée Rodin, Paris, 2022.</figcaption></figure><p>Since I’m the best kind of Software Engineer<em> </em>— the bad coder but <em>lazy</em> kind 🌝 — I decided to script all my move and document for posterity. Also showing you the mistakes I found along the way might be helpful (maybe?).</p><p>This is explained in this GCP page(s): <a href="https://cloud.google.com/resource-manager/docs/project-migration?hl=en">https://cloud.google.com/resource-manager/docs/project-migration</a></p><p>Please remember to dump your current IAM config for the 2+ organisations in case you want to recreate the previous config you just changed. Having a local JSON config for your Org is always a good thing to do. 😉 For instance:</p><pre>$ gcloud alpha organizations get-iam-policy $ORG_ID \<br> - format json > out/org-$DOMAIN-iam.json</pre><p>You can also add specific policies you care about (see below why this is important):</p><pre>$ gcloud resource-manager org-policies describe constraints/iam.allowedPolicyMemberDomains \<br> --organization $ORG_ID | tee -a out/org-$DOMAIN-policies.log</pre><h3>Know the current state of the world</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*FZLcKhtiWl7OqL0w" /></figure><p>You need to know the state of the world (<em>your </em>world). The best way for me to understand where I stand is this:</p><ul><li>Use this awesome repo <a href="https://github.com/palladius/org-folder-projects-graph">https://github.com/palladius/org-folder-projects-graph</a> I wrote a few years ago. <br><strong>Note</strong>: It launches gcloudonce per folder, it’s super-<strong>inefficient</strong> (uses bash over APIs), but has the nice aspect that it keeps all JSON from gcloud locally. This is a big plus in every case except today: ▶️ after the move you might need a make cache-clean before re-launching it.</li><li>Launch the script for one Org. Sample output:</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/477/0*-tHXIe3qGfvvxZfx" /><figcaption>Output from an Org dump script written by an Italian</figcaption></figure><p>If you don’t care about the folder structure at all, a simpler way is to just invoke something like this:</p><pre># Option 1: Flat structure (simple)<br>$ gcloud projects list - filter \<br> "parent.id=$ORG_ID AND parent.type=organization"</pre><p>For example:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/861/0*zwm-PDduGYEb60X4" /><figcaption>flat taxonomy from script1</figcaption></figure><p>Note this won’t work if you have a complex hierarchy, which is why you probably want to use my script to get all the ramification of your org.</p><p>You can also play with <a href="https://cloud.google.com/asset-inventory/docs/overview"><strong>GCP Asset Inventory</strong></a> (see great <a href="https://stackoverflow.com/questions/61244749/how-to-list-all-projects-in-gcp-that-belongs-to-a-specific-organization">stackoverflow</a> tip) to get the list <em>without</em> the 🍕 pizza slices, but you’d be sadder 😫 , I’m sure.</p><pre># Option 2: Tree structure, with AssetInventory.<br>$ gcloud beta asset search-all-resources \<br> --asset-types='cloudresourcemanager.googleapis.com/Project' \<br> --scope="organizations/$ORGID" | egrep '^name:' | cut -d/ -f 5</pre><p>Whatever your workflow, I’m pretty sure you’ll have exceptions, which is why having a spreadsheet (<a href="https://spreadsheet.new/">https://spreadsheet.new/</a> ) will help you big time. I state the source org, destination org, source folder,. source project id, expected destination folder, and notes for my exceptions.</p><p><strong>Please also read this</strong> <a href="https://cloud.google.com/resource-manager/docs/handle-special-cases?hl=en">https://cloud.google.com/resource-manager/docs/handle-special-cases</a> for special cases, like VPC-SC or GCS Bucket lock.</p><p>Let’s now find out the IAM state for your org. This is pretty sweet and took me a while to <a href="https://stackoverflow.com/questions/47006062/how-do-i-list-the-roles-associated-with-a-gcp-service-account">research</a> and adapt:</p><pre># Beautiful query which flattens (multiple roles <=> multiple identities).<br>$ gcloud organizations get-iam-policy "$ORG_ID" \<br> -flatten='bindings[].members' \<br> -format='table(bindings.role,bindings.members)' | <br> tee "t.org-iam-policy.$ORG_DOMAIN.txt"<br>ROLE MEMBERS<br>[..]<br>roles/billing.admin group:gcp-billing-admins@example.org<br>roles/billing.admin user:ricc@example.org<br>roles/billing.creator domain:example.org<br>roles/browser group:gcp-billing-admins@example.org<br>roles/cloudasset.owner user:palladius@example.org<br>roles/cloudasset.owner user:ricc@example.org<br>roles/orgpolicy.policyAdmin group:gcp-org-administrators@example.org<br>roles/orgpolicy.policyAdmin user:riccardo@example.org<br>roles/resourcemanager.projectCreator user:palladiusbonton@example.org<br>roles/resourcemanager.projectMover user:ricc@example.org</pre><h3>I don’t want to read the docs: Let’s first try brute force!</h3><p>I hate it when someone provides me with a working solution and I apply it and don’t know the mystical secrets that led to it. (<em>I invented the half derivative when I was 16, just so you know — and yes, as you can imagine, it’s useless</em>).</p><p>So let’s try to smash our head over it, one mistake at a time.</p><h3>Error 1 : “The caller does not have permission”</h3><p>Let’s try it, the command is simple — but won’t work the first 27 times you try it. You have my word. But still, try it. I’ll use the same ids as in <a href="https://cloud.google.com/resource-manager/docs/configure-org-policy?hl=en">public docs</a>:</p><pre># Your Org Admin account, and gcloud already working for it:<br>export POWER_ACCOUNT='my-power-account@gmail.com'<br># SRC ORG: 12345678901 source.example.com <br>export SRC_ORG_ID="12345678901"<br>export SRC_ORG_DOMAIN='source.example.com'<br># DST ORG: 45678901234 destination.example.com<br>export DST_ORG_ID="45678901234"<br>export DST_ORG_DOMAIN='destination.example.com'<br><br>$ gcloud beta projects move my-project-in-src-org \<br> --organization=$DST_ORG_ID --quiet<br><br>- '@type': type.googleapis.com/google.rpc.DebugInfo<br> detail: |-<br> [ORIGINAL ERROR] generic::permission_denied: The caller does not have permission<br> com.google.apps.framework.request.StatusException: <eye3 title='PERMISSION_DENIED'/> generic::PERMISSION_DENIED: The caller does not have permission [google.rpc.error_details_ext] { code: 7 message: "The caller does not have permission" }<br># Computer says no!</pre><p>The most powerful IAM permission you can have on a GCP Organization is organizationAdmin. You’d think that if you have that power, you can do <em>anything</em> in that universe, right? Think twice! There are additional powers that role doesn’t have (and I’m kind of relieved it is this way).<br>One of them is the OrgPolicyAdmin, which means that as OrgAdmin, you first need to give yourself that power before you can use it (not very UNIX, I know, but definitely safer).</p><pre># Set IAM for SrcOrg<br>$ gcloud organizations add-iam-policy-binding $ORG_ID \<br> --member='user:$ACCOUNT' \<br> --role='roles/resourcemanager.organizationAdmin' \<br> --role='roles/resourcemanager.projectIamAdmin' \<br> --role='roles/resourcemanager.projectMover' \<br> --role='roles/orgpolicy.policyAdmin' \<br> --condition=None</pre><p>For this command to work, you need the issuer (gcloud config get account) to have the power to grant — but this is beside the scope of this doc.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/517/0*GSemn4o3mK4xKGGs" /><figcaption>In the UI it will look something like this</figcaption></figure><p>Note: Org Policies are documented <a href="https://cloud.google.com/resource-manager/docs/organization-policy/org-policy-constraints?hl=it">here</a>.</p><h4>Error 2: Constraint `constraints/resourcemanager.allowedExportDestinations`</h4><p>Let’s try to migrate a project now:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*E7XsdJVJDqFz9u1aka3Gqw.png" /><figcaption>This is NOT where I would have expected a Input/Output error! 😆</figcaption></figure><p>Next step: Let me configure the <strong>allowedPolicyMemberDomains </strong>policy first.<em> (Why? It failed but I forgot to capture the error, my bad. For this error you just need to trust me).</em></p><pre># This might fail..<br>$ gcloud beta projects move "my-project-in-src-org" \<br> --organization="$DST_ORG_ID" --quiet<br><br># This sets target customer_id!<br>$ gcloud resource-manager org-policies allow --organization "$SRC_ORG_ID" \<br> iam.allowedPolicyMemberDomains 'C04abcdef' # gives access to SrcOrg to Dest id</pre><p><strong>Note.</strong> The “C0..” id is the Directory Customer Id you get from gcloud organization list for your destination org.</p><p><em>- I’m ready… Now it’s going to work, I’m sure! <br>- Oh wait… 🙈</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Zoc2xlTjUlowvAGn" /><figcaption>Damn, another error!</figcaption></figure><p>What does it mean? It means that you need to configure your “Org Firewall” to allow projects out of your Source Organization. If you’re a Cisco Certified engineer — you already know what error is coming next.. <strong>Allowlisting palooza </strong>😜<strong>!</strong></p><pre># Aloow SRC to export to DST<br>gcloud resource-manager org-policies allow --organization "$SRC_ORG_ID" \<br> resourcemanager.allowedExportDestinations \<br> "under:organizations/$DST_ORG_ID"<br># Note the "under:" needed to pass the policy all the way down from that folder</pre><h4>Error 3: constraints/resourcemanager.allowedImportSource</h4><p>This is similar to the error above, but it’s from the receiving end.</p><p>To fix: damn I did it on the UI. My apologies. I would presume it’s really the symmetrical opposite, like:</p><pre># This is me guessing but I should be too far. <br># TODO(ricc): remove powers from DST and try again<br>gcloud resource-manager org-policies allow --organization "$DST_ORG_ID" \<br> resourcemanager.allowedImportSource \<br> "under:organizations/$SRC_ORG_ID"</pre><h4>[optional] Refining the security</h4><p>Note that to make things simple I gave a single user OrgAdmin for two organizations.</p><ul><li><strong>simple/insecure (pet project).</strong> The simple way is to have a single powerful account (gcloud account, eg “<a href="mailto:poweruser@company.com">poweruser@company.com</a>”) which has access to both SRC and DST orgs. This worked for me, as I was the sole owner of them all and I was under time pressure.</li><li><strong>complex/more secure (enterprises)</strong>. In a more enterprise scenario, you’d have a less pressing time concern and you would probably be obliged to have smaller IAM permissions on both sides, probably having two individuals to carry on these two tasks. Which is why it’s nice to have them gcloud-ed.</li></ul><p>In the second case, follow the <a href="https://cloud.google.com/resource-manager/docs/assign-iam-roles?hl=it">docs</a>:</p><p><strong>Source Org :</strong></p><ul><li><strong>IAM: roles/resourcemanager.projectIamAdmin</strong> in the project to move (but if I want to do it at scale I’ll set it up in the org)</li><li><strong>IAM: roles/resourcemanager.projectMover</strong> in the parent resource.<br>Again, if you’re lazy you can just set these two at Org level and get it done once and for all. But then remember to remove this access at the end of the org move. You can also a time-bound IAM constraint ending tomorrow — this is for lazy people like me who don’t trust themselves to remember to close a parenthesis tomorrow and rather close it as you open it.</li><li><strong>OrgPolicy</strong>: <strong>constraints/resourcemanager.allowedExportDestinations</strong>. You can set the domain id (“C0somethin”) as allowed destination or you can be lazy as me and just set them ALL, depending if you’re an enterprise who wantgs to do it well or you just want to get it done soon and you don’t care much of the org you leave behind.</li></ul><p>On the <strong>destination organization:</strong></p><ul><li><strong>IAM</strong>: ProjectMover</li><li><strong>Org Policy</strong>: constraints/resourcemanager.allowedImportSource</li></ul><h4>Meta-Scripts and tidy migration</h4><p>If you migrate 300 projects from 5 orgs into one, you might want to use Folders or it’ll be a very nasty place tomorrow (<em>“where does this project come from again?”)</em>. You can track pre-migration state in a few ways: you can dump the state on a lot of TXT in local file (but I usually remove them with my ruthless make cleans), on a Google Spreadsheet, or have your business logic do the job for you. Let’s explore this one..</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1KVyCI3Vy9g2nvZJ9gzZbQ.png" /><figcaption>Here’s the migration in a nutshell: all the Source folder structure complexity is flattened in the Destination under a single special folder.</figcaption></figure><p>I had some fun coding in Ruby (in a private repo which is in no shape to be publicized unless you make a lot of noise in the comments 😉).</p><p>Nothing better than a <strong>script</strong> which writes a <strong>script</strong> for you. This is useful when you want to iterate through 10 migrations but you know that ONE project doesnt fit into it -> I create a 10 line blurb and then I just comment out the one I dont need. It’s also good for documenting which commands you ran on local file. Let me show you.</p><pre><br>$ bin/13-gcloud-move-projects-to-joonix-under-domain.rb palladius.eu palladius-eu-infra prova-enonomai-palldius-eu gcpprojects2sheets <br>[...]<br>gcloud beta projects move 'palladius-eu-infra' --folder='1038774614299' --quiet # migrate project given via CLI to proper subfolder of palladius.joonix.net under palladius.eu<br>gcloud beta projects move 'prova-enonomai-palldius-eu' --folder='1038774614299' --quiet # migrate project given via CLI to proper subfolder of palladius.joonix.net under palladius.eu<br>gcloud beta projects move 'gcpprojects2sheets' --folder='1038774614299' --quiet # migrate project given via CLI to proper subfolder of palladius.joonix.net under palladius.eu</pre><p>Note, this commands are dumped into a deterministically-named bash script which contains the commands, so I can comment out the projects I don’t want to move (yet); the deterministic naming means that if I run it again, it will re-apply the same logic to the same file, which is good if you have it in version control and if the logic is not trivial (eg, find all projects under a folder 😉).</p><p>Let’s now call my 🍕 pizza software (TM) after a make cache-clean to see if the migration worked:</p><pre>$ git clone https://github.com/palladius/org-folder-projects-graph/ <br>$ cd org-folder-projects-graph/<br>(base) ricc@mbp:🏡~/org-folder-projects-graph$ ./recurse_folders.rb palladius.joonix.net<br>[...]<br>├─ 📂 133363080569 (Spostamenti CrossOrg)<br> ├─ 📂 737267668168 (SPECIAL_ORPHANS)<br> ├─ 🍕 cicd-platinum-test041 (713582791007)<br> ├─ 📂 1038774614299 (palladius-eu)<br> ├─ 🍕 original-bot-383907 (642248137380)<br> ├─ 🍕 prova-enonomai-palldius-eu (898096115869)<br> ├─ 🍕 gcpprojects2sheets (383820962508)<br> ├─ 🍕 palladius-eu-infra (845199147079)<br>[..]<br></pre><p>And it did! A round of applause.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/357/0*GIEcWaVhShNnjqI6" /><figcaption>Plenty of magic here: my script accepts a FolderId ( 🇮🇹 “CrossOrg Movemements”) and it searches for the subfolder named like the domain (creating it if its not found). Wow!</figcaption></figure><p>My script was configured to generate a folder called like the org (changing dot with dash for obvious reasons): 133363080569. My script would create a subfolder if needed, get that folder id and create the scripts for me:</p><pre>gcloud beta projects move 'palladius-eu-infra' - folder='1038774614299' - quiet # migrate project given via CLI to proper subfolder of palladius.joonix.net under palladius.eu<br>gcloud beta projects move 'prova-enonomai-palldius-eu' - folder='1038774614299' - quiet # migrate project given via CLI to proper subfolder of palladius.joonix.net under palladius.eu<br>gcloud beta projects move 'gcpprojects2sheets' - folder='1038774614299' - quiet # migrate project given via CLI to proper subfolder of palladius.joonix.net under palladius.eu</pre><p>After a lot of lost hours debugging, I wrote my bash code-writing ruby script to add meaningful comments aside the command. This was a stroke of genius (for my limited coding abilities) since all this movement includes non-mnemonic numbers, so using the domain names after the comment helped me understand what i was doing.</p><h4>Another error: bad chars in project name (!)</h4><p>Now, I don’t know how many of you will find this error, but if you do, Google is your friend (and mine too) and you’ll find this article, I’m sure 😄</p><p>I had project ids since 2012, and I’ve given them names which included parenthesis. For instance, the project id palladiusbonton-nobilling had an astonishing description (Project Name): “<em>PalladiuBontonTest </em><strong><em>(</em></strong><em>No Billing</em><strong><em>)</em></strong>”. Now this was never a problem in my life until some day… some of them gave me error. Interestingly enough, most came from one single error type: <a href="https://www.youtube.com/watch?v=KrZHPOeOxQQ">I gave my project ids a bad name</a>: I should have known better than Bon Jovi!</p><p>Solution: I scripted in 💎 ruby a project description change which just removes the parenthesis (and the regex is DRY in case I find something new). To be on safe side, I change bad chars into <a href="https://www.youtube.com/watch?v=dg9Ae8ACTgw"><em>empty spaces</em></a> 🙂, as I love Queen more than Bon Jovi.</p><pre>$ bin/11-migrate-orphans.rb <br>[..]<br>goliardia2 Goliardia2 (no billing uffi) 576689998257<br>palladiusbonton palladiusbonton (billing disabled) 606248867298<br>palladiusbontontest-nobilling PalladiuBontonTest (No Billing) 690861376600<br>riccardo-chatroom1 riccardo-chatroom-one (billing disabled) 941891472972<br># EXECUTE this to fix the project: <br>gcloud projects update 'palladiusbonton' --name='palladiusbonton billing disabled ' # Former description was: 'palladiusbonton (billing disabled)'<br>palladiusbontontest-nobilling // 690861376600 ### 'PalladiuBontonTest (No Billing)'<br># EXECUTE this to fix the project: <br>gcloud projects update 'palladiusbontontest-nobilling' --name='PalladiuBontonTest No Billing ' # Former description was: 'PalladiuBontonTest (No Billing)'<br>riccardo-chatroom1 // 941891472972 ### 'riccardo-chatroom-one (billing disabled)'<br># EXECUTE this to fix the project: <br>gcloud projects update 'riccardo-chatroom1' --name='riccardo-chatroom-one billing disabled ' # Former description was: 'riccardo-chatroom-one (billing disabled)'</pre><p>Note the pattern: my 11th script creates an output with (1) the hopefully right command and (2) a human-readable description.</p><p>How do Igcloud it?</p><pre>gcloud projects update 'project-id-or-number' \<br> --name='New Description without Parenthesis and stuff'</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*8d9udHN3wrwHeUv3" /><figcaption>..and here its how it looks if you have good eyes or monitor.</figcaption></figure><h4>Wait: what about the orphans?</h4><p>I noticed that not all projects were under an organization, some were orphans (ie, not attached to any organization).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*C0W-d5Xa_F6c1E63.png" /><figcaption>A few orphan project ids, stranded in Google Cloud</figcaption></figure><p>Now the <strong>migration</strong> script is the same as per other projects (<a href="https://cloud.google.com/sdk/gcloud/reference/beta/projects/move">documentation</a>):</p><pre>gcloud beta projects move $PROJECT_ID \<br>--{folder|organization} $DESTINATION_ID</pre><p>However, finding org-less projects require a different search, which took me a while. <a href="https://stackoverflow.com/questions/58543614/how-do-i-list-my-gcp-projects-under-no-organization-using-the-resource-manager-a">Let me Stackoverflow it for you</a>:</p><pre>gcloud projects list --filter="parent.id.yesno(yes='Yes', no='No')=No"<br># This returns the list of ptroject that current $ACCOUNT has access to</pre><p>Note that the definition of orphan depends on your identity (foo.bar@gmail.com), not from an org. Different users perceive different orphans, based on their access to projects. To make sure you see them all, you should iterate through all your accounts (<a href="https://gist.github.com/palladius/a99993feb7e6d78b7a2abea0a10c3242">code snippet</a>).</p><p><strong>Beware!</strong> While Org-ful projects are likely to all belong to you or your team, the Org-less projects could belong to anyone else! I moved by mistakes projects where I was owner but which belonged to another team. Make sure you look at permissions on those projects before migrating. <br>If you’re not the sole owner, consider adding these projects to some <em>“dispute” spreadsheet</em> where you get the ok for the respective owners before moving (or any workflow which works for you).</p><h3>Conclusions</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*q5CwHu_9zctox7RL.png" /><figcaption>Success! I was able to migrate hundreds of projects from one org to another</figcaption></figure><p>A few lesson learnt:</p><ul><li>Being OrgAdmin is not enough, sometimes you need to call the Org Police 👮.</li><li>You need to allowlist the source domain to destination domain, and vice versa. Just like in a network firewall with two untrustworthy parts, which makes sense.</li><li>I believe nobody documented this gcloud approach before (prove me wrong — or maybe not, as I’ve spent 72+ hours doing this😛). Researching the right gcloud filters is sometime a tedious work.</li><li>Nothing helps more than a Migration Spreadsheet.</li><li>Write code which writes <em>and documents</em> code, particularly where you have to do with boring <em>numeric</em> ids.</li><li>Migrating N source orgs into N deterministically-named folders of a target destination org sub-folder was a <strong>really good idea</strong>.; by deterministically I mean: take the org domain and change dots with dashes (since dot are illegal): eg “example-com”.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1KVyCI3Vy9g2nvZJ9gzZbQ.png" /><figcaption>Here’s the migration in a nutshell: all the Source folder structure complexity is flattened in the Destination under a single special folder.</figcaption></figure><p><strong>Please let me know if you find mistakes,</strong> or you find a failure domains I haven’t described that can be <em>gclouded</em> , or any missing note. Thanks in advance. 😙</p><h3>Next Steps</h3><ul><li>open source my code:</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/612/1*HwPOay1JOOHfgash9Mjd3w.png" /></figure><ul><li>recreate the source folder structure in the destination (!)</li><li>Bonus point to copy/preserve IAM and Org Policies across folders structure (although this probably needs some source/destination IAM mapping which is beside the scope of this article).</li><li>Put some code in a GitHub Gist:</li></ul><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3c408b048fe9b49c8ea3a56ab8dfd0d0/href">https://medium.com/media/3c408b048fe9b49c8ea3a56ab8dfd0d0/href</a></iframe><h3>URLography</h3><p>Pages I used to find answers to this:</p><ul><li><a href="http://www.google.com">www.google.com</a></li><li><a href="https://github.com/palladius/org-folder-projects-graph/">https://github.com/palladius/org-folder-projects-graph/</a> Amazing script to dump your org</li><li><a href="https://cloud.google.com/resource-manager/docs/project-migration?hl=it">https://cloud.google.com/resource-manager/docs/project-migration</a> (official). It took me a while to understand there are 6 more pages to navigate on the left (see image below).</li><li><a href="https://cloud.google.com/resource-manager/docs/organization-policy/org-policy-constraints?hl=it">https://cloud.google.com/resource-manager/docs/organization-policy/org-policy-constraints</a> Org policies explained.</li><li><a href="https://medium.com/google-cloud/migrating-a-project-from-one-organization-to-another-gcp-4b37a86dd9e6">https://medium.com/google-cloud/migrating-a-project-from-one-organization-to-another-gcp-4b37a86dd9e6</a> Yogesh wrote the article before me, but no gcloud :)</li><li><a href="https://medium.com/google-cloud/setting-up-google-cloud-identity-for-multinational-companies-4afcbb18dee1">https://medium.com/google-cloud/setting-up-google-cloud-identity-for-multinational-companies-4afcbb18dee1</a> this guy is solving another problem, but he’s using gcloud</li><li><a href="https://stackoverflow.com/questions/73411088/unable-to-migrate-gcp-project">https://stackoverflow.com/questions/73411088/unable-to-migrate-gcp-project</a></li><li><a href="https://stackoverflow.com/questions/61244749/how-to-list-all-projects-in-gcp-that-belongs-to-a-specific-organization">https://stackoverflow.com/questions/61244749/how-to-list-all-projects-in-gcp-that-belongs-to-a-specific-organization</a></li><li><a href="https://stackoverflow.com/questions/72781977/move-project-to-a-different-organization">https://stackoverflow.com/questions/72781977/move-project-to-a-different-organization</a></li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gPA047UcE_wPuQ4pHTefDA.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c7e254ab90af" width="1" height="1" alt=""><hr><p><a href="https://medium.com/google-cloud/how-to-migrate-projects-across-organizations-c7e254ab90af">Migrate ☁️ GCP projects across organizations, with gcloud</a> was originally published in <a href="https://medium.com/google-cloud">Google Cloud - Community</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>
Author
Link
Published date
Image url
Feed url
Guid
Hidden blurb
--- !ruby/object:Feedjira::Parser::RSSEntry title: Migrate ☁️ GCP projects across organizations, with gcloud url: https://medium.com/google-cloud/how-to-migrate-projects-across-organizations-c7e254ab90af?source=rss-b5293b96912f------2 author: Riccardo Carlesso categories: - gcp-security-operations - google-cloud-platform - migration published: 2023-04-18 13:16:26.000000000 Z entry_id: !ruby/object:Feedjira::Parser::GloballyUniqueIdentifier is_perma_link: 'false' guid: https://medium.com/p/c7e254ab90af carlessian_info: news_filer_version: 2 newspaper: Riccardo Carlesso - Medium macro_region: Blogs rss_fields: - title - url - author - categories - published - entry_id - content content: "<p><em>Nel mezzo del cammin di nostra vita, <br>mi ritrovai per una selva oscura, <br>ché la diritta via era smarrita”</em></p><p><em>— </em>Dante Alighieri(*), <a href=\"https://en.wikipedia.org/wiki/Divine_Comedy\">Divine Comedy</a></p><p><em>(*) the Italian version of Shakespeare, just better</em></p><p>Translated for non-\U0001F1EE\U0001F1F9: some day I was encouraged by some external entity to move a lot of projects from 5 of my organizations (<em>source</em>) to another organization (<em>destination</em>).</p><p><strong>TL;DR</strong> If you find this article too long and you want to jump to the code, click on <a href=\"https://gist.github.com/palladius/a99993feb7e6d78b7a2abea0a10c3242\">this </a>\U0001F419\U0001F63A<a href=\"https://gist.github.com/palladius/a99993feb7e6d78b7a2abea0a10c3242\"> gist</a>.</p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/974/1*Hl8tr6N3VrV_AGItbPb25w.jpeg\" /><figcaption>My two boys in front of <a href=\"https://en.wikipedia.org/wiki/The_Gates_of_Hell\">The Gates of Hell</a>, Musée Rodin, Paris, 2022.</figcaption></figure><p>Since I’m the best kind of Software Engineer<em> </em>— the bad coder but <em>lazy</em> kind \U0001F31D — I decided to script all my move and document for posterity. Also showing you the mistakes I found along the way might be helpful (maybe?).</p><p>This is explained in this GCP page(s): <a href=\"https://cloud.google.com/resource-manager/docs/project-migration?hl=en\">https://cloud.google.com/resource-manager/docs/project-migration</a></p><p>Please remember to dump your current IAM config for the 2+ organisations in case you want to recreate the previous config you just changed. Having a local JSON config for your Org is always a good thing to do. \U0001F609 For instance:</p><pre>$ gcloud alpha organizations get-iam-policy $ORG_ID \\<br> - format json > out/org-$DOMAIN-iam.json</pre><p>You can also add specific policies you care about (see below why this is important):</p><pre>$ gcloud resource-manager org-policies describe constraints/iam.allowedPolicyMemberDomains \\<br> --organization $ORG_ID | tee -a out/org-$DOMAIN-policies.log</pre><h3>Know the current state of the world</h3><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/0*FZLcKhtiWl7OqL0w\" /></figure><p>You need to know the state of the world (<em>your </em>world). The best way for me to understand where I stand is this:</p><ul><li>Use this awesome repo <a href=\"https://github.com/palladius/org-folder-projects-graph\">https://github.com/palladius/org-folder-projects-graph</a> I wrote a few years ago. <br><strong>Note</strong>: It launches gcloudonce per folder, it’s super-<strong>inefficient</strong> (uses bash over APIs), but has the nice aspect that it keeps all JSON from gcloud locally. This is a big plus in every case except today: ▶️ after the move you might need a make cache-clean before re-launching it.</li><li>Launch the script for one Org. Sample output:</li></ul><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/477/0*-tHXIe3qGfvvxZfx\" /><figcaption>Output from an Org dump script written by an Italian</figcaption></figure><p>If you don’t care about the folder structure at all, a simpler way is to just invoke something like this:</p><pre># Option 1: Flat structure (simple)<br>$ gcloud projects list - filter \\<br> "parent.id=$ORG_ID AND parent.type=organization"</pre><p>For example:</p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/861/0*zwm-PDduGYEb60X4\" /><figcaption>flat taxonomy from script1</figcaption></figure><p>Note this won’t work if you have a complex hierarchy, which is why you probably want to use my script to get all the ramification of your org.</p><p>You can also play with <a href=\"https://cloud.google.com/asset-inventory/docs/overview\"><strong>GCP Asset Inventory</strong></a> (see great <a href=\"https://stackoverflow.com/questions/61244749/how-to-list-all-projects-in-gcp-that-belongs-to-a-specific-organization\">stackoverflow</a> tip) to get the list <em>without</em> the \U0001F355 pizza slices, but you’d be sadder \U0001F62B , I’m sure.</p><pre># Option 2: Tree structure, with AssetInventory.<br>$ gcloud beta asset search-all-resources \\<br> --asset-types='cloudresourcemanager.googleapis.com/Project' \\<br> --scope="organizations/$ORGID" | egrep '^name:' | cut -d/ -f 5</pre><p>Whatever your workflow, I’m pretty sure you’ll have exceptions, which is why having a spreadsheet (<a href=\"https://spreadsheet.new/\">https://spreadsheet.new/</a> ) will help you big time. I state the source org, destination org, source folder,. source project id, expected destination folder, and notes for my exceptions.</p><p><strong>Please also read this</strong> <a href=\"https://cloud.google.com/resource-manager/docs/handle-special-cases?hl=en\">https://cloud.google.com/resource-manager/docs/handle-special-cases</a> for special cases, like VPC-SC or GCS Bucket lock.</p><p>Let’s now find out the IAM state for your org. This is pretty sweet and took me a while to <a href=\"https://stackoverflow.com/questions/47006062/how-do-i-list-the-roles-associated-with-a-gcp-service-account\">research</a> and adapt:</p><pre># Beautiful query which flattens (multiple roles <=> multiple identities).<br>$ gcloud organizations get-iam-policy "$ORG_ID" \\<br> \ -flatten='bindings[].members' \\<br> -format='table(bindings.role,bindings.members)' | <br> tee "t.org-iam-policy.$ORG_DOMAIN.txt"<br>ROLE MEMBERS<br>[..]<br>roles/billing.admin \ group:gcp-billing-admins@example.org<br>roles/billing.admin user:ricc@example.org<br>roles/billing.creator \ domain:example.org<br>roles/browser group:gcp-billing-admins@example.org<br>roles/cloudasset.owner \ user:palladius@example.org<br>roles/cloudasset.owner user:ricc@example.org<br>roles/orgpolicy.policyAdmin group:gcp-org-administrators@example.org<br>roles/orgpolicy.policyAdmin user:riccardo@example.org<br>roles/resourcemanager.projectCreator user:palladiusbonton@example.org<br>roles/resourcemanager.projectMover user:ricc@example.org</pre><h3>I don’t want to read the docs: Let’s first try brute force!</h3><p>I hate it when someone provides me with a working solution and I apply it and don’t know the mystical secrets that led to it. (<em>I invented the half derivative when I was 16, just so you know — and yes, as you can imagine, it’s useless</em>).</p><p>So let’s try to smash our head over it, one mistake at a time.</p><h3>Error 1 : “The caller does not have permission”</h3><p>Let’s try it, the command is simple — but won’t work the first 27 times you try it. You have my word. But still, try it. I’ll use the same ids as in <a href=\"https://cloud.google.com/resource-manager/docs/configure-org-policy?hl=en\">public docs</a>:</p><pre># Your Org Admin account, and gcloud already working for it:<br>export POWER_ACCOUNT='my-power-account@gmail.com'<br># SRC ORG: 12345678901 source.example.com <br>export SRC_ORG_ID="12345678901"<br>export SRC_ORG_DOMAIN='source.example.com'<br># DST ORG: 45678901234 destination.example.com<br>export DST_ORG_ID="45678901234"<br>export DST_ORG_DOMAIN='destination.example.com'<br><br>$ gcloud beta projects move my-project-in-src-org \\<br> --organization=$DST_ORG_ID --quiet<br><br>- '@type': type.googleapis.com/google.rpc.DebugInfo<br> detail: |-<br> [ORIGINAL ERROR] generic::permission_denied: The caller does not have permission<br> com.google.apps.framework.request.StatusException: <eye3 title='PERMISSION_DENIED'/> generic::PERMISSION_DENIED: The caller does not have permission [google.rpc.error_details_ext] { code: 7 message: "The caller does not have permission" }<br># Computer says no!</pre><p>The most powerful IAM permission you can have on a GCP Organization is organizationAdmin. You’d think that if you have that power, you can do <em>anything</em> in that universe, right? Think twice! There are additional powers that role doesn’t have (and I’m kind of relieved it is this way).<br>One of them is the OrgPolicyAdmin, which means that as OrgAdmin, you first need to give yourself that power before you can use it (not very UNIX, I know, but definitely safer).</p><pre># Set IAM for SrcOrg<br>$ gcloud organizations add-iam-policy-binding $ORG_ID \\<br> --member='user:$ACCOUNT' \\<br> --role='roles/resourcemanager.organizationAdmin' \\<br> --role='roles/resourcemanager.projectIamAdmin' \\<br> --role='roles/resourcemanager.projectMover' \\<br> --role='roles/orgpolicy.policyAdmin' \ \\<br> --condition=None</pre><p>For this command to work, you need the issuer (gcloud config get account) to have the power to grant — but this is beside the scope of this doc.</p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/517/0*GSemn4o3mK4xKGGs\" /><figcaption>In the UI it will look something like this</figcaption></figure><p>Note: Org Policies are documented <a href=\"https://cloud.google.com/resource-manager/docs/organization-policy/org-policy-constraints?hl=it\">here</a>.</p><h4>Error 2: Constraint `constraints/resourcemanager.allowedExportDestinations`</h4><p>Let’s try to migrate a project now:</p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*E7XsdJVJDqFz9u1aka3Gqw.png\" /><figcaption>This is NOT where I would have expected a Input/Output error! \U0001F606</figcaption></figure><p>Next step: Let me configure the <strong>allowedPolicyMemberDomains </strong>policy first.<em> (Why? It failed but I forgot to capture the error, my bad. For this error you just need to trust me).</em></p><pre># This might fail..<br>$ gcloud beta projects move "my-project-in-src-org" \\<br> --organization="$DST_ORG_ID" --quiet<br><br># This sets target customer_id!<br>$ gcloud resource-manager org-policies allow --organization "$SRC_ORG_ID" \\<br> iam.allowedPolicyMemberDomains 'C04abcdef' # gives access to SrcOrg to Dest id</pre><p><strong>Note.</strong> The “C0..” id is the Directory Customer Id you get from gcloud organization list for your destination org.</p><p><em>- I’m ready… Now it’s going to work, I’m sure! <br>- Oh wait… \U0001F648</em></p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/0*Zoc2xlTjUlowvAGn\" /><figcaption>Damn, another error!</figcaption></figure><p>What does it mean? It means that you need to configure your “Org Firewall” to allow projects out of your Source Organization. If you’re a Cisco Certified engineer — you already know what error is coming next.. <strong>Allowlisting palooza </strong>\U0001F61C<strong>!</strong></p><pre># Aloow SRC to export to DST<br>gcloud resource-manager org-policies allow --organization "$SRC_ORG_ID" \\<br> resourcemanager.allowedExportDestinations \\<br> \ "under:organizations/$DST_ORG_ID"<br># Note the "under:" needed to pass the policy all the way down from that folder</pre><h4>Error 3: constraints/resourcemanager.allowedImportSource</h4><p>This is similar to the error above, but it’s from the receiving end.</p><p>To fix: damn I did it on the UI. My apologies. I would presume it’s really the symmetrical opposite, like:</p><pre># This is me guessing but I should be too far. <br># TODO(ricc): remove powers from DST and try again<br>gcloud resource-manager org-policies allow --organization "$DST_ORG_ID" \\<br> resourcemanager.allowedImportSource \\<br> "under:organizations/$SRC_ORG_ID"</pre><h4>[optional] Refining the security</h4><p>Note that to make things simple I gave a single user OrgAdmin for two organizations.</p><ul><li><strong>simple/insecure (pet project).</strong> The simple way is to have a single powerful account (gcloud account, eg “<a href=\"mailto:poweruser@company.com\">poweruser@company.com</a>”) which has access to both SRC and DST orgs. This worked for me, as I was the sole owner of them all and I was under time pressure.</li><li><strong>complex/more secure (enterprises)</strong>. In a more enterprise scenario, you’d have a less pressing time concern and you would probably be obliged to have smaller IAM permissions on both sides, probably having two individuals to carry on these two tasks. Which is why it’s nice to have them gcloud-ed.</li></ul><p>In the second case, follow the <a href=\"https://cloud.google.com/resource-manager/docs/assign-iam-roles?hl=it\">docs</a>:</p><p><strong>Source Org :</strong></p><ul><li><strong>IAM: roles/resourcemanager.projectIamAdmin</strong> in the project to move (but if I want to do it at scale I’ll set it up in the org)</li><li><strong>IAM: roles/resourcemanager.projectMover</strong> in the parent resource.<br>Again, if you’re lazy you can just set these two at Org level and get it done once and for all. But then remember to remove this access at the end of the org move. You can also a time-bound IAM constraint ending tomorrow — this is for lazy people like me who don’t trust themselves to remember to close a parenthesis tomorrow and rather close it as you open it.</li><li><strong>OrgPolicy</strong>: <strong>constraints/resourcemanager.allowedExportDestinations</strong>. You can set the domain id (“C0somethin”) as allowed destination or you can be lazy as me and just set them ALL, depending if you’re an enterprise who wantgs to do it well or you just want to get it done soon and you don’t care much of the org you leave behind.</li></ul><p>On the <strong>destination organization:</strong></p><ul><li><strong>IAM</strong>: ProjectMover</li><li><strong>Org Policy</strong>: constraints/resourcemanager.allowedImportSource</li></ul><h4>Meta-Scripts and tidy migration</h4><p>If you migrate 300 projects from 5 orgs into one, you might want to use Folders or it’ll be a very nasty place tomorrow (<em>“where does this project come from again?”)</em>. You can track pre-migration state in a few ways: you can dump the state on a lot of TXT in local file (but I usually remove them with my ruthless make cleans), on a Google Spreadsheet, or have your business logic do the job for you. Let’s explore this one..</p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*1KVyCI3Vy9g2nvZJ9gzZbQ.png\" /><figcaption>Here’s the migration in a nutshell: all the Source folder structure complexity is flattened in the Destination under a single special folder.</figcaption></figure><p>I had some fun coding in Ruby (in a private repo which is in no shape to be publicized unless you make a lot of noise in the comments \U0001F609).</p><p>Nothing better than a <strong>script</strong> which writes a <strong>script</strong> for you. This is useful when you want to iterate through 10 migrations but you know that ONE project doesnt fit into it -> I create a 10 line blurb and then I just comment out the one I dont need. It’s also good for documenting which commands you ran on local file. Let me show you.</p><pre><br>$ bin/13-gcloud-move-projects-to-joonix-under-domain.rb palladius.eu palladius-eu-infra prova-enonomai-palldius-eu gcpprojects2sheets <br>[...]<br>gcloud beta projects move 'palladius-eu-infra' --folder='1038774614299' --quiet # migrate project given via CLI to proper subfolder of palladius.joonix.net under palladius.eu<br>gcloud beta projects move 'prova-enonomai-palldius-eu' --folder='1038774614299' --quiet # migrate project given via CLI to proper subfolder of palladius.joonix.net under palladius.eu<br>gcloud beta projects move 'gcpprojects2sheets' --folder='1038774614299' --quiet # migrate project given via CLI to proper subfolder of palladius.joonix.net under palladius.eu</pre><p>Note, this commands are dumped into a deterministically-named bash script which contains the commands, so I can comment out the projects I don’t want to move (yet); the deterministic naming means that if I run it again, it will re-apply the same logic to the same file, which is good if you have it in version control and if the logic is not trivial (eg, find all projects under a folder \U0001F609).</p><p>Let’s now call my \U0001F355 pizza software (TM) after a make cache-clean to see if the migration worked:</p><pre>$ git clone https://github.com/palladius/org-folder-projects-graph/ <br>$ cd org-folder-projects-graph/<br>(base) ricc@mbp:\U0001F3E1~/org-folder-projects-graph$ ./recurse_folders.rb palladius.joonix.net<br>[...]<br>├─ \U0001F4C2 133363080569 (Spostamenti CrossOrg)<br> ├─ \U0001F4C2 737267668168 (SPECIAL_ORPHANS)<br> ├─ \U0001F355 cicd-platinum-test041 (713582791007)<br> ├─ \U0001F4C2 1038774614299 (palladius-eu)<br> ├─ \U0001F355 original-bot-383907 (642248137380)<br> ├─ \U0001F355 prova-enonomai-palldius-eu (898096115869)<br> ├─ \U0001F355 gcpprojects2sheets (383820962508)<br> ├─ \U0001F355 palladius-eu-infra (845199147079)<br>[..]<br></pre><p>And it did! A round of applause.</p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/357/0*GIEcWaVhShNnjqI6\" /><figcaption>Plenty of magic here: my script accepts a FolderId ( \U0001F1EE\U0001F1F9 “CrossOrg Movemements”) and it searches for the subfolder named like the domain (creating it if its not found). Wow!</figcaption></figure><p>My script was configured to generate a folder called like the org (changing dot with dash for obvious reasons): 133363080569. My script would create a subfolder if needed, get that folder id and create the scripts for me:</p><pre>gcloud beta projects move 'palladius-eu-infra' - folder='1038774614299' - quiet # migrate project given via CLI to proper subfolder of palladius.joonix.net under palladius.eu<br>gcloud beta projects move 'prova-enonomai-palldius-eu' - folder='1038774614299' - quiet # migrate project given via CLI to proper subfolder of palladius.joonix.net under palladius.eu<br>gcloud beta projects move 'gcpprojects2sheets' - folder='1038774614299' - quiet # migrate project given via CLI to proper subfolder of palladius.joonix.net under palladius.eu</pre><p>After a lot of lost hours debugging, I wrote my bash code-writing ruby script to add meaningful comments aside the command. This was a stroke of genius (for my limited coding abilities) since all this movement includes non-mnemonic numbers, so using the domain names after the comment helped me understand what i was doing.</p><h4>Another error: bad chars in project name (!)</h4><p>Now, I don’t know how many of you will find this error, but if you do, Google is your friend (and mine too) and you’ll find this article, I’m sure \U0001F604</p><p>I had project ids since 2012, and I’ve given them names which included parenthesis. For instance, the project id palladiusbonton-nobilling had an astonishing description (Project Name): “<em>PalladiuBontonTest </em><strong><em>(</em></strong><em>No Billing</em><strong><em>)</em></strong>”. Now this was never a problem in my life until some day… some of them gave me error. Interestingly enough, most came from one single error type: <a href=\"https://www.youtube.com/watch?v=KrZHPOeOxQQ\">I gave my project ids a bad name</a>: I should have known better than Bon Jovi!</p><p>Solution: I scripted in \U0001F48E ruby a project description change which just removes the parenthesis (and the regex is DRY in case I find something new). To be on safe side, I change bad chars into <a href=\"https://www.youtube.com/watch?v=dg9Ae8ACTgw\"><em>empty spaces</em></a> \U0001F642, as I love Queen more than Bon Jovi.</p><pre>$ bin/11-migrate-orphans.rb <br>[..]<br>goliardia2 Goliardia2 (no billing uffi) 576689998257<br>palladiusbonton \ palladiusbonton (billing disabled) 606248867298<br>palladiusbontontest-nobilling \ PalladiuBontonTest (No Billing) 690861376600<br>riccardo-chatroom1 riccardo-chatroom-one (billing disabled) 941891472972<br># EXECUTE this to fix the project: <br>gcloud projects update 'palladiusbonton' --name='palladiusbonton billing disabled ' # Former description was: 'palladiusbonton (billing disabled)'<br>palladiusbontontest-nobilling // 690861376600 ### 'PalladiuBontonTest (No Billing)'<br># EXECUTE this to fix the project: <br>gcloud projects update 'palladiusbontontest-nobilling' --name='PalladiuBontonTest No Billing ' # Former description was: 'PalladiuBontonTest (No Billing)'<br>riccardo-chatroom1 // 941891472972 ### 'riccardo-chatroom-one (billing disabled)'<br># EXECUTE this to fix the project: <br>gcloud projects update 'riccardo-chatroom1' --name='riccardo-chatroom-one billing disabled ' # Former description was: 'riccardo-chatroom-one (billing disabled)'</pre><p>Note the pattern: my 11th script creates an output with (1) the hopefully right command and (2) a human-readable description.</p><p>How do Igcloud it?</p><pre>gcloud projects update 'project-id-or-number' \\<br> --name='New Description without Parenthesis and stuff'</pre><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/0*8d9udHN3wrwHeUv3\" /><figcaption>..and here its how it looks if you have good eyes or monitor.</figcaption></figure><h4>Wait: what about the orphans?</h4><p>I noticed that not all projects were under an organization, some were orphans (ie, not attached to any organization).</p><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/0*C0W-d5Xa_F6c1E63.png\" /><figcaption>A few orphan project ids, stranded in Google Cloud</figcaption></figure><p>Now the <strong>migration</strong> script is the same as per other projects (<a href=\"https://cloud.google.com/sdk/gcloud/reference/beta/projects/move\">documentation</a>):</p><pre>gcloud beta projects move $PROJECT_ID \\<br>--{folder|organization} $DESTINATION_ID</pre><p>However, finding org-less projects require a different search, which took me a while. <a href=\"https://stackoverflow.com/questions/58543614/how-do-i-list-my-gcp-projects-under-no-organization-using-the-resource-manager-a\">Let me Stackoverflow it for you</a>:</p><pre>gcloud projects list --filter="parent.id.yesno(yes='Yes', no='No')=No"<br># This returns the list of ptroject that current $ACCOUNT has access to</pre><p>Note that the definition of orphan depends on your identity (foo.bar@gmail.com), not from an org. Different users perceive different orphans, based on their access to projects. To make sure you see them all, you should iterate through all your accounts (<a href=\"https://gist.github.com/palladius/a99993feb7e6d78b7a2abea0a10c3242\">code snippet</a>).</p><p><strong>Beware!</strong> While Org-ful projects are likely to all belong to you or your team, the Org-less projects could belong to anyone else! I moved by mistakes projects where I was owner but which belonged to another team. Make sure you look at permissions on those projects before migrating. <br>If you’re not the sole owner, consider adding these projects to some <em>“dispute” spreadsheet</em> where you get the ok for the respective owners before moving (or any workflow which works for you).</p><h3>Conclusions</h3><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/0*q5CwHu_9zctox7RL.png\" /><figcaption>Success! I was able to migrate hundreds of projects from one org to another</figcaption></figure><p>A few lesson learnt:</p><ul><li>Being OrgAdmin is not enough, sometimes you need to call the Org Police \U0001F46E.</li><li>You need to allowlist the source domain to destination domain, and vice versa. Just like in a network firewall with two untrustworthy parts, which makes sense.</li><li>I believe nobody documented this gcloud approach before (prove me wrong — or maybe not, as I’ve spent 72+ hours doing this\U0001F61B). Researching the right gcloud filters is sometime a tedious work.</li><li>Nothing helps more than a Migration Spreadsheet.</li><li>Write code which writes <em>and documents</em> code, particularly where you have to do with boring <em>numeric</em> ids.</li><li>Migrating N source orgs into N deterministically-named folders of a target destination org sub-folder was a <strong>really good idea</strong>.; by deterministically I mean: take the org domain and change dots with dashes (since dot are illegal): eg “example-com”.</li></ul><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*1KVyCI3Vy9g2nvZJ9gzZbQ.png\" /><figcaption>Here’s the migration in a nutshell: all the Source folder structure complexity is flattened in the Destination under a single special folder.</figcaption></figure><p><strong>Please let me know if you find mistakes,</strong> or you find a failure domains I haven’t described that can be <em>gclouded</em> , or any missing note. Thanks in advance. \U0001F619</p><h3>Next Steps</h3><ul><li>open source my code:</li></ul><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/612/1*HwPOay1JOOHfgash9Mjd3w.png\" /></figure><ul><li>recreate the source folder structure in the destination (!)</li><li>Bonus point to copy/preserve IAM and Org Policies across folders structure (although this probably needs some source/destination IAM mapping which is beside the scope of this article).</li><li>Put some code in a GitHub Gist:</li></ul><iframe src=\"\" width=\"0\" height=\"0\" frameborder=\"0\" scrolling=\"no\"><a href=\"https://medium.com/media/3c408b048fe9b49c8ea3a56ab8dfd0d0/href\">https://medium.com/media/3c408b048fe9b49c8ea3a56ab8dfd0d0/href</a></iframe><h3>URLography</h3><p>Pages I used to find answers to this:</p><ul><li><a href=\"http://www.google.com\">www.google.com</a></li><li><a href=\"https://github.com/palladius/org-folder-projects-graph/\">https://github.com/palladius/org-folder-projects-graph/</a> Amazing script to dump your org</li><li><a href=\"https://cloud.google.com/resource-manager/docs/project-migration?hl=it\">https://cloud.google.com/resource-manager/docs/project-migration</a> (official). It took me a while to understand there are 6 more pages to navigate on the left (see image below).</li><li><a href=\"https://cloud.google.com/resource-manager/docs/organization-policy/org-policy-constraints?hl=it\">https://cloud.google.com/resource-manager/docs/organization-policy/org-policy-constraints</a> Org policies explained.</li><li><a href=\"https://medium.com/google-cloud/migrating-a-project-from-one-organization-to-another-gcp-4b37a86dd9e6\">https://medium.com/google-cloud/migrating-a-project-from-one-organization-to-another-gcp-4b37a86dd9e6</a> Yogesh wrote the article before me, but no gcloud :)</li><li><a href=\"https://medium.com/google-cloud/setting-up-google-cloud-identity-for-multinational-companies-4afcbb18dee1\">https://medium.com/google-cloud/setting-up-google-cloud-identity-for-multinational-companies-4afcbb18dee1</a> this guy is solving another problem, but he’s using gcloud</li><li><a href=\"https://stackoverflow.com/questions/73411088/unable-to-migrate-gcp-project\">https://stackoverflow.com/questions/73411088/unable-to-migrate-gcp-project</a></li><li><a href=\"https://stackoverflow.com/questions/61244749/how-to-list-all-projects-in-gcp-that-belongs-to-a-specific-organization\">https://stackoverflow.com/questions/61244749/how-to-list-all-projects-in-gcp-that-belongs-to-a-specific-organization</a></li><li><a href=\"https://stackoverflow.com/questions/72781977/move-project-to-a-different-organization\">https://stackoverflow.com/questions/72781977/move-project-to-a-different-organization</a></li></ul><figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*gPA047UcE_wPuQ4pHTefDA.png\" /></figure><img src=\"https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c7e254ab90af\" width=\"1\" height=\"1\" alt=\"\"><hr><p><a href=\"https://medium.com/google-cloud/how-to-migrate-projects-across-organizations-c7e254ab90af\">Migrate ☁️ GCP projects across organizations, with gcloud</a> was originally published in <a href=\"https://medium.com/google-cloud\">Google Cloud - Community</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>"
Language
Active
Ricc internal notes
Imported via /Users/ricc/git/gemini-news-crawler/webapp/db/seeds.d/import-feedjira.rb on 2024-03-31 22:27:09 +0200. Content is EMPTY here. Entried: title,url,author,categories,published,entry_id,content. TODO add Newspaper: filename = /Users/ricc/git/gemini-news-crawler/webapp/db/seeds.d/../../../crawler/out/feedjira/Blogs/Riccardo Carlesso - Medium/2023-04-18-Migrate_☁️_GCP_projects_across_organizations,_with_gcloud-v2.yaml
Ricc source
Show this article
Back to articles