diff --git a/AnsibleJob.ps1 b/AnsibleJob.ps1 index 623b30a..c17d1e4 100644 --- a/AnsibleJob.ps1 +++ b/AnsibleJob.ps1 @@ -1,69 +1,156 @@ function Get-AnsibleJob { [CmdletBinding()] - Param ( + param( [Parameter(ValueFromPipelineByPropertyName=$true)] - [int]$id + [int]$ID ) - if ($id) - { - $Return = Invoke-GetAnsibleInternalJsonResult -ItemType "jobs" -Id $id - } - Else - { - $Return = Invoke-GetAnsibleInternalJsonResult -ItemType "jobs" + if ($ID) { + $result = Invoke-GetAnsibleInternalJsonResult -ItemType "jobs" -Id $ID; + } else { + $result = Invoke-GetAnsibleInternalJsonResult -ItemType "jobs"; } - - if (!($Return)) - { - #Nothing returned from the call - Return + if (!$result) { + # Nothing returned from the call. + return $null; } - $returnobj = @() - foreach ($jsonorg in $return) + $returnObjs = @(); + foreach ($jsonorg in $result) { - #Shift back to json and let newtonsoft parse it to a strongly named object instead - $jsonorgstring = $jsonorg | ConvertTo-Json - $org = $JsonParsers.ParseToJob($jsonorgstring) - $returnobj += $org; $org = $null + # Shift back to json and let newtonsoft parse it to a strongly named object instead. + $jsonorgstring = $jsonorg | ConvertTo-Json; + $org = $JsonParsers.ParseToJob($jsonorgstring); + $returnObjs += $org; + $org = $null; } - #return the things - $returnobj + + # Return the job(s). + $returnObjs; } function Invoke-AnsibleJob { [CmdletBinding()] - Param ( - [Parameter(ValueFromPipelineByPropertyName=$true,Mandatory=$true,ParameterSetName='ByObj')] + param( + [Parameter(ValueFromPipelineByPropertyName=$true,Mandatory=$true,Position=0,ParameterSetName='ByObj')] [AnsibleTower.JobTemplate]$JobTemplate, - [Parameter(ValueFromPipelineByPropertyName=$true,Mandatory=$true,ParameterSetName='ById')] - [int]$id + [Parameter(ValueFromPipelineByPropertyName=$true,Mandatory=$true,Position=0,ParameterSetName='ById')] + [int]$ID ) if ($JobTemplate) { $ThisJobTemplate = $JobTemplate - $id = $ThisJobTemplate.id + $ID = $ThisJobTemplate.id } Else { - $ThisJobTemplate = Get-AnsibleJobTemplate -id $id + $ThisJobTemplate = Get-AnsibleJobTemplate -id $ID; } - if (!$ThisJobTemplate) {Write-Error "No Job template with id $id"; return} + if (!$ThisJobTemplate) { + throw ("Job template with id [{0}] not found" -f $ID); + } - Write-Verbose "Submitting job from template $id" - $result = Invoke-PostAnsibleInternalJsonResult -ItemType "job_templates" -itemId $id -ItemSubItem "jobs" - $JobId = $result.id - Write-Verbose "Starting job with jobid $jobid" - $result = Invoke-PostAnsibleInternalJsonResult -ItemType "jobs" -itemId $JobId -ItemSubItem "start" - $job = get-ansibleJob -id $JobId - $job + Write-Verbose ("Creating job from job template [{0}]" -f $ID); + $result = Invoke-PostAnsibleInternalJsonResult -ItemType "job_templates" -itemId $id -ItemSubItem "jobs"; + $JobID = $result.id; + Write-Verbose ("Starting job with id [{0}]" -f $JobID); + $result = Invoke-PostAnsibleInternalJsonResult -ItemType "jobs" -itemId $JobId -ItemSubItem "start"; + Get-AnsibleJob -ID $JobId } +function Wait-AnsibleJob +{ + <# + .SYNOPSIS + Waits for an Ansible job to finish. + + .DESCRIPTION + Waits for an Ansible job to finish by monitoring the 'finished' property of the job. + Every Interval the job details are requested and while 'finished' is empty the job is considered to be still running. + When the job is finished, the function returns. The caller must analyse the job state and/or result. + Inspect the status, failed and result_output properties for more information on the job result. + + If the Timeout has expired an exception is thrown. + + .PARAMETER Job + The Job object as returned by Get-AnsibleJob or Invoke-AnsibleJobTemplate. + + .PARAMETER ID + The job ID. + + .PARAMETER Timeout + The timeout in seconds to wait for the job to finish. + + .PARAMETER Interval + The interval in seconds at which the job status is inspected. + + .EXAMPLE + $job = Invoke-AnsibleJobTemplate 'Demo Job Template' + Wait-AnsibleJob -ID $job.id + + Starts a new job for job template 'Demo Job Template' and then waits for the job to finish. Inspect the $job properties status, failed and result_stdout for more details. + + .EXAMPLE + $job = Invoke-AnsibleJobTemplate 'Demo Job Template' | Wait-AnsibleJob -Interval 1 + + Starts a new job for job template 'Demo Job Template' and then waits for the job to finish by polling every second. Inspect the $job properties status, failed and result_stdout for more details. + + .EXAMPLE + $job = Invoke-AnsibleJobTemplate 'Demo Job Template' | Wait-AnsibleJob -Interval 5 -Timeout 60 + + Starts a new job for job template 'Demo Job Template' and then waits for the job to finish by polling every 5 seconds. If the job did not finish after 60 seconds, an exception is thrown. + Inspect the $job properties status, failed and result_stdout for more details. + + .OUTPUTS + The job object. + #> + [CmdletBinding(DefaultParameterSetName='Job')] + param( + [Parameter(ValueFromPipelineByPropertyName=$true,Mandatory=$true,Position=0,ParameterSetName='Job')] + [AnsibleTower.Job]$Job, + + [Parameter(ValueFromPipelineByPropertyName=$true,Mandatory=$true,Position=0,ParameterSetName='ID')] + [int]$ID, + + [int]$Timeout = 3600, + [int]$Interval = 3 + ) + + if ($ID) { + $Job = Get-AnsibleJob -id $ID; + if (!$Job) { + throw ("Failed to get job with id [{0}]" -f $ID) + } + } + + Write-Verbose ("Waiting for job [{0}] to finish..." -f $Job.id); + $startDate = Get-Date; + $finished = $false; + while (!$finished) + { + if (![string]::IsNullOrEmpty($Job.finished)) { + Write-Verbose ("Job [{0}] finished." -f $Job.id); + $finished = $true; + } else { + $timeSpan = New-TimeSpan -Start $startDate -End (Get-Date); + Write-Verbose ("Waiting for job [{0}] to finish. Job status is [{1}]. Elapsed time is [{2}] seconds." -f $Job.id,$Job.status,[math]::Round($timeSpan.TotalSeconds)); + if ($timeSpan.TotalSeconds -ge $Timeout) { + throw ("Timeout waiting for job [{0}] to finish" -f $Job.id); + } + + Write-Verbose ("Sleeping [{0}] seconds..." -f $Interval); + sleep -Seconds $Interval + } + $Job = Get-AnsibleJob -id $Job.id; + } + + # Return the job object. + $Job +} diff --git a/AnsibleJobTemplate.ps1 b/AnsibleJobTemplate.ps1 index 86a2a93..081fcdf 100644 --- a/AnsibleJobTemplate.ps1 +++ b/AnsibleJobTemplate.ps1 @@ -1,75 +1,169 @@ -function Get-AnsibleJobTemplate +function Get-AnsibleJobTemplateID { <# - .SYNOPSIS + .SYNOPSIS + Gets the job template ID from a job template name. + + .EXAMPLE + Get-AnsibleJobTemplateID -Name 'Demo Job Template' - Gets one or multiple Job Templates + .EXAMPLE + 'Demo Job Template' | Get-AnsibleJobTemplateID + .OUTPUTS + The job ID. #> + [CmdletBinding()] + param( + [Parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=0)] + [string]$Name + ) + + # We should be passing a search term here, prevents from passing all jobs via the REST call. + (Get-AnsibleJobTemplate | ? { $_.name -eq $Name }).id +} + +function Get-AnsibleJobTemplate +{ + <# + .SYNOPSIS + Gets one or all job templates. + + .EXAMPLE + Get-AnsibleJobTemplate + + Gets all job templates. + + .EXAMPLE + Get-AnsibleJobTemplate | where { $_.project -eq 4 } + + Gets all job templates that belong to project ID 4. + + .EXAMPLE + Get-AnsibleJobTemplate 'Demo Job Template' + + Gets details about job template named 'Demo Job Template'. + + .EXAMPLE + $jobTemplate = Get-AnsibleJobTemplate -ID 5 + + .OUPUTS + Strongly typed job template object(s). + #> + [CmdletBinding(DefaultParameterSetName='Name')] Param ( - [Parameter(ValueFromPipelineByPropertyName=$true)] - [int]$id + [Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=0,ParameterSetName='Name')] + [string]$Name, + + [Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=0,ParameterSetName='ID')] + [int]$ID ) - if ($id) - { - $Return = Invoke-GetAnsibleInternalJsonResult -ItemType "job_templates" -Id $id + if ($Name) { + $ID = Get-AnsibleJobTemplateID -Name $Name + if (!$ID) { + throw ("Failed to get the ID for job template named [{0}]" -f $Name) + } } - Else - { - $Return = Invoke-GetAnsibleInternalJsonResult -ItemType "job_templates" + + if ($ID) { + $return = Invoke-GetAnsibleInternalJsonResult -ItemType "job_templates" -Id $ID + } else { + $return = Invoke-GetAnsibleInternalJsonResult -ItemType "job_templates" } - - if (!($Return)) + if (!$return) { - #Nothing returned from the call - Return + # Nothing returned from the call + return } - $returnobj = @() + + $returnObjs = @() foreach ($jsonorg in $return) { - #Shift back to json and let newtonsoft parse it to a strongly named object instead + # Shift back to json and let newtonsoft parse it to a strongly named object instead $jsonorgstring = $jsonorg | ConvertTo-Json $org = $JsonParsers.ParseToJobTemplate($jsonorgstring) - $returnobj += $org; $org = $null + $returnObjs += $org; + $org = $null } #return the things - $returnobj + $returnObjs } function Invoke-AnsibleJobTemplate { <# - .SYNOPSIS + .SYNOPSIS + Runs an Ansible job template. + + .PARAMETER Name + Name of the Ansible job template. - Invokes an Ansible Job Template + .PARAMETER ID + ID of the Ansible job template. - .EXAMPLE + .PARAMETER Data + Any additional data to be supplied to Tower in order to run the job template. Most common is "extra_vars". + Supply a normal Powershell hash table. It will be converted to JSON. See the examples for more information. - Connect-AnsibleTower -Credential (get-credential) -TowerUrl "https://mytower" -DisableCertificateVerification - $jobtemplate = get-ansibleJobTemplate -id 5 - $jobtemplate | Invoke-AnsibleJobTemplate + .EXAMPLE + Invoke-AnsibleJobTemplate -Name 'Demo Job Template' + + Runs a job for job template named 'Demo Job Template'. + + .EXAMPLE + $job = Invoke-AnsibleJobTemplate -ID 5 + + Runs a job for job template with ID 5. + + .EXAMPLE + $jobTemplateData = @{ + "extra_vars" = @{ + 'var1' = 'value1'; + 'var2' = 'value2'; + }; + } + $job = Invoke-AnsibleJobTemplate -Name 'My Ansible Job Template' -Data $jobTemplateData + Launches job template named 'My Ansible Job Template' and passes extra variables for the job to run with. + + .OUTPUTS + Strongly typed job object. #> - [CmdletBinding()] + [CmdletBinding(DefaultParameterSetName='Name')] Param ( - [Parameter(ValueFromPipelineByPropertyName=$true,Mandatory=$true)] - [int]$id - ) + [Parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=0,ParameterSetName='Name')] + [string]$Name, - $ThisJobTemplate = Get-AnsibleJobTemplate -id $id + [Parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=0,ParameterSetName='ID')] + [int]$ID, - if (!$ThisJobTemplate) {Write-Error "No Job template with id $id"; return} - - $result = Invoke-PostAnsibleInternalJsonResult -ItemType "job_templates" -itemId $id -ItemSubItem "jobs" - $JobId = $result.id - $job = get-ansibleJob -id $JobId - $job -} + [Object]$Data + ) + if ($Name) { + $ID = Get-AnsibleJobTemplateID -Name $Name + if (!$ID) { + throw ("Failed to get the ID for job template named [{0}]" -f $Name) + } + } + $params = @{ + ItemType = 'job_templates'; + itemId = $ID; + ItemSubItem = 'launch'; + }; + if ($Data) { + $params.Add('InputObject', $Data); + } + $result = Invoke-PostAnsibleInternalJsonResult @params; + if (!$result -and !$result.id) { + throw ("Failed to start job for job template ID [{0}]" -f $ID); + } + Get-AnsibleJob -id $result.id +} diff --git a/AnsibleTower.psd1 b/AnsibleTower.psd1 index 7af3ea8..0a6c41b 100644 Binary files a/AnsibleTower.psd1 and b/AnsibleTower.psd1 differ diff --git a/AnsibleTower.psm1 b/AnsibleTower.psm1 index 858a32c..e9f1095 100644 --- a/AnsibleTower.psm1 +++ b/AnsibleTower.psm1 @@ -1,210 +1,415 @@ -$AnsibleUrl = $null -$AnsibleCredential = $null +$script:AnsibleUrl = $null; +$script:TowerApiUrl = $null; +$script:AnsibleCredential = $null; +$script:AnsibleResourceUrlCache = @{}; +$script:AnsibleUseBasicAuth = $false; +$script:AnsibleBasicAuthHeaders = $null; + +# Load Json +$NewtonSoftJsonPath = join-path $PSScriptRoot "AnsibleTowerClasses\AnsibleTower\AnsibleTower\bin\Release\Newtonsoft.Json.dll" +Add-Type -Path $NewtonSoftJsonPath + +# Compile the .net classes +$ClassPath = Join-Path $PSScriptRoot "AnsibleTowerClasses\AnsibleTower\AnsibleTower\DataTypes.cs" +$Code = Get-Content -Path $ClassPath -Raw +Add-Type -TypeDefinition $Code -ReferencedAssemblies $NewtonSoftJsonPath + +# Load the json parsers to have it handy whenever. +$JsonParsers = New-Object AnsibleTower.JsonFunctions +#D ot-source/Load the other powershell scripts +Get-ChildItem "*.ps1" -path $PSScriptRoot | where {$_.Name -notmatch "test"} | ForEach-Object { . $_.FullName } -#Load Json -$NewtonSoftJsonPath = join-path $PSScriptRoot "AnsibleTowerClasses\AnsibleTower\AnsibleTower\bin\Debug\Newtonsoft.Json.dll" -add-type -Path $NewtonSoftJsonPath +function Disable-CertificateVerification +{ + <# + .SYNOPSIS + Disables Certificate verification. Use this when using Tower with 'troublesome' certificates, such as self-signed. + #> -#Compile the .net classes -$ClassPath = Join-Path $PSScriptRoot "AnsibleTowerClasses\AnsibleTower\AnsibleTower\DataTypes.cs" -#$ClassPath2 = Join-Path $PSScriptRoot "AnsibleTower (c# project)\AnsibleTower\AnsibleTower\JsonParsers.cs" -$Code = Get-Content -Path $ClassPath -Raw -#$Code2 = Get-Content -Path $ClassPath2 -Raw + # Danm you here-strings for messing up my indendation!! + Add-Type @" + using System.Net; + using System.Security.Cryptography.X509Certificates; + + public class NoSSLCheckPolicy : ICertificatePolicy { + public NoSSLCheckPolicy() {} + public bool CheckValidationResult( + ServicePoint sPoint, X509Certificate cert, + WebRequest wRequest, int certProb) { + return true; + } + } +"@ + [System.Net.ServicePointManager]::CertificatePolicy = new-object NoSSLCheckPolicy +} -add-type -TypeDefinition $Code -ReferencedAssemblies $NewtonSoftJsonPath +function Join-AnsibleUrl +{ + <# + .SYNOPSIS + Joins url parts together into a valid Tower url. -#Load the json parsers to have it handy whenever. -$JsonParsers = New-Object AnsibleTower.JsonFunctions + .PARAMETER Parts + Url parts that will be joined together. + .EXAMPLE + Join-AnsibleUrl 'https://tower.domain.com','api','v1','job_templates' -#Dot-source/Load the other powershell scripts -Get-ChildItem "*.ps1" -path $PSScriptRoot | where {$_.Name -notmatch "test"} | ForEach-Object { . $_.FullName } + .OUTPUTS + Combined url with a trailing slash. + #> + param( + [string[]]$Parts + ) + + return (($Parts | ? { $_ } | % { $_.trim('/').trim() } | ? { $_ } ) -join '/') + '/'; +} -Function Invoke-GetAnsibleInternalJsonResult +function Get-AnsibleResourceUrl { - Param ($AnsibleUrl=$AnsibleUrl, - [System.Management.Automation.PSCredential]$Credential=$AnsibleCredential, - $ItemType, - $Id, - $ItemSubItem) + <# + .SYNOPSIS + Gets the url part for a Tower API resource of function. + + .PARAMETER Resource + The resource name to get the API url for. + + .EXAMPLE + Get-AnsibleResourceUrl 'job_templates' + Returns: "/api/v1/job_templates/" + + .OUTPUTS + API url part for the specified resource, e.g. "/api/v1/job_templates/" + #> + param( + [Parameter(Mandatory=$true)] + [string]$Resource + ) - if ((!$AnsibleUrl) -or (!$Credential)) + $cachedUrl = $script:AnsibleResourceUrlCache[$Resource]; + if ($cachedUrl) { + return $cachedUrl; + } + + $args = @{ + Uri = $script:TowerApiUrl; + }; + if ($script:AnsibleUseBasicAuth) { - throw "You need to connect first, use Connect-AnsibleTower" + Write-Verbose "Get-AnsibleResourceUrl: Using Basic Authentication"; + $args.Add('Headers',$script:AnsibleBasicAuthHeaders); } - $Result = Invoke-RestMethod -Uri ($AnsibleUrl + "/api/v1/") -Credential $Credential - $ItemApiUrl = $result.$ItemType - if ($id) + else { - $ItemApiUrl += "$id/" + Write-Verbose "Get-AnsibleResourceUrl: Using detected Authentication"; + $args.Add('Credential',$script:AnsibleCredential); + } + $result = Invoke-RestMethod @args; + if (!$result) { + throw "Failed to access the Tower api list"; + } + if (!$result.$Resource) { + throw ("Failed to find the url for resource [{0}]" -f $Resource); } - if ($ItemSubItem) - { - $ItemApiUrl = $ItemApiUrl + "/$ItemSubItem/" + $script:AnsibleResourceUrlCache.Add($Resource,$result.$Resource); + + return $result.$Resource; +} + +function Invoke-GetAnsibleInternalJsonResult +{ + param( + [Parameter(Mandatory=$true)] + $ItemType, + + $Id, + $ItemSubItem + ) + + if (!$script:AnsibleUrl -and (!$script:AnsibleCredential -or !$script:AnsibleBasicAuthHeaders)) { + throw "You need to connect first, use Connect-AnsibleTower"; + } + + $ItemApiUrl = Get-AnsibleResourceUrl $ItemType + + if ($id) { + $ItemApiUrl = Join-AnsibleUrl $ItemApiUrl, $id } - $ItemApiUrl = $ItemApiUrl.Replace("//","/") + if ($ItemSubItem) { + $ItemApiUrl = Join-AnsibleUrl $ItemApiUrl, $ItemSubItem + } - $invokeresult = Invoke-RestMethod -Uri ($ansibleurl + $ItemApiUrl) -Credential $Credential + $params = @{ + 'Uri' = (Join-AnsibleUrl $script:AnsibleUrl,$ItemApiUrl); + 'ErrorAction' = 'Stop'; + } + if ($id -eq $null -and $ItemSubItem -eq $null) { + Write-Verbose "Appending ?page_size=1000 to url"; + $params.Uri += '?page_size=1000'; + } - if ($InvokeResult.id) + if ($script:AnsibleUseBasicAuth) { - return $InvokeResult + Write-Verbose "Invoke-GetAnsibleInternalJsonResult: Using Basic Authentication"; + $params.Add('Headers',$script:AnsibleBasicAuthHeaders); } - Elseif ($InvokeResult.results) + else { - return $InvokeResult.results + Write-Verbose "Invoke-GetAnsibleInternalJsonResult: Using detected Authentication"; + $params.Add('Credential',$script:AnsibleCredential); + } + Write-Verbose ("Invoke-GetAnsibleInternalJsonResult: Invoking url [{0}]" -f $params.Uri); + $invokeResult = Invoke-RestMethod @params; + if ($invokeResult.id) { + return $invokeResult + } + Elseif ($invokeResult.results) { + return $invokeResult.results } - } Function Invoke-PostAnsibleInternalJsonResult { - Param ( - $AnsibleUrl=$AnsibleUrl, - [System.Management.Automation.PSCredential]$Credential=$AnsibleCredential, + param( + [Parameter(Mandatory=$true)] $ItemType, + $itemId, $ItemSubItem, - $InputObject) + $InputObject + ) - if ((!$AnsibleUrl) -or (!$Credential)) - { - throw "You need to connect first, use Connect-AnsibleTower" + if (!$script:AnsibleUrl -and (!$script:AnsibleCredential -or !$script:AnsibleBasicAuthHeaders)) { + throw "You need to connect first, use Connect-AnsibleTower"; } - $Result = Invoke-RestMethod -Uri ($AnsibleUrl + "/api/v1/") -Credential $Credential - $ItemApiUrl = $result.$ItemType - - if ($itemId) - { - $ItemApiUrl = $ItemApiUrl + "$itemId" + + $ItemApiUrl = Get-AnsibleResourceUrl $ItemType + + if ($itemId) { + $ItemApiUrl = Join-AnsibleUrl $ItemApiUrl, $itemId } - if ($ItemSubItem) - { - $ItemApiUrl = $ItemApiUrl + "/$ItemSubItem/" + if ($ItemSubItem) { + $ItemApiUrl = Join-AnsibleUrl $ItemApiUrl, $ItemSubItem } - $Params = @{ - "uri"=($ansibleurl + $ItemApiUrl); - "Credential"=$Credential; - } - if ($InputObject) - { + $params = @{ + 'Uri' = Join-AnsibleUrl $script:AnsibleUrl, $ItemApiUrl; + 'Method' = 'Post'; + 'ContentType' = 'application/json'; + 'ErrorAction' = 'Stop'; + } + if ($InputObject) { $params.Add("Body",($InputObject | ConvertTo-Json -Depth 99)) } - Write-Debug "Credentials are: $($credential.UserName)" - Write-Debug "Invoking call with body: $($InputObject | ConvertTo-Json -Depth 99)" - $invokeresult += Invoke-RestMethod -Method Post -ContentType "application/json" @params - $invokeresult - + if ($script:AnsibleUseBasicAuth) + { + Write-Verbose "Invoke-GetAnsibleInternalJsonResult: Using Basic Authentication"; + $params.Add('Headers',$script:AnsibleBasicAuthHeaders); + } + else + { + Write-Verbose "Invoke-GetAnsibleInternalJsonResult: Using detected Authentication"; + $params.Add('Credential',$script:AnsibleCredential); + } + Write-Verbose ("Invoke-PostAnsibleInternalJsonResult: Invoking url [{0}]" -f $params.Uri); + return Invoke-RestMethod @params } Function Invoke-PutAnsibleInternalJsonResult { Param ( - $AnsibleUrl=$AnsibleUrl, - [System.Management.Automation.PSCredential]$Credential=$AnsibleCredential, - $ItemType,$InputObject + $ItemType, + $InputObject ) - if ((!$AnsibleUrl) -or (!$Credential)) + if (!$script:AnsibleUrl -and (!$script:AnsibleCredential -or !$script:AnsibleBasicAuthHeaders)) { + throw "You need to connect first, use Connect-AnsibleTower"; + } + $ItemApiUrl = Get-AnsibleResourceUrl $ItemType + + $id = $InputObject.id + + $ItemApiUrl = Join-AnsibleUrl $ItemApiUrl, $id + + $params = @{ + 'Uri' = Join-AnsibleUrl $script:AnsibleUrl, $ItemApiUrl; + 'Method' = 'Put'; + 'ContentType' = 'application/json'; + 'Body' = ($InputObject | ConvertTo-Json -Depth 99); + 'ErrorAction' = 'Stop'; + } + + if ($script:AnsibleUseBasicAuth) { - throw "You need to connect first, use Connect-AnsibleTower" + Write-Verbose "Invoke-GetAnsibleInternalJsonResult: Using Basic Authentication"; + $params.Add('Headers',$script:AnsibleBasicAuthHeaders); } - $Result = Invoke-RestMethod -Uri ($AnsibleUrl + "/api/v1/") -Credential $Credential - $ItemApiUrl = $result.$ItemType - if (!($id)) + else { - Write-Error "I couldnt find the id of that object" - return + Write-Verbose "Invoke-GetAnsibleInternalJsonResult: Using detected Authentication"; + $params.Add('Credential',$script:AnsibleCredential); } - $id = $InputObject.id + Write-Verbose ("Invoke-PutAnsibleInternalJsonResult: Invoking url [{0}]" -f $params.Uri); + return Invoke-RestMethod @params; +} - $ItemApiUrl += "$id/" +function Connect-AnsibleTower +{ + <# + .SYNOPSIS + Connects to the Tower API and returns the user details. + .PARAMETER Credential + Credential to authenticate with at the Tower API. - $invokeresult += Invoke-RestMethod -Uri ($ansibleurl + $ItemApiUrl) -Credential $Credential -Method Put -Body ($InputObject | ConvertTo-Json -Depth 99) -ContentType "application/json" - $invokeresult + .PARAMETER UserName + Username for connecting to AnsibleTower. -} + .PARAMETER Password + Password of type SecureString for the UserName. -Function Connect-AnsibleTower -{ - Param ( + .PARAMETER PlainPassword + Password in plain text for the UserName. + + .PARAMETER TowerUrl + Url of the Tower host, e.g. https://ansible.mydomain.local + + .PARAMETER DisableCertificateVerification + Disables Certificate verification. Use when Tower responds with 'troublesome' certificates, such as self-signed. + + .PARAMETER BasicAuth + Forces the AnsibleTower module to use Basic authentication when communicating with AnsibleTower. + + .EXAMPLE + Connect-AnsibleTower -Credential (Get-Credential) -TowerUrl 'https://ansible.domain.local' + + User is prompted for credentials and then connects to the Tower host at 'https://ansible.domain.local'. User details are displayed in the output. + + .EXAMPLE + $me = Connect-AnsibleTower -Credential $myCredential -TowerUrl 'https://ansible.domain.local' -DisableCertificateVerification + + Connects to the Tower host at 'https://ansible.domain.local' using the credential supplied in $myCredential. Any certificate errors are ignored. + User details beloning to the specified credential are in the $me variable. + + .EXAMPLE + $me = Connect-AnsibleTower -UserName srvcAsnible -Password $securePassword -BasicAuth -TowerUrl 'https://ansible.domain.local' + + Connects to the Tower host at 'https://ansible.domain.local' using the specified UserName and Password. The username and password are send with each request to force basic authentication. + #> + param ( + [Parameter(Mandatory=$true, ParameterSetName="Credential")] [System.Management.Automation.PSCredential]$Credential, + + [Parameter(Mandatory=$true, ParameterSetName="SecurePassword")] + [Parameter(ParameterSetName="PlainPassword")] + [string]$UserName, + [Parameter(Mandatory=$true, ParameterSetName="PlainPassword")] + [string]$PlainPassword, + [Parameter(Mandatory=$true, ParameterSetName="SecurePassword")] + [securestring]$Password, + + [Parameter(Mandatory=$true)] [string]$TowerUrl, + + [Switch]$BasicAuth, + [Switch]$DisableCertificateVerification ) if ($DisableCertificateVerification) { - #Danm you, here-strings for messing up my indendation!! - add-type @" - using System.Net; - using System.Security.Cryptography.X509Certificates; - - public class NoSSLCheckPolicy : ICertificatePolicy { - public NoSSLCheckPolicy() {} - public bool CheckValidationResult( - ServicePoint sPoint, X509Certificate cert, - WebRequest wRequest, int certProb) { - return true; - } - } -"@ - [System.Net.ServicePointManager]::CertificatePolicy = new-object NoSSLCheckPolicy + Disable-CertificateVerification; } - #Try and figure out what address we were given - - if ($TowerUrl -match "/api/") - { + if ($TowerUrl -match "/api") { throw "Specify the URL without the /api part" } - Else - { - $TestUrl = $TowerUrl + "/api/" - } - - #$towerurl = $TowerUrl.Replace("//","/") try { + [System.Uri]$uri = $TowerUrl; + if ($uri.Scheme -eq "https") + { + Write-Verbose "TowerURL scheme is https. Enforcing TLS 1.2"; + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; + } + Write-Verbose "Determining current Tower API version url..." + $TestUrl = Join-AnsibleUrl $TowerUrl, 'api' + Write-Verbose "TestUrl=$TestUrl" $result = Invoke-RestMethod -Uri $TestUrl -ErrorAction Stop + if (!$result.current_version) { + throw "Could not determine current version of Tower API"; + } + $TowerApiUrl = Join-AnsibleUrl $TowerUrl, $result.current_version } catch { - Throw "That didn't work at all" + throw ("Could not connect to Tower api url: " + $_.Exception.Message); } - - #Get the version - $TowerVersion = $result.current_version - $TowerApiUrl = $TowerUrl + $TowerVersion - #Try to log on - $MeUri = $TowerApiUrl + "me/" try { - $MeResult = Invoke-RestMethod -Uri $MeUri -Credential $Credential -ErrorAction Stop + switch($PsCmdlet.ParameterSetName) + { + "Credential" { + Write-Verbose "Extracting username and password from credential"; + $UserName = $Credential.UserName; + $PlainPassword = $Credential.GetNetworkCredential().Password; + } + "PlainPassword" { + Write-Verbose "Constructing credential object from UserName and PlainPassword"; + $Credential = New-Object System.Management.Automation.PSCredential ($UserName, (ConvertTo-SecureString $PlainPassword -AsPlainText -Force)); + } + "SecurePassword" { + Write-Verbose "Constructing credential object from UserName and SecurePassword"; + $Credential = New-Object System.Management.Automation.PSCredential ($UserName, $Password); + $PlainPassword = $Credential.GetNetworkCredential().Password; + } + } + + $params = @{ + Uri = Join-AnsibleUrl $TowerApiUrl, 'me'; + ErrorAction = 'Stop'; + }; + + if ($BasicAuth.IsPresent) + { + Write-Verbose "Constructing headers for Basic Authentication."; + $encodedCreds = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes("$($UserName):$($PlainPassword)")); + $headers = @{ + Authorization = "Basic $encodedCreds" + } + $script:AnsibleUseBasicAuth = $true; + $params.Add('Headers', $headers); + } + else { + Write-Verbose "Using credential for authentication." + $params.Add('Credential', $Credential); + } + + Write-Verbose "Connecting to AnsibleTower, requesting the /me function." + $meResult = Invoke-RestMethod @params; + if (!$meResult -or !$meResult.results) { + throw "Could not authenticate to Tower"; + } + $me = $JsonParsers.ParseToUser((ConvertTo-Json ($meResult.results | select -First 1))); + Write-Verbose "Connection to AnsibleTower successful." } Catch { - throw "Could not authenticate" + throw "Could not authenticate: " + $_.Exception.Message; } - - #Code for error-handling goes here - #If we got this far, we could connect. Go ahead and get a session ticket - - #Set the global connection var - #$MDwebapiurl = $WebApiUrl - set-variable -Name AnsibleUrl -Value $TowerUrl -Scope 1 - - set-variable -Name AnsibleCredential -Value $Credential -Scope 1 - -} + # Connection and login success. + $script:AnsibleUrl = $TowerUrl; + $script:TowerApiUrl = $TowerApiUrl; + $script:AnsibleCredential = $Credential; + $script:AnsibleBasicAuthHeaders = $headers; + return $me; +} diff --git a/AnsibleTowerClasses/AnsibleTower/AnsibleTower/DataTypes.cs b/AnsibleTowerClasses/AnsibleTower/AnsibleTower/DataTypes.cs index ba91429..c56e5ff 100644 --- a/AnsibleTowerClasses/AnsibleTower/AnsibleTower/DataTypes.cs +++ b/AnsibleTowerClasses/AnsibleTower/AnsibleTower/DataTypes.cs @@ -1,10 +1,6 @@ -using System; +using Newtonsoft.Json; +using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Newtonsoft.Json; - namespace AnsibleTower { @@ -23,18 +19,19 @@ public class Organization public class User { - - public int id { get; set; } - public string type { get; set; } - public string url { get; set; } - public string created { get; set; } - public string username { get; set; } - public string first_name { get; set; } - public string last_name { get; set; } - public string email { get; set; } - public bool is_superuser { get; set; } - public string ldap_dn { get; set; } - + public int id { get; set; } + public string type { get; set; } + public string url { get; set; } + //public string created { get; set; } + public string username { get; set; } + public string first_name { get; set; } + public string last_name { get; set; } + public string email { get; set; } + public bool is_superuser { get; set; } + public bool is_system_auditor { get; set; } + public string ldap_dn { get; set; } + public string external_account { get; set; } + public DateTime? created { get; set; } } public class Project @@ -89,9 +86,16 @@ public class JobTemplate public class Job { public int id { get; set; } + public string type { get; set; } + public string url { get; set; } public string name { get; set; } public string description { get; set; } - //public object unified_job_template { get; set; } + public int unified_job_template { get; set; } + public string launch_type { get; set; } + public string status { get; set; } + public bool failed { get; set; } + public double elapsed { get; set; } + public string job_explanation { get; set; } public string job_type { get; set; } public object inventory { get; set; } public object project { get; set; } @@ -103,7 +107,8 @@ public class Job public int verbosity { get; set; } public string extra_vars { get; set; } public string job_tags { get; set; } - //public object job_template { get; set; } + public int job_template { get; set; } + public string result_stdout { get; set; } public DateTime? started { get; set; } public DateTime? finished { get; set; } } @@ -216,8 +221,5 @@ public AnsibleTower.Group ParseToGroup(string JsonString) AnsibleTower.Group ConvertedObject = JsonConvert.DeserializeObject(JsonString); return ConvertedObject; } - } - - } diff --git a/AnsibleTowerClasses/AnsibleTower/AnsibleTower/bin/Debug/AnsibleTower.dll b/AnsibleTowerClasses/AnsibleTower/AnsibleTower/bin/Debug/AnsibleTower.dll index 3fc439f..a3d4367 100644 Binary files a/AnsibleTowerClasses/AnsibleTower/AnsibleTower/bin/Debug/AnsibleTower.dll and b/AnsibleTowerClasses/AnsibleTower/AnsibleTower/bin/Debug/AnsibleTower.dll differ