Although vCenter Orchestrator does offer an upgrade path for their appliances there are times where I have found myself needing to deploy a new one and migrate all of my configuration and workflows to it. vCO has some great built in workflows that can configure itself, but nothing that really deals with workflows. Sure, you can export and import workflows one at time using the client, which may be ok if you have 10 workflows, but what if you have 20, 40, 100 that you need to migrate. That could get pretty monotonous.
The shear energy of a mouse click.
That’s right – who wants to exert all that energy of clicking on the mouse to get these workflows migrated when we can just orchestrate or automate it – after all, this is vCenter Orchestrator we are talking about. vCO has a REST plugin that allows us to create workflows around any application that offers one, but did you know that vCO also has it’s own REST API available for us to use? So that’s where I started with my task of migrating workflows and by the time I got to writing this post it truly ended up being a community effort.
“Steal from the best”
This was a quote that I seen on one of Alan Renouf’s slides during a VMworld presentation on PowerCLI. “Steal from the best”, “Don’t re-invent the wheel” – sayings that have resonated with me for my entire career – Why re-do something if it has already been done. So when I set out on this small project I ended up using two main sources; This post by William Lam on VirtuallyGhetto on how to use curl to export a single vCO workflow and this forum reply by igaydajiev who “showed me the light” on how to import a workflow back in! Without these two guys I wouldn’t have been able to do any of this.
Enough already let’s get to it!
So I chose to go the PowerShell route to accomplish this as I’m not too familiar with the REST plugin for vCO. As well, I am targeting only a certain category – so basically what the script does is take in the following parameters
- OldvCOServer – the old vCO appliance
- OldvCOUser/OldvCOPass – credentials for the old appliance
- OldvCOCategory – Category Name to export workflows from
- TempFolder – Location to store the exported workflows
- NewvCOServer – The new vCO appliance
- NewvCOUser/NewvCOPass – credentials for the new appliance
- NewvCOCategory – Category name on new server where you would like the worfkflows imported.
As far as an explanation I’ll just let you follow the code and figure it out. It’s basically broke into two different sections; the export and the import. During the import routine there is a little bit of crazy wonky code that gets the ID of the targeted category. This is the only way I could figure out how to get it and I’m sure there is a way more efficient way of doing so, but for now, this will have to do. Anyways, the script is shown below and is downloadable here.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
Param( [string]$oldvCOServer="localhost", [string]$oldvCOUser="vcoadmin", [string]$oldvCOPass="vcoadmin", [string]$oldvCOCategory="MyOldWorkflows", [string]$newvCOServer="localhost", [string]$newvCOUser="vcoadmin", [string]$newvCOPass="vcoadmin", [string]$newvCOCategory="MyWorkflows", [string]$TempFolder ) # vCO Port $vcoPort="8281" # Type to handle self-signed certificates add-type @" using System.Net; using System.Security.Cryptography.X509Certificates; public class TrustAllCertsPolicy : ICertificatePolicy { public bool CheckValidationResult( ServicePoint srvPoint, X509Certificate certificate, WebRequest request, int certificateProblem) { return true; } } "@ [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy [byte[]]$CRLF = 13, 10 function Get-AsciiBytes([String] $str) { return [System.Text.Encoding]::ASCII.GetBytes($str) } function ConvertTo-Base64($string) { $bytes = [System.Text.Encoding]::UTF8.GetBytes($string); $encoded = [System.Convert]::ToBase64String($bytes); return $encoded; } clear ############## EXPORT OLD WORKFLOWS ############################## Write-Host "Beginning Export Routine" -ForegroundColor Black -BackgroundColor Yellow Write-Host "" # build uri $uri = "https://"+$oldvCOServer+":"+$vCOPort+"/vco/api/workflows/?conditions=categoryName="+$oldvCOCategory # Authentication token or old server $token = ConvertTo-Base64("$($oldvCOUser):$($oldvCOPass)"); $auth = "Basic $($token)"; $header = @{"Authorization"= $auth; }; #execute API call $workflows = Invoke-RestMethod -URi $uri -Method Get -ContentType "application/xml" -Headers $header Write-Host "Exporting $($workflows.total) workflows from $oldvCOCategory" Write-Host "-------------------------------------------------------------------------------------------" #loop through each workflow and export to TempFolder foreach ($href in $workflows.link.href) { #retrieve information about the specific workflow $header = @{"Authorization"= $auth; }; $workflow = Invoke-RestMethod -URi $href -Method Get -ContentType "application/xml" -Headers $header #replace all spaces in workflow name $workflowname = [System.Text.RegularExpressions.Regex]::Replace($($workflow.name),"[^1-9a-zA-Z_]","") $filename = $TempFolder + $workflowname + ".workflow" # setup new header $header = @{"Authorization"= $auth; "Accept"="application/zip"; }; Write-Host "Exporting $($workflow.name) to $filename - " -NoNewline Invoke-RestMethod -URi $href -Method Get -ContentType "application/xml" -Headers $header -OutFile $filename Write-Host "Done" -ForegroundColor Green } Write-Host "" Write-Host "Export Routine Complete" -ForegroundColor Black -BackgroundColor Yellow ################################################################## ############## IMPORT WORKFLOWS ############################## Write-Host "" Write-Host "" Write-Host "Import Routines to new server" -ForegroundColor Black -BackgroundColor Yellow Write-Host "" #Generate auth for new vCO Server $token = ConvertTo-Base64("$($newvCOUser):$($newVCOPass)"); $auth = "Basic $($token)"; #Get Category ID $header = @{"Authorization"= $auth; }; $uri = "https://"+$newvCOServer+":"+$vCOPort+"/vco/api/categories/" $categories = Invoke-RestMethod -URi $uri -Method Get -Headers $header -ContentType "application/xml" foreach ($att in $categories.link) { if ($att.attributes.value -eq "HPEDSB") { foreach ($newatt in $att.attributes ) { if ($newatt.name -eq "id") { $categoryID = $newatt.value } } } } $impUrl = "https://$($newvCOServer):$($vcoPort)/vco/api/workflows?categoryId=$($categoryId)&overwrite=true"; $header = @{"Authorization"= $auth; "Accept"= "application/zip"; "Accept-Encoding"= "gzip,deflate,sdch";}; $workflows = Get-ChildItem $TempFolder -Filter *.workflow Write-Host "Importing $($workflows.count) workflows to $newvCOCategory" Write-Host "-------------------------------------------------------------------------------------------" foreach ($workflow in $workflows) { Write-Host "Importing $($workflow.name) - " -NoNewline $body = New-Object System.IO.MemoryStream $boundary = [Guid]::NewGuid().ToString().Replace('-','') $ContentType = 'multipart/form-data; boundary=' + $boundary $b2 = Get-AsciiBytes ('--' + $boundary) $body.Write($b2, 0, $b2.Length) $body.Write($CRLF, 0, $CRLF.Length) $b = (Get-AsciiBytes ('Content-Disposition: form-data; name="categoryId"')) $body.Write($b, 0, $b.Length) $body.Write($CRLF, 0, $CRLF.Length) $body.Write($CRLF, 0, $CRLF.Length) $b = (Get-AsciiBytes $categoryId) $body.Write($b, 0, $b.Length) $body.Write($CRLF, 0, $CRLF.Length) $body.Write($b2, 0, $b2.Length) $body.Write($CRLF, 0, $CRLF.Length) $b = (Get-AsciiBytes ('Content-Disposition: form-data; name="file"; filename="$($workflow.Name)";')) $body.Write($b, 0, $b.Length) $body.Write($CRLF, 0, $CRLF.Length) $b = (Get-AsciiBytes 'Content-Type:application/octet-stream') $body.Write($b, 0, $b.Length) $body.Write($CRLF, 0, $CRLF.Length) $body.Write($CRLF, 0, $CRLF.Length) $b = [System.IO.File]::ReadAllBytes($workflow.FullName) $body.Write($b, 0, $b.Length) $body.Write($CRLF, 0, $CRLF.Length) $body.Write($b2, 0, $b2.Length) $b = (Get-AsciiBytes '--'); $body.Write($b, 0, $b.Length); $body.Write($CRLF, 0, $CRLF.Length); $header = @{"Authorization"= $auth; "Accept"= "application/zip"; "Accept-Encoding"= "gzip,deflate,sdch";}; Invoke-RestMethod -Method POST -Uri $impUrl -ContentType $ContentType -Body $body.ToArray() -Headers $header Write-Host "Done" -foregroundcolor Green } Write-Host "" Write-Host "Import Routine complete" -ForegroundColor Black -BackgroundColor Yellow ################################################################## |
Just want to be sure that readers also realize that workflows do not need to be a 1×1 basis for export/import… instead, the preferred, recommended method for getting workflows from one server to another is to use a vCO Package π Benefit is that not only do you get the workflows, but also their dependent Configuration Elements, Actions, Resources, etc….
Steps:
1) Create Package, Add workflow folders(s) with solution(s) – dependencies automatically added.
2) Export Package to file
3) Import to target vCO server
How reliable/compatible is this? I have imported VSO 3.x (That’s not a typo, Dunes product was VSO) into current vCO 5.5.1 servers and all versions in between π
– Burke
Thanks Burke! That solution sounds way easier π As you can tell I’m still in the early stages of my experiences with vCO. It’s great to see such a helpful community within a community revolving around this one app.
Oh, and Great write-up !!! It’s always great to see creative ways to have systems/apps/etc call vCO workflows to do something productive π