PSSemaphore.psm1
function Connect-Semaphore { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [ValidatePattern("^(https?://[\w\.-]+)")] [String]$Url, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.Management.Automation.PSCredential]$Credential ) try { # Add a trailing slash if it's not there: if($Url[-1] -ne '/') { $Url += '/' } $APIBaseEndPoint = $Url + 'api' # Set a script scoped variable containing the host URL (and any other required data), to be used by all calls within the module: $Script:Config = [PSCustomObject]@{ url = $APIBaseEndPoint } } catch { throw $_ } Write-Verbose -Message "Logging into $Url as $($Credential.UserName)" try { # Construct the body of the request for logging in: $Body = @{ 'auth' = $Credential.UserName 'password' = $Credential.GetNetworkCredential().Password } | ConvertTo-Json -Compress # Make the call to login, storing the session in a script scoped variable to be used by all calls within the module: Invoke-RestMethod -Uri "$($Script:Config.url)/auth/login" -Method Post -Body $Body -ContentType 'application/json' -SessionVariable Script:Session | Out-Null } catch { throw $_ } } function Disable-SemaphoreUserToken { [CmdletBinding(SupportsShouldProcess)] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '', Justification = 'Does not alter system state.')] param ( [Parameter(Mandatory = $true)] [String] $TokenId ) begin { Write-Verbose -Message "Calling function $($MyInvocation.MyCommand)" if(!$Script:Session) { throw "Please run Connect-Semaphore first" } } process { # Encode the token: $TokenId = [System.Web.HttpUtility]::UrlEncode($TokenId) try { Invoke-RestMethod -Uri "$($Script:Config.url)/user/tokens/$TokenId" -Method Delete -ContentType 'application/json' -WebSession $Script:Session } catch { throw $_ } } end { } } function Get-SemaphoreProject { [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [string] $Name ) begin { Write-Verbose -Message "Calling function $($MyInvocation.MyCommand)" if(!$Script:Session) { throw "Please run Connect-Semaphore first" } } process { Write-Verbose -Message "Getting projects" try { $Data = Invoke-RestMethod -Uri "$($Script:Config.url)/projects" -Method Get -ContentType 'application/json' -WebSession $Script:Session if($Name) { $Data = $Data | Where-Object { $_.name -eq $Name } } $Data } catch { throw $_ } } end { } } function Get-SemaphoreProjectEnvironment { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateRange(1, [int]::MaxValue)] [int] $ProjectId, [Parameter(Mandatory = $false)] [string] $Name ) begin { Write-Verbose -Message "Calling function $($MyInvocation.MyCommand)" if(!$Script:Session) { throw "Please run Connect-Semaphore first" } } process { Write-Verbose -Message "Getting environment(s) for project $ProjectId" try { $Data = Invoke-RestMethod -Uri "$($Script:Config.url)/project/$ProjectId/environment" -Method Get -ContentType 'application/json' -WebSession $Script:Session if($Name) { $Data = $Data | Where-Object { $_.name -eq $Name } } $Data } catch { throw $_ } } end { } } function Get-SemaphoreProjectInventory { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateRange(1, [int]::MaxValue)] [int] $ProjectId, [Parameter(Mandatory = $false)] [String] $Name ) begin { Write-Verbose -Message "Calling function $($MyInvocation.MyCommand)" if(!$Script:Session) { throw "Please run Connect-Semaphore first" } } process { try { $Data = Invoke-RestMethod -Uri "$($Script:Config.url)/project/$ProjectId/inventory" -Method Get -ContentType 'application/json' -WebSession $Script:Session if($Name) { $Data = $Data | Where-Object { $_.name -eq $Name } } $Data } catch { throw $_ } } end { } } function Get-SemaphoreProjectKey { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateRange(1, [int]::MaxValue)] [int] $ProjectId, [Parameter(Mandatory = $false)] [String] $Name ) begin { Write-Verbose -Message "Calling function $($MyInvocation.MyCommand)" if(!$Script:Session) { throw "Please run Connect-Semaphore first" } } process { try { $Data = Invoke-RestMethod -Uri "$($Script:Config.url)/project/$ProjectId/keys" -Method Get -ContentType 'application/json' -WebSession $Script:Session if($Name) { $Data = $Data | Where-Object { $_.name -eq $Name } } $Data } catch { throw $_ } } end { } } function Get-SemaphoreProjectRepository { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateRange(1, [int]::MaxValue)] [int] $ProjectId, [Parameter(Mandatory = $false)] [String] $Name ) begin { Write-Verbose -Message "Calling function $($MyInvocation.MyCommand)" if(!$Script:Session) { throw "Please run Connect-Semaphore first" } } process { try { $Data = Invoke-RestMethod -Uri "$($Script:Config.url)/project/$ProjectId/repositories" -Method Get -ContentType 'application/json' -WebSession $Script:Session if($Name) { $Data = $Data | Where-Object { $_.name -eq $Name } } $Data } catch { throw $_ } } end { } } function Get-SemaphoreProjectTask { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateRange(1, [int]::MaxValue)] [int] $ProjectId, [Parameter(Mandatory = $false)] [ValidateRange(1, [int]::MaxValue)] $Id, [Parameter(Mandatory = $false)] [ValidateRange(1, [int]::MaxValue)] $TemplateId ) begin { Write-Verbose -Message "Calling function $($MyInvocation.MyCommand)" if(!$Script:Session) { throw "Please run Connect-Semaphore first" } } process { try { $Data = Invoke-RestMethod -Uri "$($Script:Config.url)/project/$ProjectId/tasks/$Id" -Method Get -ContentType 'application/json' -WebSession $Script:Session # E.g. if we only want the tasks for a specific template (note this will only apply if we are getting all tasks for a project) if($TemplateId) { $Data = $Data | Where-Object { $_.template_id -eq $TemplateId } } $Data } catch { throw $_ } } end { } } function Get-SemaphoreProjectTaskOutput { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateRange(1, [int]::MaxValue)] [int] $ProjectId, [Parameter(Mandatory = $false)] [ValidateRange(1, [int]::MaxValue)] [int] $Id, [Parameter(Mandatory = $false)] [ValidateSet('json', 'text')] [string] $ParseType = 'json' ) begin { Write-Verbose -Message "Calling function $($MyInvocation.MyCommand)" if(!$Script:Session) { throw "Please run Connect-Semaphore first" } } process { try { $Data = Invoke-RestMethod -Uri "$($Script:Config.url)/project/$ProjectId/tasks/$Id/output" -Method Get -ContentType 'application/json' -WebSession $Script:Session if(!$Data) { return $Null } # Write all data to the verbose stream so we can see it if we want to: Write-Verbose -Message $($Global:Data | Out-String) } catch { throw $_ } if($ParseType -eq 'json') { # The output, when ansible.cfg is set to return JSON is as followed: <# task_id task time output ------- ---- ---- ------ 25 17/10/2023 13:47:53 Task 25 added to queue 25 17/10/2023 13:47:58 Started: 25 25 17/10/2023 13:47:58 Run TaskRunner with template: Test Install Via Choco on TESTHOST 25 17/10/2023 13:47:58 Preparing: 25 25 17/10/2023 13:47:58 Updating Repository https://github.com/temp/ansibleplaybooks.git 25 17/10/2023 13:47:58 From https://github.com/temp/ansibleplaybooks 25 17/10/2023 13:47:58 * branch main -> FETCH_HEAD 25 17/10/2023 13:47:58 Already up to date. 25 17/10/2023 13:47:58 No collections/requirements.yml file found. Skip galaxy install process. 25 17/10/2023 13:47:58 No roles/requirements.yml file found. Skip galaxy install process. 25 17/10/2023 13:48:45 { 25 17/10/2023 13:48:45 "custom_stats": {}, 25 17/10/2023 13:48:45 "global_custom_stats": {}, 25 17/10/2023 13:48:45 "plays": [ 25 17/10/2023 13:48:45 { ..................................... SNIP ..................................... 25 17/10/2023 13:48:45 } 25 17/10/2023 13:48:45 ], 25 17/10/2023 13:48:45 "stats": { 25 17/10/2023 13:48:45 "testhost.domain.com": { 25 17/10/2023 13:48:45 "changed": 1, 25 17/10/2023 13:48:45 "failures": 0, 25 17/10/2023 13:48:45 "ignored": 0, 25 17/10/2023 13:48:45 "ok": 6, 25 17/10/2023 13:48:45 "rescued": 0, 25 17/10/2023 13:48:45 "skipped": 0, 25 17/10/2023 13:48:45 "unreachable": 0 25 17/10/2023 13:48:45 } 25 17/10/2023 13:48:45 } 25 17/10/2023 13:48:45 } #> #Region Find Start and End of JSON try { # Find the array number where .output equals exactly "{" as this is the start of the JSON data: $JSONStart = $Data.Output.IndexOf('{') # If -1 then we have no result data yet. if($JSONStart -eq -1) { return $Null } # Not sure why but LastIndexOf('}') returns an array. Let's use IndexOf('}') instead. This works as the JSON is returned 'pretty' with indentation so the final line is just }. $JSONEnd = $Data.Output.IndexOf('}') # If this function is called at exactly the right (wrong) time, it can be possible that the { is found but the } is not. This is because the task data is one line per record # and presumably behind the scenes, data is still being written to disk and thus we end up with a partial result. # To cater to this, if we've found '{' but '}' isn't a value above 0, use a small retry loop making additional queries for the task data. $RetryCount = 0 while(($JSONEnd -lt 0) -and $RetryCount -lt 20) { $JSONEnd = $Data.Output.IndexOf('}') $Data = Invoke-RestMethod -Uri "$($Script:Config.url)/project/$ProjectId/tasks/$Id/output" -Method Get -ContentType 'application/json' -WebSession $Script:Session -Verbose:$False $RetryCount++ Start-Sleep -Seconds 2 } if($JSONEnd -eq -1) { throw "Unable to find end of JSON data." } # We are assuming these are integers here... if($JSONStart -and $JSONEnd) { # Add all items in the array between the start and end to a new array: $Global:JSON = $Data.Output[$JSONStart..$JSONEnd] } else { return $Null } } catch { throw $_ } #EndRegion #Region Convert to a PowerShell object and hope it doesn't break: try { $Converted = $JSON | ConvertFrom-Json -ErrorAction Stop } catch { throw $_ } #EndRegion #Region Manipulate data to make it more useful: # Unfortunately, .stats is converted to singular PSCustomObject (count = 1!) with the host names as NoteProperties and their value is another PSCustomObject # with multiple properties (failures, changed, ok ,etc). This means you can't iterate over it to use with Where/Select statements. So, convert it into a useful array of objects: return $Converted.stats | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name | ForEach-Object { [pscustomobject]@{'Name' = $_; 'Results' = $Converted.stats.$_ } } #EndRegion } elseif($ParseType -eq 'text') { return $Data } else { } } end { } } function Get-SemaphoreProjectTemplate { [CmdletBinding(DefaultParameterSetName = 'Default')] param ( [Parameter(Mandatory = $true)] [ValidateRange(1, [int]::MaxValue)] [int] $ProjectId, [Parameter(Mandatory = $true, ParameterSetName = "Id")] [ValidateRange(1, [int]::MaxValue)] $Id, [Parameter(Mandatory = $true, ParameterSetName = "Name")] [String] $Name ) begin { Write-Verbose -Message "Calling function $($MyInvocation.MyCommand)" if(!$Script:Session) { throw "Please run Connect-Semaphore first" } } process { try { $Data = Invoke-RestMethod -Uri "$($Script:Config.url)/project/$ProjectId/templates/$Id" -Method Get -ContentType 'application/json' -WebSession $Script:Session if($Name) { $Data = $Data | Where-Object { $_.name -eq $Name } } $Data } catch { throw $_ } } end { } } function Get-SemaphoreUserToken { [CmdletBinding(SupportsShouldProcess)] param ( ) begin { Write-Verbose -Message "Calling function $($MyInvocation.MyCommand)" if(!$Script:Session) { throw "Please run Connect-Semaphore first" } } process { try { Invoke-RestMethod -Uri "$($Script:Config.url)/user/tokens" -Method Get -ContentType 'application/json' -WebSession $Script:Session } catch { throw $_ } } end { } } function New-SemaphoreProject { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [String]$Name, [Parameter(Mandatory = $false)] [Switch]$Alert, [Parameter(Mandatory = $false)] [String]$TelegramChatId, [Parameter(Mandatory = $false)] [ValidateRange(0, [int]::MaxValue)] [Int]$MaxParallelTasks ) begin { Write-Verbose -Message "Calling function $($MyInvocation.MyCommand)" if(!$Script:Session) { throw "Please run Connect-Semaphore first" } } process { #Region Check If Exists # Check if already exists by name. Whilst permitted in Semaphore, it's impossible to tell them apart when using them in Task Templates. $CheckIfExists = Get-SemaphoreProject -Name $Name if($CheckIfExists) { throw "An project with the name $Name already exists in project $ProjectId. Please use a different name." } #EndRegion #Region Construct body and send the request try { $Body = @{ name = $Name } if($Alert) { $Body.Add("alert", $true) } if($TelegramChatId) { $Body.Add("telegram_chat_id", $TelegramChatId) } if($MaxParallelTasks) { $Body.Add("max_parallel_tasks", $MaxParallelTasks) } $Body = $Body | ConvertTo-Json -Compress Invoke-RestMethod -Uri "$($Script:Config.url)/projects" -Method Post -Body $Body -ContentType 'application/json' -WebSession $Script:Session } catch { throw $_ } #EndRegion } end { } } function New-SemaphoreProjectEnvironment { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateRange(1, [int]::MaxValue)] [int] $ProjectId, [Parameter(Mandatory = $true)] [String]$Name ) begin { Write-Verbose -Message "Calling function $($MyInvocation.MyCommand)" if(!$Script:Session) { throw "Please run Connect-Semaphore first" } } process { #Region Check If Exists # Check if already exists by name. Whilst permitted in Semaphore, it's impossible to tell them apart when using them in Task Templates. $CheckIfExists = Get-SemaphoreProjectEnvironment -ProjectId $ProjectId -Name $Name if($CheckIfExists) { throw "An environment with the name $Name already exists in project $ProjectId. Please use a different name." } #EndRegion #Region Construct body and send the request try { $Body = @{ json = "{}" name = $Name project_id = $ProjectId } | ConvertTo-Json -Compress Invoke-RestMethod -Uri "$($Script:Config.url)/project/$ProjectId/environment" -Method Post -Body $Body -ContentType 'application/json' -WebSession $Script:Session | Out-Null # Return the created object: Get-SemaphoreProjectEnvironment -ProjectId $ProjectId -Name $Name } catch { throw $_ } #EndRegion } end { } } function New-SemaphoreProjectInventory { [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateRange(1, [int]::MaxValue)] [int] $ProjectId, [Parameter(Mandatory = $true)] [ValidateRange(1, [int]::MaxValue)] [int] $KeyId, [Parameter(Mandatory = $true)] [string] $Name, [Parameter(Mandatory = $true, ParameterSetName = 'Static')] [string[]]$Hostnames, [Parameter(Mandatory = $false, ParameterSetName = 'Static')] [Switch]$WinRMConnection, [Parameter(Mandatory = $true, ParameterSetName = 'File')] [string]$InventoryFile ) begin { Write-Verbose -Message "Calling function $($MyInvocation.MyCommand)" if(!$Script:Session) { throw "Please run Connect-Semaphore first" } } process { #Region Check If Exists # Check if already exists by name. Whilst permitted in Semaphore, it's impossible to tell them apart when using them in Task Templates. $CheckIfExists = Get-SemaphoreProjectInventory -ProjectId $ProjectId -Name $Name if($CheckIfExists) { throw "An inventory with the name $Name already exists in project $ProjectId. Please use a different name." } #EndRegion #Region Construct body and send the request try { $Body = @{ "name" = $Name.ToLower() "project_id" = $ProjectId "ssh_key_id" = $KeyId "become_key_id" = $KeyId } if($Hostnames) { $InventoryData = "[$Name]`n" + ($Hostnames -join "`n") if($WinRMConnection) { $InventoryData += "`n`n" $InventoryData += "[$($Name):vars]`n" $InventoryData += "ansible_connection=winrm`n" $InventoryData += "ansible_winrm_transport=ntlm`n" $InventoryData += "ansible_winrm_server_cert_validation=ignore`n" } $Body.Add("inventory", $InventoryData) $Body.Add("type", "static") } elseif($InventoryFile) { $Body.Add("inventory", $InventoryFile) $Body.Add("type", "file") } $Body = $Body | ConvertTo-Json -Compress if($PSCmdlet.ShouldProcess("Project $ProjectId", "Create inventory $Name")) { Invoke-RestMethod -Uri "$($Script:Config.url)/project/$ProjectId/inventory" -Method Post -Body $Body -ContentType 'application/json' -WebSession $Script:Session # Return the created object| EDIT: No need because it actually returns the object... #Get-SemaphoreProjectInventory -ProjectId $ProjectId -Name $Name } } catch { throw $_ } #EndRegion } end { } } function New-SemaphoreProjectKey { [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'EmptyCredentials')] param ( [Parameter(Mandatory = $true)] [ValidateRange(1, [int]::MaxValue)] [int] $ProjectId, [Parameter(Mandatory = $true)] [string] $Name, [Parameter(Mandatory = $true, ParameterSetName = 'Credentials')] [ValidateSet('UserNamePassword', 'SSHKey', 'Empty')] [String] $Type, [Parameter(Mandatory = $true, ParameterSetName = 'Credentials')] [pscredential]$Credential ) begin { Write-Verbose -Message "Calling function $($MyInvocation.MyCommand)" if(!$Script:Session) { throw "Please run Connect-Semaphore first" } } process { #Region Check If Exists # Check if already exists by name. Whilst permitted in Semaphore, it's impossible to tell them apart when using them in Task Templates. $CheckIfExists = Get-SemaphoreProjectKey -ProjectId $ProjectId -Name $Name if($CheckIfExists) { throw "A key with the name $Name already exists in project $ProjectId. Please use a different name." } #EndRegion #Region Construct body and send the request try { $Body = @{ "name" = $Name "project_id" = $ProjectId } # If the parameter set is Credentials: if($PSCmdlet.ParameterSetName -eq 'Credentials') { # Append the appropriate key type and credentials: if($Type -eq 'UserNamePassword') { $Body.Add("type", "login_password") $Body.Add("login_password", @{ "login" = $Credential.UserName "password" = $Credential.GetNetworkCredential().Password }) } elseif($Type -eq 'SSHKey') { $Body.Add("type", "ssh") $Body.Add("ssh", @{ "private_key" = $Credential.GetNetworkCredential().Password "login" = $Credential.UserName }) } } else { # If the default parameter set is EmptyCredentials, so set the type to none: $Body.Add("type", "none") } $Body = $Body | ConvertTo-Json -Compress if($PSCmdlet.ShouldProcess("Project $ProjectId", "Create key $Name")) { Invoke-RestMethod -Uri "$($Script:Config.url)/project/$ProjectId/keys" -Method Post -Body $Body -ContentType 'application/json' -WebSession $Script:Session | Out-Null # Return the created object: Get-SemaphoreProjectKey -ProjectId $ProjectId -Name $Name } else { Write-Verbose -Message "Would create key $Name in project $ProjectId" } } catch { throw $_ } #EndRegion } end { } } function New-SemaphoreProjectRepository { [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateRange(1, [int]::MaxValue)] [int] $ProjectId, [Parameter(Mandatory = $true)] [String]$Name, [Parameter(Mandatory = $true)] [String]$Url, [Parameter(Mandatory = $true)] [String]$Branch, [Parameter(Mandatory = $true)] [ValidateRange(1, [int]::MaxValue)] [Int]$KeyId ) begin { Write-Verbose -Message "Calling function $($MyInvocation.MyCommand)" if(!$Script:Session) { throw "Please run Connect-Semaphore first" } } process { #Region Check If Exists # Check if already exists by name. Whilst permitted in Semaphore, it's impossible to tell them apart when using them in Task Templates. $CheckIfExists = Get-SemaphoreProjectRepository -ProjectId $ProjectId -Name $Name if($CheckIfExists) { throw "A repository with the name $Name already exists in project $ProjectId. Please use a different name." } #EndRegion #Region Construct body and send the request try { $Body = [Ordered]@{ name = $Name git_url = $Url git_branch = $Branch ssh_key_id = $KeyId project_id = $ProjectId } | ConvertTo-Json -Compress Invoke-RestMethod -Uri "$($Script:Config.url)/project/$ProjectId/repositories" -Method Post -Body $Body -ContentType 'application/json' -WebSession $Script:Session | Out-Null # Return the created object: Get-SemaphoreProjectRepository -ProjectId $ProjectId -Name $Name } catch { throw $_ } #EndRegion } end { } } function New-SemaphoreProjectTemplate { [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateRange(1, [int]::MaxValue)] [int] $ProjectId, [Parameter(Mandatory = $true)] [ValidateRange(1, [int]::MaxValue)] [int] $InventoryId, [Parameter(Mandatory = $true)] [ValidateRange(1, [int]::MaxValue)] [int] $RepositoryId, [Parameter(Mandatory = $true)] [ValidateRange(1, [int]::MaxValue)] [int] $EnvironmentId, [Parameter(Mandatory = $true)] [ValidateRange(1, [int]::MaxValue)] [int] $KeyId, [Parameter(Mandatory = $true)] [String]$Playbook, [Parameter(Mandatory = $true)] [String]$Name, [Parameter(Mandatory = $false)] [String]$Description = 'Inventory created by New-SemaphoreProjectTemplate' ) begin { Write-Verbose -Message "Calling function $($MyInvocation.MyCommand)" if(!$Script:Session) { throw "Please run Connect-Semaphore first" } } process { <# { "project_id": 1, "inventory_id": 1, "repository_id": 1, "environment_id": 1, "view_id": 1, "name": "Test", "playbook": "test.yml", "arguments": "[]", "description": "Hello, World!", "": false, "limit": "", "suppress_success_alerts": true, "survey_vars": [ { "name": "string", "title": "string", "description": "string", "type": "String => \"\", Integer => \"int\"", "required": true } ] } #> #Region Construct body and send the request try { $Body = @{ 'type' = '' 'name' = $Name 'description' = $Description 'playbook' = $Playbook 'inventory_id' = $InventoryId 'repository_id' = $RepositoryId 'environment_id' = $EnvironmentId 'vault_key_id' = $KeyId 'project_id' = $ProjectId 'suppress_success_alerts' = $SuppressSuccessAlerts 'allow_override_args_in_task' = $AllowOverrideArgsInTask } | ConvertTo-Json Invoke-RestMethod -Uri "$($Script:Config.url)/project/$ProjectId/templates" -Method Post -Body $Body -ContentType 'application/json' -WebSession $Script:Session | Out-Null # Return the created object: Get-SemaphoreProjectTemplate -ProjectId $ProjectId -Name $Name } catch { throw $_ } #EndRegion } end { } } function New-SemaphoreUserToken { [CmdletBinding(SupportsShouldProcess)] param ( ) begin { Write-Verbose -Message "Calling function $($MyInvocation.MyCommand)" if(!$Script:Session) { throw "Please run Connect-Semaphore first" } } process { #Region Send the request try { Invoke-RestMethod -Uri "$($Script:Config.url)/user/tokens" -Method Post -ContentType 'application/json' -WebSession $Script:Session } catch { throw $_ } #EndRegion } end { } } function Remove-SemaphoreProject { [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateRange(1, [int]::MaxValue)] [int] $Id ) begin { Write-Verbose -Message "Calling function $($MyInvocation.MyCommand)" if(!$Script:Session) { throw "Please run Connect-Semaphore first" } } process { #Region Send the request to remove try { if($PSCmdlet.ShouldProcess("Project $ProjectId", "Remove $Id")) { Invoke-RestMethod -Uri "$($Script:Config.url)/project/$Id" -Method Delete -WebSession $Script:Session | Out-Null } } catch { throw $_ } #EndRegion } end { } } function Remove-SemaphoreProjectEnvironment { [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateRange(1, [int]::MaxValue)] [int] $ProjectId, [Parameter(Mandatory = $true)] [ValidateRange(1, [int]::MaxValue)] [int] $Id ) begin { Write-Verbose -Message "Calling function $($MyInvocation.MyCommand)" if(!$Script:Session) { throw "Please run Connect-Semaphore first" } } process { #Region Send the request to remove try { if($PSCmdlet.ShouldProcess("Project $ProjectId", "Remove $Id")) { Invoke-RestMethod -Uri "$($Script:Config.url)/project/$ProjectId/environment/$Id" -Method Delete -WebSession $Script:Session | Out-Null } } catch { throw $_ } #EndRegion } end { } } function Remove-SemaphoreProjectInventory { [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateRange(1, [int]::MaxValue)] [int] $ProjectId, [Parameter(Mandatory = $true)] [ValidateRange(1, [int]::MaxValue)] [int] $Id ) begin { Write-Verbose -Message "Calling function $($MyInvocation.MyCommand)" if(!$Script:Session) { throw "Please run Connect-Semaphore first" } } process { #Region Send the request to remove try { if($PSCmdlet.ShouldProcess("Project $ProjectId", "Remove $Id")) { Invoke-RestMethod -Uri "$($Script:Config.url)/project/$ProjectId/inventory/$Id" -Method Delete -WebSession $Script:Session | Out-Null } } catch { throw $_ } #EndRegion } end { } } function Remove-SemaphoreProjectKey { [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateRange(1, [int]::MaxValue)] [int] $ProjectId, [Parameter(Mandatory = $true)] [ValidateRange(1, [int]::MaxValue)] [int] $Id ) begin { Write-Verbose -Message "Calling function $($MyInvocation.MyCommand)" if(!$Script:Session) { throw "Please run Connect-Semaphore first" } } process { #Region Send the request to remove try { if($PSCmdlet.ShouldProcess("Project $ProjectId", "Remove $Id")) { Invoke-RestMethod -Uri "$($Script:Config.url)/project/$ProjectId/keys/$Id" -Method Delete -WebSession $Script:Session | Out-Null } } catch { throw $_ } #EndRegion } end { } } function Remove-SemaphoreProjectRepository { [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateRange(1, [int]::MaxValue)] [int] $ProjectId, [Parameter(Mandatory = $true)] [ValidateRange(1, [int]::MaxValue)] [int] $Id ) begin { Write-Verbose -Message "Calling function $($MyInvocation.MyCommand)" if(!$Script:Session) { throw "Please run Connect-Semaphore first" } } process { #Region Send the request to remove try { if($PSCmdlet.ShouldProcess("Project $ProjectId", "Remove $Id")) { Invoke-RestMethod -Uri "$($Script:Config.url)/project/$ProjectId/repositories/$Id" -Method Delete -WebSession $Script:Session | Out-Null } } catch { throw $_ } #EndRegion } end { } } function Remove-SemaphoreProjectTemplate { [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateRange(1, [int]::MaxValue)] [int] $ProjectId, [Parameter(Mandatory = $true)] [ValidateRange(1, [int]::MaxValue)] [int] $Id ) begin { Write-Verbose -Message "Calling function $($MyInvocation.MyCommand)" if(!$Script:Session) { throw "Please run Connect-Semaphore first" } } process { #Region Send the request to remove try { if($PSCmdlet.ShouldProcess("Project $ProjectId", "Remove $Id")) { Invoke-RestMethod -Uri "$($Script:Config.url)/project/$ProjectId/templates/$Id" -Method Delete -WebSession $Script:Session | Out-Null } } catch { throw $_ } #EndRegion } end { } } function Start-SemaphoreProjectTask { [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [ValidateRange(1, [int]::MaxValue)] [int] $ProjectId, [Parameter(Mandatory = $true)] [ValidateRange(1, [int]::MaxValue)] [int] $TemplateId, [Parameter(Mandatory = $false)] [String]$CLIArguments, [Parameter(Mandatory = $false)] [Switch] $Wait ) begin { Write-Verbose -Message "Calling function $($MyInvocation.MyCommand)" if(!$Script:Session) { throw "Please run Connect-Semaphore first" } } process { $Body = @{ "template_id" = $TemplateId "environment" = "{}" "project_id" = $ProjectId } if($CLIArguments) { $Body.Add("cli_arguments", $CLIArguments) } try { $Data = Invoke-RestMethod -Uri "$($Script:Config.url)/project/$ProjectId/tasks" -Method Post -Body $Body -ContentType 'application/json' -WebSession $Script:Session if(!$Data) { return $Null } } catch { throw $_ } if(!$Wait) { return $Data } else { # Start a loop that calls Get-SemaphoreProjectTask with the Id returned from the previous call. If the status property is running or success # break out of the loop and return the task object. Attempt the loop for a maximum of 50 attempts with 5 seconds wait between each attempt. $AttemptCount = 0 $MaxAttempts = 50 $WaitTime = 5 $TaskId = $Data.id do { $AttemptCount++ Write-Verbose -Message "Attempt $AttemptCount of $MaxAttempts" Write-Progress -Activity "Waiting for task to complete" -Status "Attempt $AttemptCount of $MaxAttempts" -PercentComplete (($AttemptCount / $MaxAttempts) * 100) try { $Task = Get-SemaphoreProjectTask -ProjectId $ProjectId -TaskId $TaskId if($Task.status -eq "running") { Write-Verbose -Message "Task is running" Start-Sleep -Seconds $WaitTime } elseif($Task.status -eq "waiting") { Write-Verbose -Message "Task is waiting" Start-Sleep -Seconds $WaitTime } else { Write-Verbose -Message "Task status is: $($Task.status)" break } } catch { throw $_ } } until($AttemptCount -eq $MaxAttempts) } return $Task } end { } } |