From 783b501327de043fa7c00cd5adbafe87391615b0 Mon Sep 17 00:00:00 2001 From: Mark Culhane Date: Mon, 11 Nov 2024 19:39:37 +1100 Subject: [PATCH 01/11] prepping docker runner --- .../Updating-Confluence-Pages.md | 1 + ...RCosm-SharePointViewer-Template.confluence | 24 +++ .../AtlassianPowerKit-Jira.psd1 | 2 + .../AtlassianPowerKit-Jira.psm1 | 165 +++++++++++++++++- .../AtlassianPowerKit-Shared.psm1 | 50 +++--- AtlassianPowerKit.psm1 | 52 ++---- Dockerfile | 22 +++ README.md | 13 ++ Run.ps1 | 26 +++ build_and_push.ps1 | 1 + osm_home/tmp | 1 + 11 files changed, 297 insertions(+), 60 deletions(-) create mode 100644 AtlassianPowerKit-Confluence/Updating-Confluence-Pages.md create mode 100644 AtlassianPowerKit-GRCosm/templates/GRCosm-SharePointViewer-Template.confluence create mode 100644 Dockerfile create mode 100644 Run.ps1 create mode 100644 build_and_push.ps1 create mode 100644 osm_home/tmp diff --git a/AtlassianPowerKit-Confluence/Updating-Confluence-Pages.md b/AtlassianPowerKit-Confluence/Updating-Confluence-Pages.md new file mode 100644 index 0000000..792d600 --- /dev/null +++ b/AtlassianPowerKit-Confluence/Updating-Confluence-Pages.md @@ -0,0 +1 @@ +# diff --git a/AtlassianPowerKit-GRCosm/templates/GRCosm-SharePointViewer-Template.confluence b/AtlassianPowerKit-GRCosm/templates/GRCosm-SharePointViewer-Template.confluence new file mode 100644 index 0000000..a1e0134 --- /dev/null +++ b/AtlassianPowerKit-GRCosm/templates/GRCosm-SharePointViewer-Template.confluence @@ -0,0 +1,24 @@ +true{{ FILE_NAME_BASE }}middleview source: {{ VIEW_URL }}, download source: + {{ DOWNLOAD_URL }}0yes100%{{ FILE_NAME_BASE }}hide{{ FILE_NAME_BASE }}01080px +

diff --git a/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psd1 b/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psd1 index 086d8f9..19cfd96 100644 --- a/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psd1 +++ b/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psd1 @@ -71,6 +71,8 @@ # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. FunctionsToExport = @( 'Add-FormsFromJQLQueryResults', + 'Export-RestorableJiraBackupJQL', + 'Import-JIRAIssueFromJSONBackup', 'Clear-JiraProjectProperty', 'Get-FormsForJiraIssue', 'Get-JiraActiveWorkflows', diff --git a/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psm1 b/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psm1 index 14fa09f..e1cd9e4 100644 --- a/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psm1 +++ b/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psm1 @@ -67,6 +67,8 @@ GitHub: https://github.com/markz0r/AtlassianPowerKit #> $ErrorActionPreference = 'Stop'; $DebugPreference = 'Continue' +# Directory of this file +Import-Module "$env:OSM_INSTALL\AtlassianPowerKit\AtlassianPowerKit-Shared\AtlassianPowerKit-Shared.psd1" -Force $REQ_SLEEP_SEC = 1 $REQ_SLEEP_SEC_LONG = 10 function Convert-JiraIssueToTableRow { @@ -89,6 +91,158 @@ function Convert-JiraIssueToTableRow { return $TABLE_ROW } +function Export-RestorableJiraBackupJQL { + param ( + [Parameter(Mandatory = $true)] + [string]$JQL_STRING + ) + $OUTPUT_DIR = "$($env:OSM_HOME)\$($env:AtlassianPowerKit_PROFILE_NAME)\JIRA\Exported-Backup-$(Get-Date -Format 'yyyyMMdd-HHmmss')" + if (-not (Test-Path $OUTPUT_DIR)) { + New-Item -ItemType Directory -Path $OUTPUT_DIR -Force | Out-Null + } + $JIRA_ISSUES = Get-JiraCloudJQLQueryResult -JQL_STRING $JQL_STRING -ReturnJSONOnly + $JIRA_ISSUES | ConvertFrom-Json -Depth 100 | ForEach-Object { + $ISSUE = $_ + $ISSUE_KEY = $ISSUE.key + Write-Debug "Exporting issue: $ISSUE_KEY to $OUTPUT_DIR\$ISSUE_KEY ..." + if (-not (Test-Path "$OUTPUT_DIR\$ISSUE_KEY")) { + New-Item -ItemType Directory -Path "$OUTPUT_DIR\$ISSUE_KEY" -Force | Out-Null + } + $ISSUE | ConvertTo-Json -Depth 100 | Out-File -FilePath "$OUTPUT_DIR\$ISSUE_KEY\$ISSUE_KEY.json" -Force + if ($ISSUE.fields.attachment) { + $ATTACHMENTS = $ISSUE.fields.attachment + $ATTACHMENTS | ForEach-Object { + $ATTACHMENT = $_ + $ATTACHMENT_ID = $ATTACHMENT.id + $ATTACHMENT_FILENAME = $ATTACHMENT.filename + Write-Debug "Exporting attachment: $OUTPUT_DIR\$ISSUE_KEY\$ATTACHMENT_FILENAME ..." + Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/attachment/content/$ATTACHMENT_ID" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get -ContentType $Attachment.mimeType -OutFile "$OUTPUT_DIR\$ISSUE_KEY\$ATTACHMENT_FILENAME" + Write-Debug "Exporting attachment: $OUTPUT_DIR\$ISSUE_KEY\$ATTACHMENT_FILENAME ... Done" + } + } + } +} + +function Import-JIRAIssueFromJSONBackup { + param ( + [Parameter(Mandatory = $true)] + [string]$JSON_FILE_PATH, + [Parameter(Mandatory = $true)] + [string]$DEST_PROJECT_KEY, + [Parameter(Mandatory = $true)] + [string]$DEST_ISSUE_TYPE, + [Parameter(Mandatory = $false)] + [string]$FIELD_MAP_JSON + ) + + + $ISSUE = Get-Content -Path $JSON_FILE_PATH | ConvertFrom-Json -Depth 100 + if ($FIELD_MAP_JSON) { + Write-Debug "Field map provided: $FIELD_MAP_JSON" + $FIELD_MAP = Get-Content -Path $FIELD_MAP_JSON -Raw | ConvertFrom-Json -NoEnumerate -Depth 100 + } + else { + Write-Debug 'Using manual mapping...' + $POST_ISSUE = @{ + fields = @{ + project = @{ + key = $DEST_PROJECT_KEY + } + issuetype = @{ + name = $DEST_ISSUE_TYPE + } + summary = $ISSUE.fields.summary # Summary of the issue + description = $ISSUE.fields.description # Description of the issue + } + } + } + $ISSUE_SOURCE_INFO = "Source: $($ISSUE.fields.project.key) - $($ISSUE.key) - $($ISSUE.fields.issuetype.name) - $($ISSUE.self)" + $ISSUE_KEY = $ISSUE.key + Write-Debug "Importing issue: $ISSUE_KEY to $DEST_PROJECT_KEY as $DEST_ISSUE_TYPE ..." + + $POST_COMMENT_JSON = "{ + 'version': 1, + 'type': 'doc', + 'content': [ + { + 'type': 'bulletList', + 'content': [ + { + 'type': 'listItem', + 'content': [ + { + 'type': 'paragraph', + 'content': [ + { + 'type': 'text', + 'text': 'Importing Issue using AtlassianPowerKit, ``$ISSUE_SOURCE_INFO``' + } + ] + } + ] + } + ] + } + ] + }" + # Write-Debug 'Converting fields from issue json:' + # $ISSUE | ConvertTo-Json -Depth 100 | Write-Debug + # $ISSUE.fields | ForEach-Object { + # $FIELD = $_ + # $FIELD_NAME = $FIELD.Key + # $FIELD_VALUE = $FIELD.Value + # if ($FIELD_MAP.ConvertToComments -contains $FIELD_NAME) { + # Write-Debug "Converting field to comment: $FIELD_NAME" + # $POST_ISSUE.fields.$FIELD_NAME = @{ + # body = $FIELD_VALUE + # } + # } + # elseif ($FIELD_MAP.IgnorePatterns -contains $FIELD_NAME) { + # Write-Debug "Ignoring field: $FIELD_NAME" + # } + # else { + # Write-Debug "Adding field: $FIELD_NAME" + # $POST_ISSUE.fields.$FIELD_NAME = $FIELD_VALUE + # } + # } + # https://your-domain.atlassian.net/rest/api/3/issue/createmeta/{projectIdOrKey}/issuetypes' + # Write-Debug 'CREATE ISSUE METADATA: ' + # $CREATE_ISSUE_METADATA = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/issue/createmeta/$DEST_PROJECT_KEY/issuetypes" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get -ContentType 'application/json' + # $CREATE_ISSUE_METADATA | ConvertTo-Json -Depth 100 | Write-Debug + # return + + + Write-Debug "POSTING ISSUE: $($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/issue" + $POST_ISSUE | ConvertTo-Json -Depth 100 -EscapeHandling Default | Write-Debug + try { + $POST_REST_RESPONSE = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/issue" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Post -ContentType 'application/json' -Body $($POST_ISSUE | ConvertTo-Json -EscapeHandling Default -Depth 100) + } + catch { + Write-Debug "Error importing issue: $ISSUE_KEY to $DEST_PROJECT_KEY as $DEST_ISSUE_TYPE" + Write-Debug ($_ | Select-Object -Property * -ExcludeProperty psobject | Out-String) + Write-Error $_.Exception.Message + } + $NEW_ISSUE_KEY = $POST_REST_RESPONSE.key + Write-Debug "Successfully imported issue, new issue key: $NEW_ISSUE_KEY" + $ATTACHMENT_POST_HEADERS = $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) + $ATTACHMENT_POST_HEADERS.Add('X-Atlassian-Token', 'no-check') + + if ($ISSUE.fields.attachment) { + $ATTACHMENTS = $ISSUE.fields.attachment + $ATTACHMENTS | ForEach-Object { + $ATTACHMENT = $_ + $ATTACHMENT_ID = $ATTACHMENT.id + $ATTACHMENT_FILENAME = $ATTACHMENT.filename + Write-Debug "Importing attachment: $OUTPUT_DIR\$ISSUE_KEY\$ATTACHMENT_FILENAME ..." + Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/issue/$NEW_ISSUE_KEY/attachments/$ATTACHMENT_ID" -Headers $ATTACHMENT_POST_HEADERS -Method Post -ContentType $Attachment.mimeType -InFile "$OUTPUT_DIR\$ISSUE_KEY\$ATTACHMENT_FILENAME" + Write-Debug "Importing attachment: $OUTPUT_DIR\$ISSUE_KEY\$ATTACHMENT_FILENAME ... Done" + + } + } + # Add comment + Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/issue/$NEW_ISSUE_KEY/comment" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Post -ContentType 'application/json' -Body $POST_COMMENT_JSON +} + function Get-JiraFilterResultsAsConfluenceTable { param ( [Parameter(Mandatory = $true)] @@ -460,7 +614,7 @@ function Get-JiraCloudJQLQueryResult { [Parameter(Mandatory = $false)] [switch]$ReturnJSONOnly = $false ) - $OUTPUT_DIR = "$($env:OSM_HOME)\$($env:AtlassianPowerKit_PROFILE_NAME)\JIRA\$($env:AtlassianPowerKit_PROFILE_NAME)" + $OUTPUT_DIR = "$($env:OSM_HOME)\$($env:AtlassianPowerKit_PROFILE_NAME)\JIRA" $OUTPUT_FILE = "$OUTPUT_DIR\JIRA-Query-Results-$(Get-Date -Format 'yyyyMMdd-HHmmss').json" if (-not (Test-Path $OUTPUT_DIR)) { New-Item -ItemType Directory -Path $OUTPUT_DIR -Force | Out-Null @@ -487,7 +641,7 @@ function Get-JiraCloudJQLQueryResult { return } } - $POST_BODY.expand = @('names') + $POST_BODY.expand = @('names', 'renderedFields') $POST_BODY.remove('startAt') $POST_BODY.maxResults = 100 if ($RETURN_FIELDS -and $null -ne $RETURN_FIELDS -and $RETURN_FIELDS.Count -gt 0) { @@ -503,7 +657,7 @@ function Get-JiraCloudJQLQueryResult { $OUTPUT_FILE_LIST = 0..($DYN_LIMIT / 100) | ForEach-Object -Parallel { try { $PARTIAL_OUTPUT_FILE = ($using:OUTPUT_FILE).Replace('.json', "_$_.json") - $REST_RESPONSE = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/2/search" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Post -Body $(@{startAt = ($_ * 100) } + $using:POST_BODY | ConvertTo-Json -Depth 10) -ContentType 'application/json' + $REST_RESPONSE = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/search" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Post -Body $(@{startAt = ($_ * 100) } + $using:POST_BODY | ConvertTo-Json -Depth 10) -ContentType 'application/json' $REST_RESPONSE.issues | ConvertTo-Json -Depth 100 -Compress | Out-File -FilePath $PARTIAL_OUTPUT_FILE return $PARTIAL_OUTPUT_FILE } @@ -573,10 +727,13 @@ function Get-JiraCloudJQLQueryResult { else { $COMBINED_ISSUES | ConvertTo-Json -Depth 100 -Compress | Out-File -FilePath $OUTPUT_FILE Write-Debug "JIRA COMBINED Query results written to: $OUTPUT_FILE" + $OUTPUT_FILE_LIST | ForEach-Object { + Remove-Item -Path $_ -Force + } #Write-Debug '########## Get-JiraCloudJQLQueryResult completed, OUTPUT_FILE_LIST: ' #$OUTPUT_FILE_LIST | Write-Debug # Combine raw, compressed JSON files into a single JSON file that is valid JSON - return $OUTPUT_FILE_LIST + return $OUTPUT_FILE } } diff --git a/AtlassianPowerKit-Shared/AtlassianPowerKit-Shared.psm1 b/AtlassianPowerKit-Shared/AtlassianPowerKit-Shared.psm1 index 075be26..21e651a 100644 --- a/AtlassianPowerKit-Shared/AtlassianPowerKit-Shared.psm1 +++ b/AtlassianPowerKit-Shared/AtlassianPowerKit-Shared.psm1 @@ -35,7 +35,7 @@ GitHub: https://github.com/markz0r/AtlassianPowerKit # Vault path: $env:LOCALAPPDATA\Microsoft\PowerShell\secretmanagement\localstore\ $ErrorActionPreference = 'Stop'; $DebugPreference = 'Continue' $VAULT_NAME = 'AtlassianPowerKitProfileVault' -$VAULT_KEY_PATH = 'vault_key.xml' +$VAULT_KEY_PATH = "$($env:OSM_HOME)\vault_key.xml" function Clear-AtlassianPowerKitProfile { # Clear all environment variables starting with AtlassianPowerKit_ @@ -192,6 +192,29 @@ function Get-VaultKey { return $VAULT_KEY } +function New-AtlassianPowerKitProfile { + # Ask user to enter the profile name + $ProfileName = Read-Host 'Enter a profile name:' + $ProfileName = $ProfileName.ToLower().Trim() + if (!$ProfileName -or $ProfileName -eq '' -or $ProfileName.Length -gt 100) { + Write-Error 'Profile name cannot be empty, or more than 100 characters, Please try again.' + # Load the selected profile or create a new profile + Write-Debug "Profile name entered: $ProfileName" + Throw 'Profile name cannot be empty, taken or mor than 100 characters, Please try again.' + } + else { + try { + $REGISTERED_PROFILE = Register-AtlassianPowerKitProfile($ProfileName) + } + catch { + Write-Debug "Error: $($_.Exception.Message)" + throw "Register-AtlassianPowerKitProfile $ProfileName failed. Exiting." + } + } + return $REGISTERED_PROFILE +} + + function Register-AtlassianPowerKitVault { # Register the secret vault # Cheking if the vault is already registered @@ -262,13 +285,7 @@ function Register-AtlassianPowerKitProfile { [Parameter(Mandatory = $true)] [string] $AtlassianAPIEndpoint, [Parameter(Mandatory = $true)] - [PSCredential] $AtlassianAPICredential, - [Parameter(Mandatory = $false)] - [string] $OpsgenieAPIEndpoint = 'api.opsgenie.com', - [Parameter(Mandatory = $false)] - [switch] $UseOpsgenieAPI = $false, - [Parameter(Mandatory = $false)] - [PSCredential] $OpsgenieAPICredential + [PSCredential] $AtlassianAPICredential ) if (!$script:REGISTER_VAULT) { Register-AtlassianPowerKitVault @@ -360,7 +377,8 @@ function Set-AtlassianPowerKitProfile { # Check if the profile exists $PROFILE_LIST = Get-AtlassianPowerKitProfileList if (!$PROFILE_LIST.Contains($SelectedProfileName)) { - Write-Debug "Profile $SelectedProfileName does not exists in the vault - we have: $PROFILE_LIST" + Write-Debug "Profile $SelectedProfileName does not exists in the vault - we have: $PROFILE_LIST, creating... $SelectedProfileName" + New-AtlassianPowerKitProfile -ProfileName $SelectedProfileName return $false } else { @@ -411,20 +429,6 @@ function Test-AtlassianPowerKitProfile { throw 'Atlassian Cloud API Auth test failed.' } Write-Debug "Atlassian Cloud Auth test returned: $($REST_RESULTS.displayName) --- OK!" - - # Test Opsgenie API if profile uses Opsgenie API - if ($env:AtlassianPowerKit_UseOpsgenieAPI) { - try { - Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_OpsgenieAPIEndpoint)/v1/services?limit=1" -Headers $($env:AtlassianPowerKit_OpsgenieAPIHeaders | ConvertFrom-Json -AsHashtable) -Method Get - #Write-Debug (ConvertTo-Json $REST_RESULTS -Depth 10) - } - catch { - Write-Debug 'StatusCode:' $_.Exception.Response.StatusCode.value__ - Write-Debug 'StatusDescription:' $_.Exception.Response.StatusDescription - throw 'Opsgenie API Auth test failed.' - } - Write-Debug 'Opsgenie Auth test --- OK!' - } } function Unlock-Vault { diff --git a/AtlassianPowerKit.psm1 b/AtlassianPowerKit.psm1 index be9064f..f4b1021 100644 --- a/AtlassianPowerKit.psm1 +++ b/AtlassianPowerKit.psm1 @@ -73,9 +73,9 @@ function Import-NestedModules { function Test-OSMHomeDir { # If the OSM_HOME environment variable is not set, set it to the current directory. - $new_home = $(Get-Item $pwd).FullName | Split-Path -Parent if (-not $env:OSM_HOME) { Write-Debug "Setting OSM_HOME to $new_home" + $new_home = $(Get-Item $pwd).FullName | Split-Path -Parent $env:OSM_HOME = $new_home } # Check the OSM_HOME environment variable directory exists @@ -84,10 +84,17 @@ function Test-OSMHomeDir { Write-Warning "Changing OSM_HOME to $new_home" $env:OSM_HOME = $new_home } - if ($env:OSM_HOME -ne $new_home) { - Write-Warn "OSM_HOME is set to $env:OSM_HOME, but the script location indicates it should be $new_home. This may cause issues." - } $ValidatedOSMHome = (Get-Item $env:OSM_HOME).FullName + if (-not $env:OSM_INSTALL) { + # if linux, set the default OSM_INSTALL path to /opt/osm + if ($IsLinux) { + $env:OSM_INSTALL = '/opt/osm' + } + else { + $env:OSM_INSTALL = $(Get-ItemProperty -Path ..\).FullName + } + + } return $ValidatedOSMHome } @@ -139,7 +146,7 @@ function Show-AtlassianPowerKitFunctions { ) $selectedFunction = $null # Remove AtlassianPowerKit-Shard and AtlassianPowerKit-UsersAndGroups from the nested modules - $NESTED_MODULES = $NESTED_MODULES | Where-Object { $_ -ne 'AtlassianPowerKit-UsersAndGroups' } + $NESTED_MODULES = $NESTED_MODULES | Where-Object { $_ -ne 'AtlassianPowerKit-UsersAndGroups' -and $_ -ne 'AtlassianPowerKit-Shared' } # List nested modules and their exported functions to the console in a readable format, grouped by module $colors = @('Green', 'Cyan', 'Red', 'Magenta', 'Yellow', 'Blue', 'Gray') $colorIndex = 0 @@ -219,28 +226,6 @@ function Show-AtlassianPowerKitFunctions { return $SelectedFunctionName } -# Function to create a new profile -function New-AtlassianPowerKitProfile { - # Ask user to enter the profile name - $ProfileName = Read-Host 'Enter a profile name:' - $ProfileName = $ProfileName.ToLower().Trim() - if (!$ProfileName -or $ProfileName -eq '' -or $ProfileName.Length -gt 100) { - Write-Error 'Profile name cannot be empty, or more than 100 characters, Please try again.' - # Load the selected profile or create a new profile - Write-Debug "Profile name entered: $ProfileName" - Throw 'Profile name cannot be empty, taken or mor than 100 characters, Please try again.' - } - else { - try { - Register-AtlassianPowerKitProfile($ProfileName) - } - catch { - Write-Debug "Error: $($_.Exception.Message)" - throw "Register-AtlassianPowerKitProfile $ProfileName failed. Exiting." - } - } -} - # Function to list availble profiles with number references for interactive selection or 'N' to create a new profile function Show-AtlassianPowerKitProfileList { #Get-AtlassianPowerKitProfileList @@ -248,7 +233,8 @@ function Show-AtlassianPowerKitProfileList { $profileIndex = 0 if (!$PROFILE_LIST) { Write-Host 'Please create a new profile.' - New-AtlassianPowerKitProfile + $REGISTERED_PROFILE = New-AtlassianPowerKitProfile + return $REGISTERED_PROFILE #Write-Debug "Profile List: $(Get-AtlassianPowerKitProfileList)" #Show-AtlassianPowerKitProfileList } @@ -305,7 +291,7 @@ function AtlassianPowerKit { [Parameter(Mandatory = $false)] [string] $FunctionName, [Parameter(Mandatory = $false)] - [hashtable] $FunctionParameterHashTable, + [hashtable] $FunctionParameters, [Parameter(Mandatory = $false)] [switch] $ClearProfile ) @@ -337,7 +323,7 @@ function AtlassianPowerKit { $ProfileName = $Profile.Trim().ToLower() } if (!$ProfileName) { - $ProfileName = $(Show-AtlassianPowerKitProfileList) + $ProfileName = Show-AtlassianPowerKitProfileList } $CURRENT_PROFILE = Set-AtlassianPowerKitProfile -SelectedProfileName $ProfileName Write-Debug "Profile set to: $CURRENT_PROFILE" @@ -346,13 +332,13 @@ function AtlassianPowerKit { } # If function parameters are provided, splat them to the function Write-Debug "AtlassianPowerKit Main - Running function: $FunctionName, with profile: $CURRENT_PROFILE" - if ($FunctionParameterHashTable) { + if ($FunctionParameters) { Write-Debug ' Parameters provided to the function via hashtable:' # Iterate through the hashtable and display the key value pairs as "-key value" - $FunctionParameterHashTable.GetEnumerator() | ForEach-Object { + $FunctionParameters.GetEnumerator() | ForEach-Object { Write-Debug " -$($_.Key) $_.Value" } - Invoke-AtlassianPowerKitFunction -FunctionName $FunctionName -FunctionParameters $FunctionParameterHashTable + Invoke-AtlassianPowerKitFunction -FunctionName $FunctionName -FunctionParameters $FunctionParameters } elseif ($FunctionName) { Write-Debug "AtlassianPowerKit Main: No parameters provided to the function, attempting to run the function without parameters: $FunctionName" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..24881a9 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,22 @@ +# Use the official PowerShell image as the base image +FROM mcr.microsoft.com/powershell:latest + +# Set the working directory +WORKDIR /opt/osm/AtlassianPowerKit +ADD . . + +# Install Git and required PowerShell modules +RUN apt-get update && \ + apt-get install -y git && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* && \ + mkdir -p /mnt/osm && \ + chmod 755 -R ./* && \ + pwsh -Command "Install-Module -Name PowerShellGet -Force" && \ + pwsh -Command "Install-Module -Name Microsoft.PowerShell.SecretManagement,Microsoft.PowerShell.SecretStore -Force" + +# Set the OSM_HOME environment variable +ENV OSM_HOME=/mnt/osm + +# Use CMD instead of ENTRYPOINT for overridable command +CMD ["pwsh", "./Run.ps1"] diff --git a/README.md b/README.md index b45ee67..1dd0ba2 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,18 @@ AtlassianPowerKit AtlassianPowerKitFunction -FunctionName "Get-JiraIssue" -FunctionParameters @{"Key"="TEST-1"} -Profile "zoak" ``` +```docker +# Windows +mkdir .\osm_home +docker run --rm -v ${PWD}\osm_home:/mnt/osm -v "$Env:LOCALAPPDATA\Microsoft\PowerShell\secretmanagement\:/root/.secretmanagement/" -it markz0r/atlassian-powerkit:latest + +# Linux +mkdir ./osm_home +docker run -it --rm -v ${PWD}/osm_home:/mnt/osm -v "$HOME/.local/share/powershell/secretmanagement/ " +``` + +```powershell + ## Prerequisites - Windows PowerShell 7.0 or later @@ -34,3 +46,4 @@ See [LICENSE](LICENSE.md) file. ## Disclaimer This module is provided as-is without any warranty or support. Use it at your own risk. +``` diff --git a/Run.ps1 b/Run.ps1 new file mode 100644 index 0000000..c960753 --- /dev/null +++ b/Run.ps1 @@ -0,0 +1,26 @@ +# Run.ps1 + +# Set the environment variable if needed +$env:OSM_HOME = '/mnt/osm' +$env:OSM_INSTALL = '/opt/osm' + +# Import necessary modules +Import-Module -Name Microsoft.PowerShell.SecretManagement, Microsoft.PowerShell.SecretStore -Force +Set-Location /opt/osm/AtlassianPowerKit +Import-Module /opt/osm/AtlassianPowerKit/AtlassianPowerKit.psd1 -Force +$env:SECRETSTORE_PATH = $env:OSM_HOME + + +# Check if arguments were passed to the script +if ($args.Count -gt 0) { + # Run AtlassianPowerKit with the provided arguments + AtlassianPowerKit @args +} +else { + # Default command + Write-Output 'No arguments provided. Starting Atlassian PowerKit...' + AtlassianPowerKit +} + +## We can override the default command by passing the command as an argument to the script, e.g.: +## docker run -v osm_home:/mnt/osm --rm --mount osm_home:/mnt/osm -ti markz0r/atlassian-powerkit:latest -FunctionName Get-JiraCloudJQLQueryResult -FunctionParameters @{"JQLQuery"="project in (GRCOSM, HROSM)"}' \ No newline at end of file diff --git a/build_and_push.ps1 b/build_and_push.ps1 new file mode 100644 index 0000000..7d339a7 --- /dev/null +++ b/build_and_push.ps1 @@ -0,0 +1 @@ +docker build -t markz0r/atlassian-powerkit:latest . \ No newline at end of file diff --git a/osm_home/tmp b/osm_home/tmp new file mode 100644 index 0000000..1c2f433 --- /dev/null +++ b/osm_home/tmp @@ -0,0 +1 @@ +tmp \ No newline at end of file From dc53ca61ee02ceec63fa312faf6f372411e255b2 Mon Sep 17 00:00:00 2001 From: Mark Culhane Date: Sun, 17 Nov 2024 15:05:01 +1100 Subject: [PATCH 02/11] adding standardised Get-PaginatedJSONResults --- .../AtlassianPowerKit-JSM.psd1 | 4 +- .../AtlassianPowerKit-JSM.psm1 | 52 ++- .../AtlassianPowerKit-Jira.psm1 | 363 ++++++++++++------ .../AtlassianPowerKit-JIRAGRCosmDeploy.psd1 | 138 +++++++ .../AtlassianPowerKit-JIRAGRCosmDeploy.psm1 | 19 + 5 files changed, 457 insertions(+), 119 deletions(-) create mode 100644 AtlassinPowerKit-JIRAGRCosmDeploy/AtlassianPowerKit-JIRAGRCosmDeploy.psd1 create mode 100644 AtlassinPowerKit-JIRAGRCosmDeploy/AtlassianPowerKit-JIRAGRCosmDeploy.psm1 diff --git a/AtlassianPowerKit-JSM/AtlassianPowerKit-JSM.psd1 b/AtlassianPowerKit-JSM/AtlassianPowerKit-JSM.psd1 index 1e49cfb..6772744 100644 --- a/AtlassianPowerKit-JSM/AtlassianPowerKit-JSM.psd1 +++ b/AtlassianPowerKit-JSM/AtlassianPowerKit-JSM.psd1 @@ -71,7 +71,9 @@ # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. FunctionsToExport = @( 'Get-AtlassianOrgs', - 'Export-OrgHistory' + 'Export-OrgHistory', + 'Get-JiraServiceDeskRequestTypes', + 'Get-JiraServiceDeskAllRequestTypes' ) # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. diff --git a/AtlassianPowerKit-JSM/AtlassianPowerKit-JSM.psm1 b/AtlassianPowerKit-JSM/AtlassianPowerKit-JSM.psm1 index a293b64..a758d70 100644 --- a/AtlassianPowerKit-JSM/AtlassianPowerKit-JSM.psm1 +++ b/AtlassianPowerKit-JSM/AtlassianPowerKit-JSM.psm1 @@ -6,18 +6,58 @@ GitHub: https://github.com/markz0r/AtlassianPowerKit #> $ErrorActionPreference = 'Stop'; $DebugPreference = 'Continue' +# Function to export Org history from Jira Cloud +function Export-OrgHistory { + param ( + [Parameter(Mandatory = $true)] + [string]$ORG_ID, + [Parameter(Mandatory = $false)] + [string]$OUTPUT_PATH = "$($env:OSM_HOME)\$($env:AtlassianPowerKit_PROFILE_NAME)\JIRA" + ) + $FILENAME = "$env:AtlassianPowerKit_PROFILE_NAME-$ORG_ID-OrgHistory-$(Get-Date -Format 'yyyyMMdd-HHmmss').json" + $ORG_HISTORY = Invoke-RestMethod -Uri "https://api.atlassian.com/admin/v1/orgs/$ORG_ID/events" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get -ContentType 'application/json' + $ORG_HISTORY | ConvertTo-Json -Depth 50 | Out-File -FilePath "$OUTPUT_PATH\$FILENAME" + Return $ORG_HISTORY +} + # Function to get list of Orgs from Jira Cloud function Get-AtlassianOrgs { + param ( + [Parameter(Mandatory = $false)] + [string]$OUTPUT_PATH = "$($env:OSM_HOME)\$($env:AtlassianPowerKit_PROFILE_NAME)\JIRA" + ) + $FILENAME = "$env:AtlassianPowerKit_PROFILE_NAME-OrgList-$(Get-Date -Format 'yyyyMMdd-HHmmss').json" $ORG_LIST = Invoke-RestMethod -Uri 'https://api.atlassian.com/admin/v1/orgs' -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get -ContentType 'application/json' - $ORG_LIST | ConvertTo-Json -Depth 10 + $ORG_LIST | ConvertTo-Json -Depth 50 | Out-File -FilePath "$OUTPUT_PATH\$FILENAME" + Write-Debug "ORG_LIST written to $OUTPUT_PATH\$FILENAME" + Return $ORG_LIST } -# Function to get Org history from Jira Cloud -function Export-OrgHistory { +# Function to get list of Request Types from Jira Cloud JSM project +function Get-JiraServiceDeskRequestTypes { param ( [Parameter(Mandatory = $true)] - [string]$ORG_ID + [string]$PROJECT_KEY, + [Parameter(Mandatory = $false)] + [string]$OUTPUT_PATH = "$($env:OSM_HOME)\$($env:AtlassianPowerKit_PROFILE_NAME)\JIRA" ) - $ORG_HISTORY = Invoke-RestMethod -Uri "https://api.atlassian.com/admin/v1/orgs/$ORG_ID/events" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get -ContentType 'application/json' - $ORG_HISTORY | ConvertTo-Json -Depth 10 + $FILENAME = "$env:AtlassianPowerKit_PROFILE_NAME-$PROJECT_KEY-IssueTypeSchema-$(Get-Date -Format 'yyyyMMdd-HHmmss').json" + $REQUEST_TYPES = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_JiraCloudInstance)/rest/servicedeskapi/servicedesk/$PROJECT_KEY/requesttype" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get -ContentType 'application/json' + $REQUEST_TYPES | ConvertTo-Json -Depth 50 | Out-File -FilePath "$OUTPUT_PATH\$FILENAME" + Write-Debug "REQUEST_TYPES [$PROJECT_KEY] written to $OUTPUT_PATH\$FILENAME" + Return $REQUEST_TYPES +} + +# Function to get All Request Types from Jira Cloud JSM project +function Get-JiraServiceDeskAllRequestTypes { + param ( + [Parameter(Mandatory = $false)] + [string]$OUTPUT_PATH = "$($env:OSM_HOME)\$($env:AtlassianPowerKit_PROFILE_NAME)\JIRA" + ) + $FILENAME = "$($env:AtlassianPowerKit_PROFILE_NAME)-AllIssueTypeSchema-$(Get-Date -Format 'yyyyMMdd-HHmmss').json" + $ALL_REQUEST_TYPES += Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_JiraCloudInstance)/rest/servicedeskapi/requesttype" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get -ContentType 'application/json' + $ALL_REQUEST_TYPES | ConvertTo-Json -Depth 50 | Out-File -FilePath "$OUTPUT_PATH\$FILENAME" + Write-Debug "ALL_REQUEST_TYPES written to $OUTPUT_PATH\$FILENAME" + Return $ALL_REQUEST_TYPES + } diff --git a/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psm1 b/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psm1 index e1cd9e4..0fc231c 100644 --- a/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psm1 +++ b/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psm1 @@ -100,8 +100,14 @@ function Export-RestorableJiraBackupJQL { if (-not (Test-Path $OUTPUT_DIR)) { New-Item -ItemType Directory -Path $OUTPUT_DIR -Force | Out-Null } + Write-Debug "$($MyInvocation.MyCommand.Name) Getting JIRA issues to for JQL: $JQL_STRING ..." $JIRA_ISSUES = Get-JiraCloudJQLQueryResult -JQL_STRING $JQL_STRING -ReturnJSONOnly - $JIRA_ISSUES | ConvertFrom-Json -Depth 100 | ForEach-Object { + Write-Debug "$($MyInvocation.MyCommand.Name) - JQL Results Received from Get-JiraCloudJQLQueryResult..." + #$JIRA_ISSUES | ConvertTo-Json -Depth 100 | Out-File -FilePath "$OUTPUT_DIR\Full.json" -Force + #Write-Debug "Raw JSON file exported to: $OUTPUT_DIR\Full.json" + $JIRA_ISSUES_JSON = $JIRA_ISSUES | ConvertFrom-Json -Depth 1024 -NoEnumerate + Write-Debug "Exporting $($JIRA_ISSUES_JSON.Count) JIRA issues to: $OUTPUT_DIR ..." + $JIRA_ISSUES_JSON | ForEach-Object { $ISSUE = $_ $ISSUE_KEY = $ISSUE.key Write-Debug "Exporting issue: $ISSUE_KEY to $OUTPUT_DIR\$ISSUE_KEY ..." @@ -134,9 +140,24 @@ function Import-JIRAIssueFromJSONBackup { [Parameter(Mandatory = $false)] [string]$FIELD_MAP_JSON ) + $ISSUE = Get-Content -Path $JSON_FILE_PATH | ConvertFrom-Json -Depth 100 -NoEnumerate + ## HACKE + # Change content objects of type = mediaSingle or mediaGroup to code block + $ISSUE.fields.description.content | Where-Object { $_.type -eq 'mediaSingle' -or $_.type -eq 'mediaGroup' } | ForEach-Object { + $_.type = 'codeBlock' + $COMPRESSED_CONTENT = $_.content | ConvertTo-Json -Depth 100 -Compress + $_.content = @( + @{ + type = 'text' + text = $COMPRESSED_CONTENT + }, + @{ + type = 'text' + text = '# Attachment removed by OSM Power Kit Restore, will be re-attached but not re-embedded, see attachments below.' + } + ) + } - - $ISSUE = Get-Content -Path $JSON_FILE_PATH | ConvertFrom-Json -Depth 100 if ($FIELD_MAP_JSON) { Write-Debug "Field map provided: $FIELD_MAP_JSON" $FIELD_MAP = Get-Content -Path $FIELD_MAP_JSON -Raw | ConvertFrom-Json -NoEnumerate -Depth 100 @@ -156,6 +177,8 @@ function Import-JIRAIssueFromJSONBackup { } } } + + $ISSUE_SOURCE_INFO = "Source: $($ISSUE.fields.project.key) - $($ISSUE.key) - $($ISSUE.fields.issuetype.name) - $($ISSUE.self)" $ISSUE_KEY = $ISSUE.key Write-Debug "Importing issue: $ISSUE_KEY to $DEST_PROJECT_KEY as $DEST_ISSUE_TYPE ..." @@ -219,6 +242,9 @@ function Import-JIRAIssueFromJSONBackup { } catch { Write-Debug "Error importing issue: $ISSUE_KEY to $DEST_PROJECT_KEY as $DEST_ISSUE_TYPE" + # Write full errordetails to terminal, ensuring $ErrorDetails returned as json is fully written to terminal and not truncated + Write-Debug ($_.ErrorDetails.ErrorMessages | ConvertFrom-Json -Depth 100 -NoEnumerate | ConvertTo-Json -Depth 100) + Write-Debug ($_ | Select-Object -Property * -ExcludeProperty psobject | Out-String) Write-Error $_.Exception.Message } @@ -242,6 +268,80 @@ function Import-JIRAIssueFromJSONBackup { # Add comment Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/issue/$NEW_ISSUE_KEY/comment" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Post -ContentType 'application/json' -Body $POST_COMMENT_JSON } +function Get-PaginatedJSONResults { + param ( + [Parameter(Mandatory = $true)] + [string]$URI, + [Parameter(Mandatory = $true)] + [string]$METHOD, + [Parameter(Mandatory = $false)] + [string]$POST_BODY, + [Parameter(Mandatory = $false)] + [string]$RESPONSE_JSON_OBJECT_FILTER_KEY, + [Parameter(Mandatory = $false)] + [string]$API_HEADERS = $env:AtlassianPowerKit_AtlassianAPIHeaders + ) + $RESULTS_ARRAY = @() + try { + $NEXT_PAGE = $false + do { + if ($NEXT_PAGE) { + Write-Debug "There are more results, getting next page with token: $NEXT_PAGE" + $POST_BODY.nextPageToken = $NEXT_PAGE + } + Write-Debug "Getting results from: $URI using $METHOD..." + # If method is Get, don't include body (case insensitive) + if ($METHOD -ieq 'Get') { + $REST_RESPONSE = Invoke-RestMethod -Uri $URI -Headers $(ConvertFrom-Json -AsHashtable $API_HEADERS) -Method $METHOD -ContentType 'application/json' -StatusCodeVariable 'scv' + } + elseif ($METHOD -ieq 'Post') { + $REST_RESPONSE = Invoke-RestMethod -Uri $URI -Headers $(ConvertFrom-Json -AsHashtable $API_HEADERS) -Method $METHOD -Body $($POST_BODY | ConvertTo-Json -Depth 30) -ContentType 'application/json' -StatusCodeVariable 'scv' + } + else { + Throw "Unsupported method: $METHOD" + } + Write-Debug "REST_RESPONSE received: $($REST_RESPONSE.GetType()), StatusCode: $scv" + #Write-Debug "REST_RESPONSE: $($REST_RESPONSE | ConvertTo-Json -Depth 10)" + if ($scv -eq 429) { + Write-Debug "429 response, waiting $REQ_SLEEP_SEC_LONG seconds..." + Start-Sleep -Seconds $REQ_SLEEP_SEC_LONG + $COMPLETE = $false + } + # elseif rest response contains nextPageToken that is not empty or whitespace or tab or newline + elseif ($REST_RESPONSE.PSObject.Properties.Match('nextPageToken') -and [string]::IsNullOrWhiteSpace($REST_RESPONSE.nextPageToken) -eq $false) { + #$REST_RESPONSE | ConvertTo-Json -Depth 50 | Write-Debug + Write-Debug "Next page token found: $($REST_RESPONSE.nextPageToken), adding this page to the RESULTS_ARRAY..." + $NEXT_PAGE = $REST_RESPONSE.nextPageToken + $COMPLETE = $false + } + else { + Write-Debug 'No more pages, returning results...' + $COMPLETE = $true + } + if ($RESPONSE_JSON_OBJECT_FILTER_KEY) { + $RESULTS_ARRAY += $REST_RESPONSE.$RESPONSE_JSON_OBJECT_FILTER_KEY + } + else { + $RESULTS_ARRAY += $REST_RESPONSE + } + Write-Debug "RESULTS_ARRAY Count: $($RESULTS_ARRAY.Count)" + #Write-Debug "$($MyInvocation.MyCommand.Name): End of loop, Collected Issue count: $($RESULTS_ARRAY.Count)...Completed?: $COMPLETE" + } while (! $COMPLETE) + } + catch { + Write-Debug "Failed to get paginated results from: $URI" + Write-Debug '##############################################' + Write-Debug "-Uri $URI" + Write-Debug " -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders), -Method $METHOD, -Headers $(ConvertFrom-Json -AsHashtable $API_HEADERS)" + if ($POST_BODY) { + Write-Debug "-Body $($POST_BODY | ConvertTo-Json -Depth 30)" + } + Write-Debug ($_ | Select-Object -Property * -ExcludeProperty psobject | Out-String) + Write-Error "$($MyInvocation.MyCommand.Name): Error getting results: $($_.Exception.Message)" + Write-Debug '##############################################' + } + return $RESULTS_ARRAY +} function Get-JiraFilterResultsAsConfluenceTable { param ( @@ -565,16 +665,17 @@ function Clear-EmptyFields { [Parameter(Mandatory = $true)] [psobject]$Object ) + Write-Warning 'THIS IS CURRENTLY BROKENNNN' if ($Object -is [System.Management.Automation.PSCustomObject]) { # Process hashtable or custom object $Object.psobject.properties | ForEach-Object { if ($null -eq $_.Value -or $_.Value -eq '' -or ($_.Value -is [System.Collections.ICollection] -and $_.Value.Count -eq 0)) { - $Object.psobject.properties.Remove($_.Name) + $Object.psobject.properties.Remove($_.Name) | Out-Null } else { # Recursively clean nested objects - $_.Value = Clear-EmptyFields -Object $_.Value + $_.Value = Clear-EmptyFields -Object $_.Value | Out-Null } } } @@ -583,7 +684,7 @@ function Clear-EmptyFields { $keys = @($Object.Keys) foreach ($key in $keys) { if ($null -eq $Object[$key] -or $Object[$key] -eq '' -or ($Object[$key] -is [System.Collections.ICollection] -and $Object[$key].Count -eq 0)) { - $Object.Remove($key) + $Object.Remove($key) | Out-Null } else { # Recursively clean nested objects @@ -610,24 +711,30 @@ function Get-JiraCloudJQLQueryResult { [Parameter(Mandatory = $false)] [string[]]$RETURN_FIELDS, [Parameter(Mandatory = $false)] - [switch]$IncludeEmptyFields = $false, + [switch]$ClearEmptyFields = $false, [Parameter(Mandatory = $false)] [switch]$ReturnJSONOnly = $false ) - $OUTPUT_DIR = "$($env:OSM_HOME)\$($env:AtlassianPowerKit_PROFILE_NAME)\JIRA" - $OUTPUT_FILE = "$OUTPUT_DIR\JIRA-Query-Results-$(Get-Date -Format 'yyyyMMdd-HHmmss').json" - if (-not (Test-Path $OUTPUT_DIR)) { - New-Item -ItemType Directory -Path $OUTPUT_DIR -Force | Out-Null - } + $OSM_TEMP_DIR = "$($env:OSM_HOME)\$($env:AtlassianPowerKit_PROFILE_NAME)\JIRA\.temp" + if (-not (Test-Path $OSM_TEMP_DIR)) { + New-Item -ItemType Directory -Path $OSM_TEMP_DIR -Force | Out-Null + } + if (! $ReturnJSONOnly) { + $OUTPUT_DIR = "$($env:OSM_HOME)\$($env:AtlassianPowerKit_PROFILE_NAME)\JIRA" + $OUTPUT_FILE = "$OUTPUT_DIR\JIRA-Query-Results-$(Get-Date -Format 'yyyyMMdd-HHmmss').json" + if (-not (Test-Path $OUTPUT_DIR)) { + New-Item -ItemType Directory -Path $OUTPUT_DIR -Force | Out-Null + } + } $POST_BODY = @{ - jql = "$JQL_STRING" - fieldsByKeys = $false - maxResults = 1 + jql = "$JQL_STRING" } # Get total number of results for the JQL query $WARNING_LIMIT = 2000 - $VALIDATE_QUERY = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/2/search" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Post -Body ($POST_BODY | ConvertTo-Json) -ContentType 'application/json' - $DYN_LIMIT = $VALIDATE_QUERY.total + Write-Debug "Validating JQL query: $JQL_STRING ..." + $VALIDATE_QUERY = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/search/approximate-count" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Post -Body ($POST_BODY | ConvertTo-Json) -ContentType 'application/json' -StatusCodeVariable 'scv' + Write-Debug "Total results for JQL query: $VALIDATE_QUERY" + $DYN_LIMIT = $VALIDATE_QUERY.count if ($DYN_LIMIT -eq 0) { Write-Debug 'No results found for the JQL query...' return @@ -641,47 +748,67 @@ function Get-JiraCloudJQLQueryResult { return } } - $POST_BODY.expand = @('names', 'renderedFields') - $POST_BODY.remove('startAt') - $POST_BODY.maxResults = 100 + $POST_BODY.expand = 'names' + $POST_BODY.maxResults = 5000 if ($RETURN_FIELDS -and $null -ne $RETURN_FIELDS -and $RETURN_FIELDS.Count -gt 0) { $POST_BODY.fields = $RETURN_FIELDS } else { Write-Debug 'RETURN_FIELDS not provided, using default fields...' - $POST_BODY.fields = @('*all', '-attachments', '-comment', '-issuelinks', '-subtasks', '-worklog') + $POST_BODY.fields = @('*all', '-issuelinks', '-subtasks', '-worklog', '-changelog', '-comment') } # sequence for 0 to $VALIDATE_QUERY.total in increments of 100 # Set contents of $OUTPUT_FILE '[ #'[' | Out-File -FilePath $OUTPUT_FILE - $OUTPUT_FILE_LIST = 0..($DYN_LIMIT / 100) | ForEach-Object -Parallel { - try { - $PARTIAL_OUTPUT_FILE = ($using:OUTPUT_FILE).Replace('.json', "_$_.json") - $REST_RESPONSE = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/search" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Post -Body $(@{startAt = ($_ * 100) } + $using:POST_BODY | ConvertTo-Json -Depth 10) -ContentType 'application/json' - $REST_RESPONSE.issues | ConvertTo-Json -Depth 100 -Compress | Out-File -FilePath $PARTIAL_OUTPUT_FILE - return $PARTIAL_OUTPUT_FILE - } - catch { - Write-Error "Error processing page PAGE_NUMBER: $_" - return $null - } - } -AsJob -ThrottleLimit 5 | Receive-Job -AutoRemoveJob -Wait - $COMBINED_ISSUES = $OUTPUT_FILE_LIST | ForEach-Object { - $JSON_CONTENT = Get-Content -Path $_ -Raw - #Write-Debug "JSON_CONTENT: $JSON_CONTENT" - #Return all of the object within the JSON object array as individual objects - $JSON_OBJECT_ARRAY = $JSON_CONTENT | ConvertFrom-Json -Depth 100 - Write-Debug "JSON_OBJECT_ARRAY_COUNT: $($JSON_OBJECT_ARRAY.Count)" - $JSON_OBJECT_ARRAY - } - Write-Debug "COMBINED_ISSUES: $($COMBINED_ISSUES.GetType())" - Write-Debug "COMBINED_ISSUES Count: $($COMBINED_ISSUES.Count)" - if ($IncludeEmptyFields -eq $false) { + # $OUTPUT_FILE_LIST = 0..($DYN_LIMIT / 100) | ForEach-Object -Parallel { + $ISSUE_ARRAY = @() + try { + $NEXT_PAGE = $false + do { + if ($NEXT_PAGE) { + Write-Debug "There are more results, getting next page with token: $NEXT_PAGE" + $POST_BODY.nextPageToken = $NEXT_PAGE + $OUTPUT_FILE = $OUTPUT_FILE.Replace('.json', "-$NEXT_PAGE.json") + } + Write-Debug 'Getting JQL results via /rest/api/3/search/jql...' + $REST_RESPONSE = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/search/jql" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Post -Body $($POST_BODY | ConvertTo-Json -Depth 10) -ContentType 'application/json' -StatusCodeVariable 'scv' + Write-Debug "REST_RESPONSE received: $($REST_RESPONSE.GetType()), StatusCode: $scv" + #Write-Debug "REST_RESPONSE: $($REST_RESPONSE | ConvertTo-Json -Depth 10)" + if ($scv -eq 429) { + Write-Debug "429 response, waiting $REQ_SLEEP_SEC_LONG seconds..." + Start-Sleep -Seconds $REQ_SLEEP_SEC_LONG + $COMPLETE = $false + } + # elseif rest response contains nextPageToken, set $NEXT_PAGE to nextPageToken and continue + elseif ($REST_RESPONSE.nextPageToken) { + Write-Debug "Next page token found: $($REST_RESPONSE.nextPageToken), adding this page to the results..." + $NEXT_PAGE = $REST_RESPONSE.nextPageToken + $COMPLETE = $false + } + else { + Write-Debug "$($MyInvocation.MyCommand.Name): No more results, adding $($REST_RESPONSE.issues.Count) issues to the results..." + $COMPLETE = $true + } + $ISSUE_ARRAY += $REST_RESPONSE.issues + Write-Debug "ISSUE_ARRAY Count: $($ISSUE_ARRAY.Count)" + Write-Debug "$($MyInvocation.MyCommand.Name): End of loop, Collected Issue count: $($ISSUE_ARRAY.Count)...Completed?: $COMPLETE" + } while (! $COMPLETE) + } + catch { + Write-Debug 'Error getting jql results with Request details:' + Write-Debug '##############################################' + Write-Debug "-Uri https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/search/jql -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Post -Body $($POST_BODY | ConvertTo-Json -Depth 10) " + Write-Debug ($_ | Select-Object -Property * -ExcludeProperty psobject | Out-String) + Write-Error "$($MyInvocation.MyCommand.Name): Error getting jql : $($_.Exception.Message)" + Write-Debug '##############################################' + } + Write-Debug "$($MyInvocation.MyCommand.Name): COMPLETED COLLECTION of JQL query results received to ISSUE_ARRAY: $($ISSUE_ARRAY.Count) ... Processing..." + if ($ClearEmptyFields -eq $true) { Write-Debug 'Cleaning empty fields...' - $CLEAN_ISSUES = $COMBINED_ISSUES | ForEach-Object { + $CLEAN_ISSUES = $ISSUE_ARRAY | ForEach-Object { $ISSUE = $_ #Write-Debug "Processing issue: $($ISSUE.key)" - $ISSUE | ConvertTo-Json -Depth 100 | Write-Debug + #$ISSUE | ConvertTo-Json -Depth 100 | Write-Debug $FIELDS_ARRAY = $ISSUE.fields #Write-Debug "FIELDS ARRAY TYPE IS: $($FIELDS_ARRAY.GetType())' #Write-Debug 'FIELD COUNT FOR ISSUE: $($FIELDS_ARRAY.Count)" @@ -690,12 +817,12 @@ function Get-JiraCloudJQLQueryResult { Write-Debug "FIELDS_ARRAY Count: $($FIELDS_ARRAY.Count)" $CLEAN_FIELD_ARRAY = Clear-EmptyFields -Object $FIELDS_ARRAY # Replace the fields array with the cleaned fields array in the issue object - Write-Debug "Updating Issue.fields using CLEAN_FILED_ARRAY: $($CLEAN_FIELD_ARRAY.GetType())" + #Write-Debug "Updating Issue.fields using CLEAN_FILED_ARRAY: $($CLEAN_FIELD_ARRAY.GetType())" $ISSUE.fields = $CLEAN_FIELD_ARRAY return $ISSUE } # Replace the combined issues array with the cleaned issues array - $COMBINED_ISSUES = $CLEAN_ISSUES + $ISSUE_ARRAY = $CLEAN_ISSUES } if ($MapFieldNames) { # Get the field mappings from Jira @@ -704,28 +831,30 @@ function Get-JiraCloudJQLQueryResult { # Create a hashtable to map field IDs to field names $JIRA_FIELD_MAPS = @{} $JIRA_FIELDS | ForEach-Object { - $JIRA_FIELD_MAPS[$_.id] = $_.name - Write-Debug "JIRA_FIELD_MAPS: $($_.id) - $($_.name)" + $JIRA_FIELD_MAPS[$_.id] = $_.name | Out-Null + #Write-Debug "JIRA_FIELD_MAPS: $($_.id) - $($_.name)" } # Map the field names in the combined issues - $COMBINED_ISSUES | ForEach-Object { + $ISSUE_ARRAY | ForEach-Object { $_.fields.PSObject.Properties | ForEach-Object { if ($JIRA_FIELD_MAPS.ContainsKey($_.Name)) { $newName = $JIRA_FIELD_MAPS[$_.Name] - $_ | Add-Member -MemberType NoteProperty -Name $newName -Value $_.Value -Force - $_.PSObject.Properties.Remove($_.Name) + $_ | Add-Member -MemberType NoteProperty -Name $newName -Value $_.Value -Force | Out-Null + $_.PSObject.Properties.Remove($_.Name) | Out-Null } } } } if ($ReturnJSONOnly) { Write-Debug 'Returning JSON only...' - $COMBINED_ISSUES | ConvertTo-Json -Depth 100 | Write-Debug - return $($COMBINED_ISSUES | ConvertTo-Json -Depth 100 -Compress) + #Write-Debug "########## $($MyInvocation.MyCommand.Name) completed, returning JSON..:" + #$ISSUE_ARRAY | ConvertTo-Json -Depth 100 | Write-Debug + #Write-Debug "########## $($MyInvocation.MyCommand.Name) completed, returning JSON ^^^" + return $($ISSUE_ARRAY | ConvertTo-Json -Depth 100) } else { - $COMBINED_ISSUES | ConvertTo-Json -Depth 100 -Compress | Out-File -FilePath $OUTPUT_FILE + $ISSUE_ARRAY | ConvertTo-Json -Depth 100 -Compress | Out-File -FilePath $OUTPUT_FILE Write-Debug "JIRA COMBINED Query results written to: $OUTPUT_FILE" $OUTPUT_FILE_LIST | ForEach-Object { Remove-Item -Path $_ -Force @@ -1103,69 +1232,77 @@ function Get-JSMService { function Get-JiraProjectIssuesTypes { param ( [Parameter(Mandatory = $true)] - [string]$JiraCloudProjectKey, + [string]$PROJECT_KEY_OR_ID, [Parameter(Mandatory = $false)] [string]$OUTPUT_PATH = "$($env:OSM_HOME)\$($env:AtlassianPowerKit_PROFILE_NAME)\JIRA" ) - $FILENAME = "$env:AtlassianPowerKit_PROFILE_NAME-$JiraCloudProjectKey-IssueTypes-$(Get-Date -Format 'yyyyMMdd-HHmmss').json" + if ($PROJECT_KEY_OR_ID -match '^\d+$') { + $PROJECT_ID = $PROJECT_KEY_OR_ID + } + else { + $PROJECT_ID = (Get-JiraProjectList -PROJECT_KEY $PROJECT_KEY_OR_ID).id + } + $FILENAME = "$env:AtlassianPowerKit_PROFILE_NAME-$PROJECT_KEY_OR_ID-IssueTypes-$(Get-Date -Format 'yyyyMMdd-HHmmss').json" if (-not (Test-Path $OUTPUT_PATH)) { New-Item -ItemType Directory -Path $OUTPUT_PATH -Force | Out-Null } $OUTPUT_FILE = "$OUTPUT_PATH\$FILENAME" - Write-Debug "Output file: $OUTPUT_FILE" - # Initiate json file with { "Project": "$JiraCloudProjectKey", "JiraIssueTypes": [ - $OUTPUT_FILE_HEADER = "{ `"Project`": `"$JiraCloudProjectKey`", `"JiraIssueTypes`": [" - $OUTPUT_FILE_HEADER | Out-File -FilePath $OUTPUT_FILE - $REST_RESULTS = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/issue/createmeta/$JiraCloudProjectKey/issuetypes" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get - Write-Debug $REST_RESULTS.getType() - foreach ($issueType in $REST_RESULTS.issueTypes) { - #Write-Debug "############## Issue Type: $($issueType.name) ##############" - #Write-Debug "Issue Type: $($issueType | Get-Member -MemberType Properties)" - #Write-Debug "Issue Type ID: $($issueType.id)" - $ISSUE_FIELDS = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/issue/createmeta/$JiraCloudProjectKey/issuetypes/$($issueType.id)" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get - #Write-Debug (ConvertTo-Json $ISSUE_FIELDS -Depth 10) - #Write-Debug '######################################################################' - # Append ConvertTo-Json $ISSUE_FIELDS -Depth 10 to the $OUTPUT_FILE - # Create a JSON object in file to hold the issue type fields - "{""Issue Type"": ""$($issueType.name)"", ""FieldInfo"":" | Out-File -FilePath $OUTPUT_FILE -Append - $ISSUE_FIELDS | ConvertTo-Json -Depth 10 | Out-File -FilePath $OUTPUT_FILE -Append - # Add a comma to the end of the file to separate the issue types - ' - }, ' | Out-File -FilePath $OUTPUT_FILE -Append - } - # Remove the last comma from the file, replace with ]}, ensuring the entire line is written not repeated - $content = Get-Content $OUTPUT_FILE - $content[-1] = $content[-1] -replace ' -}, ', ' -}] }' - $PARSED = $content | ConvertFrom-Json - # Write the content back to the file ensuring JSON formatting is correc - $PARSED | ConvertTo-Json -Depth 30 | Set-Content $OUTPUT_FILE - Write-Debug 'Issue Types found: ' - $PARSED.JiraIssueTypes | ForEach-Object { - $CUSTOM_FIELD_COUNT = ($_.FieldInfo.fields | Where-Object { $_.key -like 'customfield*' }).Count - Write-Debug "$($_.'Issue Type') - Field Count: $($_.'FieldInfo'.total), Custom Field Count: $CUSTOM_FIELD_COUNT" - } - Write-Debug "See Issue Types JSON file created: $OUTPUT_FILE" + # Use Get-PaginatedResults to get all issues types for the project + Write-Debug "Getting Jira Project Issue Types for project: $PROJECT_ID ..." + $REST_RESULTS = Get-PaginatedJSONResults -URI "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/issuetype/project?projectId=$PROJECT_ID" -Method Get + Write-Debug "Jira Project Issue Types for project: $PROJECT_ID received... writing to file..." + $REST_RESULTS | ConvertTo-Json -Depth 50 | Out-File -FilePath $OUTPUT_FILE + Write-Debug "Jira Project Issue Types written to: $OUTPUT_FILE" + return $REST_RESULTS } # Function to get issue type metadata for a Jira Cloud project function Get-JiraCloudIssueTypeMetadata { param ( [Parameter(Mandatory = $true)] - [string]$JiraCloudProjectKey + [string]$PROJECT_KEY, + [Parameter(Mandatory = $false)] + [string]$OUTPUT_PATH = "$($env:OSM_HOME)\$($env:AtlassianPowerKit_PROFILE_NAME)\JIRA" + ) + $FILENAME = "$env:AtlassianPowerKit_PROFILE_NAME-$PROJECT_KEY-IssueTypeMetadata-$(Get-Date -Format 'yyyyMMdd-HHmmss').json" + $REST_RESULTS = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/issue/createmeta/$PROJECT_KEY&expand=projects.issuetypes.fields" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get + ConvertTo-Json $REST_RESULTS -Depth 50 | Out-File -FilePath "$OUTPUT_PATH\$FILENAME" + Write-Debug "Issue Type Metadata JSON file created: $OUTPUT_PATH\$FILENAME" + Return $REST_RESULTS +} + +# Fuction to get Issue Type schema for a Jira Cloud project +function Get-JiraCloudIssueTypeSchema { + param ( + [Parameter(Mandatory = $true)] + [string]$PROJECT_KEY_OR_ID, + [Parameter(Mandatory = $false)] + [string]$OUTPUT_PATH = "$($env:OSM_HOME)\$($env:AtlassianPowerKit_PROFILE_NAME)\JIRA" ) - $REST_RESULTS = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/issue/createmeta/$JiraCloudProjectKey&expand=projects.issuetypes.fields" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get + $FILENAME = "$env:AtlassianPowerKit_PROFILE_NAME-$PROJECT_KEY-IssueTypeSchema-$(Get-Date -Format 'yyyyMMdd-HHmmss').json" + # if the project key is passed, get the project ID (key is Alpha-numeric, ID is numeric) + if ($PROJECT_KEY_OR_ID -match '^\d+$') { + $PROJECT_ID = $PROJECT_KEY_OR_ID + } + else { + $PROJECT_ID = (Get-JiraProjectList -PROJECT_KEY $PROJECT_KEY_OR_ID).id + } + $REST_RESULTS = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/issuetypescheme/project?projectId=$PROJECT_ID" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get Write-Debug $REST_RESULTS.getType() - Write-Debug (ConvertTo-Json $REST_RESULTS -Depth 10) + ConvertTo-Json $REST_RESULTS -Depth 50 | Out-File -FilePath "$OUTPUT_PATH\$FILENAME" + Write-Debug "Issue Type Schema JSON file created: $OUTPUT_PATH\$FILENAME" + Return $REST_RESULTS } # Funtion to print the value project properties (JIRA entity) function Get-JiraProjectList { param ( [Parameter(Mandatory = $false)] - [string]$PROJECT_KEY + [string]$PROJECT_KEY, + [Parameter(Mandatory = $false)] + [string]$OUTPUT_PATH = "$($env:OSM_HOME)\$($env:AtlassianPowerKit_PROFILE_NAME)\JIRA" ) + $FILENAME = "$env:AtlassianPowerKit_PROFILE_NAME-ProjectList-$(Get-Date -Format 'yyyyMMdd-HHmmss').json" $REST_RESPONSE = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/project/search" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get $REST_RESULTS += $REST_RESPONSE.values while (!$REST_RESPONSE.isLast) { @@ -1175,14 +1312,16 @@ function Get-JiraProjectList { if ($PROJECT_KEY) { $REST_RESULTS = $REST_RESULTS | Where-Object { $_.key -eq $PROJECT_KEY } } - return $REST_RESULTS | ConvertTo-Json -Depth 50 + ConvertTo-Json $REST_RESULTS -Depth 50 | Out-File -FilePath "$OUTPUT_PATH\$FILENAME" + Write-Debug "Project List JSON file created: $OUTPUT_PATH\$FILENAME" + return $REST_RESULTS } function Get-JiraProjectProperties { param ( [Parameter(Mandatory = $true)] - [string]$JiraCloudProjectKey + [string]$PROJECT_KEY ) - $REST_RESULTS = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/project/$JiraCloudProjectKey/properties" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get + $REST_RESULTS = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/project/$PROJECT_KEY/properties" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get Write-Debug $REST_RESULTS.getType() Write-Debug (ConvertTo-Json $REST_RESULTS -Depth 10) } @@ -1191,11 +1330,11 @@ function Get-JiraProjectProperties { function Get-JiraProjectProperty { param ( [Parameter(Mandatory = $true)] - [string]$JiraCloudProjectKey, + [string]$PROJECT_KEY, [Parameter(Mandatory = $true)] [string]$PROPERTY_KEY ) - $REST_RESULTS = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/project/$JiraCloudProjectKey/properties/$PROPERTY_KEY" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get + $REST_RESULTS = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/project/$PROJECT_KEY/properties/$PROPERTY_KEY" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get Write-Debug $REST_RESULTS.getType() Write-Debug (ConvertTo-Json $REST_RESULTS -Depth 10) } @@ -1204,7 +1343,7 @@ function Get-JiraProjectProperty { function Set-JiraProjectProperty { param ( [Parameter(Mandatory = $true)] - [string]$JiraCloudProjectKey, + [string]$PROJECT_KEY, [Parameter(Mandatory = $true)] [string]$PROPERTY_KEY, [Parameter(Mandatory = $true)] @@ -1225,13 +1364,13 @@ function Set-JiraProjectProperty { $content | ConvertFrom-Json | Out-Null return } - $REST_RESULTS = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/project/$JiraCloudProjectKey/properties/$PROPERTY_KEY" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Put -Body $content -ContentType 'application/json' + $REST_RESULTS = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/project/$PROJECT_KEY/properties/$PROPERTY_KEY" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Put -Body $content -ContentType 'application/json' Write-Debug $REST_RESULTS.getType() # Write all of the $REST_RESULTS to the console as PSObjects with all properties Write-Debug (ConvertTo-Json $REST_RESULTS -Depth 10) Write-Debug '###############################################' - Write-Debug "Querying the property to confirm the value was set... $PROPERTY_KEY in $JiraCloudProjectKey via $($env:AtlassianPowerKit_AtlassianAPIEndpoint)" - Get-JiraProjectProperty -JiraCloudProjectKey $JiraCloudProjectKey -PROPERTY_KEY $PROPERTY_KEY + Write-Debug "Querying the property to confirm the value was set... $PROPERTY_KEY in $PROJECT_KEY via $($env:AtlassianPowerKit_AtlassianAPIEndpoint)" + Get-JiraProjectProperty -JiraCloudProjectKey $PROJECT_KEY -PROPERTY_KEY $PROPERTY_KEY Write-Debug '###############################################' } @@ -1239,16 +1378,16 @@ function Set-JiraProjectProperty { function Clear-JiraProjectProperty { param ( [Parameter(Mandatory = $true)] - [string]$JiraCloudProjectKey, + [string]$PROJECT_KEY, [Parameter(Mandatory = $true)] [string]$PROPERTY_KEY ) - $REST_RESULTS = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/project/$JiraCloudProjectKey/properties/$PROPERTY_KEY" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Delete + $REST_RESULTS = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/project/$PROJECT_KEY/properties/$PROPERTY_KEY" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Delete Write-Debug $REST_RESULTS.getType() Write-Debug (ConvertTo-Json $REST_RESULTS -Depth 10) Write-Debug '###############################################' - Write-Debug "Querying the propertues to confirm the value was deleted... $PROPERTY_KEY in $JiraCloudProjectKey via $($env:AtlassianPowerKit_AtlassianAPIEndpoint)" - Get-JiraProjectProperties -JiraCloudProjectKey $JiraCloudProjectKey + Write-Debug "Querying the propertues to confirm the value was deleted... $PROPERTY_KEY in $PROJECT_KEY via $($env:AtlassianPowerKit_AtlassianAPIEndpoint)" + Get-JiraProjectProperties -JiraCloudProjectKey $PROJECT_KEY Write-Debug '###############################################' } @@ -1558,10 +1697,10 @@ function Set-AttachedFormsExternalJQLQuery { function Get-FormsForJiraProject { param ( [Parameter(Mandatory = $true)] - [string]$JiraCloudProjectKey + [string]$PROJECT_KEY ) # https://api.atlassian.com/jira/forms/cloud/{cloudId}/project/{projectIdOrKey}/form - $PROJECT_FORM_ID_URL = "https://api.atlassian.com/jira/forms/cloud/$($env:AtlassianPowerKit_CloudID)/project/$JiraCloudProjectKey/form" + $PROJECT_FORM_ID_URL = "https://api.atlassian.com/jira/forms/cloud/$($env:AtlassianPowerKit_CloudID)/project/$PROJECT_KEY/form" $REST_RESULTS = Invoke-RestMethod -Uri $PROJECT_FORM_ID_URL -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get Write-Debug $REST_RESULTS.getType() Write-Debug (ConvertTo-Json $REST_RESULTS -Depth 10) diff --git a/AtlassinPowerKit-JIRAGRCosmDeploy/AtlassianPowerKit-JIRAGRCosmDeploy.psd1 b/AtlassinPowerKit-JIRAGRCosmDeploy/AtlassianPowerKit-JIRAGRCosmDeploy.psd1 new file mode 100644 index 0000000..abec0ef --- /dev/null +++ b/AtlassinPowerKit-JIRAGRCosmDeploy/AtlassianPowerKit-JIRAGRCosmDeploy.psd1 @@ -0,0 +1,138 @@ +# +# Module manifest for module 'AtlassianPowerKit' +# +# Generated by: MarkCulhane +# +# Generated on: 16/06/2024 +# + +@{ + + # Script module or binary module file associated with this manifest. + RootModule = 'AtlassianPowerKit-JIRAGRCosmDeploy.psm1' + + # Version number of this module. + ModuleVersion = '1.0.0' + + # Supported PSEditions + # CompatiblePSEditions = @() + + # ID used to uniquely identify this module + GUID = 'c5cbd86b-5b61-45e9-b4b9-4e30d2144270' + + # Author of this module + Author = 'Mark Culhane' + + # Company or vendor of this module + CompanyName = 'ZOAK Technologies' + + # Copyright statement for this module + Copyright = '(c) ZOAK PTY LTD. All rights reserved.' + + # Description of the functionality provided by this module + Description = 'Atlassian Cloud PowerShell Module for handy functions to interact with Attlassian Cloud APIs.' + + # Minimum version of the PowerShell engine required by this module + PowerShellVersion = '7.3' + + # Name of the PowerShell host required by this module + # PowerShellHostName = '' + + # Minimum version of the PowerShell host required by this module + # PowerShellHostVersion = '' + + # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. + # DotNetFrameworkVersion = '' + + # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. + # ClrVersion = '' + + # Processor architecture (None, X86, Amd64) required by this module + # ProcessorArchitecture = '' + + # Modules that must be imported into the global environment prior to importing this module + # RequiredModules = @('..\AtlassianPowerKit-Shared\AtlassianPowerKit-Shared.psd1', '..\AtlassianPowerKit-Jira\AtlassianPowerKit-Jira.psd1') + + # Assemblies that must be loaded prior to importing this module + # RequiredAssemblies = @() + + # Script files (.ps1) that are run in the caller's environment prior to importing this module. + # ScriptsToProcess = @() + + # Type files (.ps1xml) to be loaded when importing this module + # TypesToProcess = @() + + # Format files (.ps1xml) to be loaded when importing this module + # FormatsToProcess = @() + + # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess + # NestedModules = @('..\AtlassianPowerKit-Shared\AtlassianPowerKit-Shared.psd1', '..\AtlassianPowerKit-Jira\AtlassianPowerKit-Jira.psd1', '..\AtlassianPowerKit-Confluence\AtlassianPowerKit-Confluence.psd1') + + # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. + FunctionsToExport = @( + 'Get-SharePointFileMetadata', + 'New-ConfluencePolicyViewerSharePoint', + 'Get-OSMPlaceholdersJira', + 'Get-OSMPlaceholdersConfluence', + 'Update-GRCosmConfRegister' + ) + + # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. + CmdletsToExport = @() + + # Variables to export from this module + VariablesToExport = @() + + # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. + AliasesToExport = @() + + # DSC resources to export from this module + # DscResourcesToExport = @() + + # List of all modules packaged with this module + # ModuleList = @() + + # List of all files packaged with this module + # FileList = @() + + # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. + PrivateData = @{ + + PSData = @{ + + # Tags applied to this module. These help with module discovery in online galleries. + # Tags = @() + + # A URL to the license for this module. + # LicenseUri = '' + + # A URL to the main website for this project. + # ProjectUri = '' + + # A URL to an icon representing this module. + # IconUri = '' + + # ReleaseNotes of this module + # ReleaseNotes = '' + + # Prerelease string of this module + # Prerelease = '' + + # Flag to indicate whether the module requires explicit user acceptance for install/update/save + # RequireLicenseAcceptance = $false + + # External dependent modules of this module + # ExternalModuleDependencies = @() + + } # End of PSData hashtable + + } # End of PrivateData hashtable + + # HelpInfo URI of this module + # HelpInfoURI = '' + + # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. + # DefaultCommandPrefix = '' + +} + diff --git a/AtlassinPowerKit-JIRAGRCosmDeploy/AtlassianPowerKit-JIRAGRCosmDeploy.psm1 b/AtlassinPowerKit-JIRAGRCosmDeploy/AtlassianPowerKit-JIRAGRCosmDeploy.psm1 new file mode 100644 index 0000000..4d21fac --- /dev/null +++ b/AtlassinPowerKit-JIRAGRCosmDeploy/AtlassianPowerKit-JIRAGRCosmDeploy.psm1 @@ -0,0 +1,19 @@ +<# +.SYNOPSIS + Atlassian Cloud PowerShell Module - AtlassianPowerKit-JIRAGRCosmDeploy - module for creating new issue types, workflows, fields, screens, screen schemes, issue type screen schemes, and issue type screen scheme associations using the Atlassian Jira Cloud REST API.See https://developer.atlassian.com/cloud/jira/platform/rest/v3/ for more information. + +.DESCRIPTION + Atlassian Cloud PowerShell Module - AtlassianPowerKit-JIRAGRCosmDeploy + - Dependencies: AtlassianPowerKit-Shared + - New-AtlassianAPIEndpoint + For list of functions and cmdlets, run Get-Command -Module AtlassianPowerKit-JIRAGRCosmDeploy.psm1 + +.EXAMPLE + New-JiraIssueType -JiraCloudProjectKey 'OSM' -JiraIssueTypeName 'Test Issue Type' -JiraIssueTypeDescription 'This is a test issue type.' -JiraIssueTypeAvatarId '10000' + +.LINK +GitHub: https://github.com/OrganisationServiceManagement/AtlassianPowerKit.git + +#> + +$ErrorActionPreference = 'Stop'; $DebugPreference = 'Continue' From 1cc477cdffe1bdb3b2cca58a5bc7c57dc5389dad Mon Sep 17 00:00:00 2001 From: Mark Culhane Date: Mon, 18 Nov 2024 02:35:43 +1100 Subject: [PATCH 03/11] adding standardised Get-PaginatedJSONResults --- .../AtlassianPowerKit-JSM.psm1 | 12 +- .../AtlassianPowerKit-Jira.psd1 | 4 +- .../AtlassianPowerKit-Jira.psm1 | 128 +++++++++++++++--- .../AtlassianPowerKit-Shared.psm1 | 74 +++++----- AtlassianPowerKit.psm1 | 98 ++++++++++---- .../AtlassianPowerKit-JIRAGRCosmDeploy.psm1 | 11 ++ 6 files changed, 248 insertions(+), 79 deletions(-) diff --git a/AtlassianPowerKit-JSM/AtlassianPowerKit-JSM.psm1 b/AtlassianPowerKit-JSM/AtlassianPowerKit-JSM.psm1 index a758d70..730b38d 100644 --- a/AtlassianPowerKit-JSM/AtlassianPowerKit-JSM.psm1 +++ b/AtlassianPowerKit-JSM/AtlassianPowerKit-JSM.psm1 @@ -41,11 +41,17 @@ function Get-JiraServiceDeskRequestTypes { [Parameter(Mandatory = $false)] [string]$OUTPUT_PATH = "$($env:OSM_HOME)\$($env:AtlassianPowerKit_PROFILE_NAME)\JIRA" ) - $FILENAME = "$env:AtlassianPowerKit_PROFILE_NAME-$PROJECT_KEY-IssueTypeSchema-$(Get-Date -Format 'yyyyMMdd-HHmmss').json" - $REQUEST_TYPES = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_JiraCloudInstance)/rest/servicedeskapi/servicedesk/$PROJECT_KEY/requesttype" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get -ContentType 'application/json' + $FILENAME = "$env:AtlassianPowerKit_PROFILE_NAME-$PROJECT_KEY-RequestTypeSchema-$(Get-Date -Format 'yyyyMMdd-HHmmss').json" + $REQUEST_TYPE_ARRAY = @() + $REQUEST_TYPES = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/servicedeskapi/servicedesk/$PROJECT_KEY/requesttype" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get -ContentType 'application/json' + $REQUEST_TYPE_ARRAY += $REQUEST_TYPES.values + while (! $REQUEST_TYPES.isLastPage) { + $REQUEST_TYPES = Invoke-RestMethod -Uri $REQUEST_TYPES._links.next -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get -ContentType 'application/json' + $REQUEST_TYPE_ARRAY += $REQUEST_TYPES.values + } $REQUEST_TYPES | ConvertTo-Json -Depth 50 | Out-File -FilePath "$OUTPUT_PATH\$FILENAME" Write-Debug "REQUEST_TYPES [$PROJECT_KEY] written to $OUTPUT_PATH\$FILENAME" - Return $REQUEST_TYPES + Return $REQUEST_TYPES | ConvertTo-Json -Depth 100 -Compress } # Function to get All Request Types from Jira Cloud JSM project diff --git a/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psd1 b/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psd1 index 19cfd96..a725913 100644 --- a/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psd1 +++ b/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psd1 @@ -82,6 +82,7 @@ 'Get-JiraFields', 'Get-JiraFilterResultsAsConfluenceTable', 'Get-JiraOSMFilterList', + 'Get-JiraCloudIssueTypeSchema', 'Get-JiraIssue', 'Get-JiraIssueChangeNullsFromJQL', 'Get-JiraIssueLinks', @@ -89,7 +90,8 @@ 'Get-JiraProjectProperties', 'Get-JiraProjectProperty', 'Get-JiraProjectIssuesTypes', - 'Get-JiraStatuses', + 'Get-JiraProjectWorkflowSchemes', + 'Get-JiraProjectStatuses', 'Get-JSMService', 'Get-JSMServices', 'Get-JSONFieldsWithData', diff --git a/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psm1 b/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psm1 index 0fc231c..4f87ddf 100644 --- a/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psm1 +++ b/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psm1 @@ -1095,6 +1095,55 @@ function Get-JiraStatuses { } return $REST_RESULTS } +function Get-JiraProjectStatuses { + param ( + [Parameter(Mandatory = $false)] + [string]$PROJECT_KEY = $false, + [Parameter(Mandatory = $false)] + [string]$OUTPUT_DIR = "$env:OSM_HOME\$env:AtlassianPowerKit_PROFILE_NAME\JIRA" + ) + if ($PROJECT_KEY) { + $OUTPUT_FILE = "$OUTPUT_DIR\$env:AtlassianPowerKit_PROFILE_NAME-$PROJECT_KEY-JIRAProjectStatuses-$(Get-Date -Format 'yyyyMMdd-HHmmss').json" + URL = "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/project/$PROJECT_KEY/statuses" + } + else { + $OUTPUT_FILE = "$OUTPUT_DIR\$env:AtlassianPowerKit_PROFILE_NAME-ALL-JIRAProjectStatuses-$(Get-Date -Format 'yyyyMMdd-HHmmss').json" + URL = "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/status" + } +} + +function Get-JiraProjectWorkflowSchemes { + param ( + [Parameter(Mandatory = $false)] + [string]$PROJECT_KEY = $false, + [Parameter(Mandatory = $false)] + [string]$OUTPUT_DIR = "$env:OSM_HOME\$env:AtlassianPowerKit_PROFILE_NAME\JIRA" + ) + if ($PROJECT_KEY) { + $OUTPUT_FILE = "$OUTPUT_DIR\$env:AtlassianPowerKit_PROFILE_NAME-$PROJECT_KEY-JIRAProjectWorkflowSchemes-$(Get-Date -Format 'yyyyMMdd-HHmmss').json" + Write-Debug "Project Key passed: $PROJECT_KEY ... getting project ID..." + $PROJECT_OBJECT = Get-JiraProjectByKey -PROJECT_KEY $PROJECT_KEY | ConvertFrom-Json -AsHashtable -NoEnumerate + ConvertTo-Json $PROJECT_OBJECT -Depth 50 | Write-Debug + if ($PROJECT_OBJECT.id) { + $PROJECT_ID = $PROJECT_OBJECT.id + } + else { + Write-Error "Project ID not found for project key: $PROJECT_KEY" + } + $URL = "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/workflowscheme/project?projectId=$PROJECT_ID" + } + else { + Write-Debug 'No project key passed, getting all project workflow schemes...disables' + # $OUTPUT_FILE = "$OUTPUT_DIR\$env:AtlassianPowerKit_PROFILE_NAME-ALL-JIRAProjectWorkflowSchemes-$(Get-Date -Format 'yyyyMMdd-HHmmss').json" + # $URL = "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/workflowscheme" + } + $WORKFLOW_SCHEMES = Invoke-RestMethod -Uri $URL -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get -ContentType 'application/json' + $WORKFLOW_SCHEMES | ConvertTo-Json -Depth 100 | Out-File -FilePath $OUTPUT_FILE + Write-Debug "Jira Project Workflow Schemes written to: $OUTPUT_FILE" + return $WORKFLOW_SCHEMES | ConvertTo-Json -Depth 100 -Compress +} + + # Get-JiraActiveWorkflows function Get-JiraActiveWorkflows { @@ -1240,7 +1289,14 @@ function Get-JiraProjectIssuesTypes { $PROJECT_ID = $PROJECT_KEY_OR_ID } else { - $PROJECT_ID = (Get-JiraProjectList -PROJECT_KEY $PROJECT_KEY_OR_ID).id + # Get the most recent auda-ProjectList-*.json in the $OUTPUT_PATH or run Get-JiraProjectList and check again for the file + $PROJECT_LIST_FILE = Get-ChildItem -Path $OUTPUT_PATH -Filter "$env:AtlassianPowerKit_PROFILE_NAME-ProjectList-*.json" | Sort-Object -Property LastWriteTime -Descending | Select-Object -First 1 + While (-not $PROJECT_LIST_FILE) { + Write-Debug 'No Project List file found, running Get-JiraProjectList...' + Get-JiraProjectList -OUTPUT_PATH $OUTPUT_PATH + $PROJECT_LIST_FILE = Get-ChildItem -Path $OUTPUT_PATH -Filter "$env:AtlassianPowerKit_PROFILE_NAME-ProjectList-*.json" | Sort-Object -Property LastWriteTime -Descending | Select-Object -First 1 + } + $PROJECT_ID = (Get-Content -Path $PROJECT_LIST_FILE.FullName | ConvertFrom-Json | Where-Object { $_.key -eq $PROJECT_KEY_OR_ID }).id } $FILENAME = "$env:AtlassianPowerKit_PROFILE_NAME-$PROJECT_KEY_OR_ID-IssueTypes-$(Get-Date -Format 'yyyyMMdd-HHmmss').json" if (-not (Test-Path $OUTPUT_PATH)) { @@ -1253,7 +1309,7 @@ function Get-JiraProjectIssuesTypes { Write-Debug "Jira Project Issue Types for project: $PROJECT_ID received... writing to file..." $REST_RESULTS | ConvertTo-Json -Depth 50 | Out-File -FilePath $OUTPUT_FILE Write-Debug "Jira Project Issue Types written to: $OUTPUT_FILE" - return $REST_RESULTS + return $REST_RESULTS | ConvertTo-Json -Depth 100 -Compress } # Function to get issue type metadata for a Jira Cloud project @@ -1282,39 +1338,60 @@ function Get-JiraCloudIssueTypeSchema { $FILENAME = "$env:AtlassianPowerKit_PROFILE_NAME-$PROJECT_KEY-IssueTypeSchema-$(Get-Date -Format 'yyyyMMdd-HHmmss').json" # if the project key is passed, get the project ID (key is Alpha-numeric, ID is numeric) if ($PROJECT_KEY_OR_ID -match '^\d+$') { + Write-Debug "Project ID passed: $PROJECT_KEY_OR_ID" $PROJECT_ID = $PROJECT_KEY_OR_ID } else { - $PROJECT_ID = (Get-JiraProjectList -PROJECT_KEY $PROJECT_KEY_OR_ID).id + Write-Debug "Project Key passed: $PROJECT_KEY_OR_ID ... getting project ID..." + $PROJECT_OBJECT = Get-JiraProjectByKey -PROJECT_KEY $PROJECT_KEY_OR_ID | ConvertFrom-Json -AsHashtable -NoEnumerate + #ConvertTo-Json $PROJECT_OBJECT -Depth 50 | Write-Debug + if ($PROJECT_OBJECT.id) { + $PROJECT_ID = $PROJECT_OBJECT.id + } + else { + Write-Error "Project ID not found for project key: $PROJECT_KEY_OR_ID" + } } $REST_RESULTS = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/issuetypescheme/project?projectId=$PROJECT_ID" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get - Write-Debug $REST_RESULTS.getType() ConvertTo-Json $REST_RESULTS -Depth 50 | Out-File -FilePath "$OUTPUT_PATH\$FILENAME" Write-Debug "Issue Type Schema JSON file created: $OUTPUT_PATH\$FILENAME" - Return $REST_RESULTS + return $REST_RESULTS.values | ConvertTo-Json -Depth 50 -Compress } +function Get-JiraProjectByKey { + param ( + [Parameter(Mandatory = $true)] + [string]$PROJECT_KEY + ) + # Check for $($env:OSM_HOME)\$PROFILE_NAME\$PROFILE_NAME-ProjectList-*.json" that was written in past 12 hours and use it to get the project ID, else run Get-JiraProjectList, then try again + $PROJECT_LIST_FILE = Get-ChildItem -Path "$($env:OSM_HOME)\$env:AtlassianPowerKit_PROFILE_NAME\JIRA" -Filter "$env:AtlassianPowerKit_PROFILE_NAME-ProjectList-*.json" | Sort-Object -Property LastWriteTime -Descending | Select-Object -First 1 + While (-not $PROJECT_LIST_FILE) { + Write-Debug 'No Project List file found, running Get-JiraProjectList...' + Get-JiraProjectList | Out-Null + $PROJECT_LIST_FILE = Get-ChildItem -Path "$($env:OSM_HOME)\$env:AtlassianPowerKit_PROFILE_NAME\JIRA" -Filter "$env:AtlassianPowerKit_PROFILE_NAME-ProjectList-*.json" | Sort-Object -Property LastWriteTime -Descending | Select-Object -First 1 + } + $PROJECT = (Get-Content -Path $PROJECT_LIST_FILE.FullName | ConvertFrom-Json -AsHashtable -NoEnumerate) | Where-Object { $_.key -eq $PROJECT_KEY } + return $PROJECT | ConvertTo-Json -Depth 50 -Compress +} # Funtion to print the value project properties (JIRA entity) function Get-JiraProjectList { param ( - [Parameter(Mandatory = $false)] - [string]$PROJECT_KEY, [Parameter(Mandatory = $false)] [string]$OUTPUT_PATH = "$($env:OSM_HOME)\$($env:AtlassianPowerKit_PROFILE_NAME)\JIRA" ) $FILENAME = "$env:AtlassianPowerKit_PROFILE_NAME-ProjectList-$(Get-Date -Format 'yyyyMMdd-HHmmss').json" + $REST_RESULTS = @() $REST_RESPONSE = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/project/search" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get $REST_RESULTS += $REST_RESPONSE.values + Write-Debug 'Adding first page of projects to results...' while (!$REST_RESPONSE.isLast) { $REST_RESPONSE = Invoke-RestMethod -Uri $REST_RESPONSE.nextPage -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get + Write-Debug "Adding next page of projects to results...[$($REST_RESPONSE.startAt) / $($REST_RESPONSE.total)]" $REST_RESULTS += $REST_RESPONSE.values } - if ($PROJECT_KEY) { - $REST_RESULTS = $REST_RESULTS | Where-Object { $_.key -eq $PROJECT_KEY } - } ConvertTo-Json $REST_RESULTS -Depth 50 | Out-File -FilePath "$OUTPUT_PATH\$FILENAME" Write-Debug "Project List JSON file created: $OUTPUT_PATH\$FILENAME" - return $REST_RESULTS + return $REST_RESULTS | ConvertTo-Json -Depth 50 -Compress } function Get-JiraProjectProperties { param ( @@ -1322,8 +1399,8 @@ function Get-JiraProjectProperties { [string]$PROJECT_KEY ) $REST_RESULTS = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/project/$PROJECT_KEY/properties" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get - Write-Debug $REST_RESULTS.getType() - Write-Debug (ConvertTo-Json $REST_RESULTS -Depth 10) + $REST_RESULTS | ConvertTo-Json -Depth 100 | Out-File -FilePath "$env:OSM_HOME\$env:AtlassianPowerKit_PROFILE_NAME\JIRA\$env:AtlassianPowerKit_PROFILE_NAME-$PROJECT_KEY-ProjectProperties-$(Get-Date -Format 'yyyyMMdd-HHmmss').json" + return $REST_RESULTS | ConvertTo-Json -Depth 100 -Compress } # Funtion to print the value of a specific project property (JIRA entity) @@ -1697,13 +1774,30 @@ function Set-AttachedFormsExternalJQLQuery { function Get-FormsForJiraProject { param ( [Parameter(Mandatory = $true)] - [string]$PROJECT_KEY + [string]$PROJECT_KEY, + [Parameter(Mandatory = $false)] + [string]$OUTPUT_PATH = "$($env:OSM_HOME)\$($env:AtlassianPowerKit_PROFILE_NAME)\JIRA" ) + $FILENAME = "$env:AtlassianPowerKit_PROFILE_NAME-$PROJECT_KEY-Forms-$(Get-Date -Format 'yyyyMMdd-HHmmss').json" + $REST_RESULTS = @() + $REST_RESPONSE = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/project/search" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get + $REST_RESULTS += $REST_RESPONSE.values + + Write-Debug 'Adding first page of projects to results...' + while (!$REST_RESPONSE.isLast) { + $REST_RESPONSE = Invoke-RestMethod -Uri $REST_RESPONSE.nextPage -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get + Write-Debug "Adding next page of projects to results...[$($REST_RESPONSE.startAt) / $($REST_RESPONSE.total)]" + $REST_RESULTS += $REST_RESPONSE.values + } + ConvertTo-Json $REST_RESULTS -Depth 50 | Out-File -FilePath "$OUTPUT_PATH\$FILENAME" # https://api.atlassian.com/jira/forms/cloud/{cloudId}/project/{projectIdOrKey}/form $PROJECT_FORM_ID_URL = "https://api.atlassian.com/jira/forms/cloud/$($env:AtlassianPowerKit_CloudID)/project/$PROJECT_KEY/form" - $REST_RESULTS = Invoke-RestMethod -Uri $PROJECT_FORM_ID_URL -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get - Write-Debug $REST_RESULTS.getType() - Write-Debug (ConvertTo-Json $REST_RESULTS -Depth 10) + $PROJECT_FORM_INDEX = Invoke-RestMethod -Uri $PROJECT_FORM_ID_URL -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get + $PROJECT_FORM_INDEX | ConvertTo-Json -Depth 30 | Out-File -FilePath "$OUTPUT_PATH\$FILENAME" + Write-Debug "Project Form Index JSON file created: $OUTPUT_PATH\$FILENAME" + return $PROJECT_FORM_INDEX | ConvertTo-Json -Depth 50 -Compress + + } function Get-FormsForJiraIssue { param ( diff --git a/AtlassianPowerKit-Shared/AtlassianPowerKit-Shared.psm1 b/AtlassianPowerKit-Shared/AtlassianPowerKit-Shared.psm1 index 21e651a..1872c9d 100644 --- a/AtlassianPowerKit-Shared/AtlassianPowerKit-Shared.psm1 +++ b/AtlassianPowerKit-Shared/AtlassianPowerKit-Shared.psm1 @@ -369,47 +369,55 @@ function Set-AtlassianPowerKitProfile { [Parameter(Mandatory = $true)] [string]$SelectedProfileName ) - Write-Debug "Set-AtlassianPowerKitProfile - with: $SelectedProfileName ..." - # Load all profiles from the secret vault - if (!$(Get-SecretVault -Name $script:VAULT_NAME -ErrorAction SilentlyContinue)) { - Register-AtlassianPowerKitVault - } - # Check if the profile exists - $PROFILE_LIST = Get-AtlassianPowerKitProfileList - if (!$PROFILE_LIST.Contains($SelectedProfileName)) { - Write-Debug "Profile $SelectedProfileName does not exists in the vault - we have: $PROFILE_LIST, creating... $SelectedProfileName" - New-AtlassianPowerKitProfile -ProfileName $SelectedProfileName - return $false + if ($SelectedProfileName -eq $env:AtlassianPowerKit_PROFILE_NAME) { + Write-Debug "$SelectedProfileName is already loaded." + return $env:AtlassianPowerKit_PROFILE_NAME } else { - Write-Debug "Profile $SelectedProfileName exists in the vault, loading..." - try { - # if vault is locked, unlock it - Unlock-Vault -VaultName $script:VAULT_NAME - $PROFILE_DATA = (Get-Secret -Name $SelectedProfileName -Vault $script:VAULT_NAME -AsPlainText) - #Create environment variables for each item in the profile data - $PROFILE_DATA.GetEnumerator() | ForEach-Object { - Write-Debug "Setting environment variable: $($_.Key) = $($_.Value)" - # Create environment variable concatenated with AtlassianPowerKit_ prefix - $SetEnvar = '$env:AtlassianPowerKit_' + $_.Key + " = `"$($_.Value)`"" - Invoke-Expression -Command $SetEnvar | Out-Null - Write-Debug "Environment variable set: $SetEnvar" + Write-Debug "$SelectedProfileName not loaded, loading..." + # Load all profiles from the secret vault + if (!$(Get-SecretVault -Name $script:VAULT_NAME -ErrorAction SilentlyContinue)) { + Register-AtlassianPowerKitVault + } + # Check if the profile exists + $PROFILE_LIST = Get-AtlassianPowerKitProfileList + if (!$PROFILE_LIST.Contains($SelectedProfileName)) { + Write-Debug "Profile $SelectedProfileName does not exists in the vault - we have: $PROFILE_LIST, creating... $SelectedProfileName" + New-AtlassianPowerKitProfile -ProfileName $SelectedProfileName + return $false + } + else { + Write-Debug "Profile $SelectedProfileName exists in the vault, loading..." + try { + # if vault is locked, unlock it + Unlock-Vault -VaultName $script:VAULT_NAME + $PROFILE_DATA = (Get-Secret -Name $SelectedProfileName -Vault $script:VAULT_NAME -AsPlainText) + #Create environment variables for each item in the profile data + $PROFILE_DATA.GetEnumerator() | ForEach-Object { + Write-Debug "Setting environment variable: $($_.Key) = $($_.Value)" + # Create environment variable concatenated with AtlassianPowerKit_ prefix + $SetEnvar = '$env:AtlassianPowerKit_' + $_.Key + " = `"$($_.Value)`"" + Invoke-Expression -Command $SetEnvar | Out-Null + Write-Debug "Environment variable set: $SetEnvar" + } + } + catch { + Write-Debug "Failed to load profile $SelectedProfileName. Please check the vault key file." + throw "Failed to load profile $SelectedProfileName. Please check the vault key file." } - } - catch { - Write-Debug "Failed to load profile $SelectedProfileName. Please check the vault key file." - throw "Failed to load profile $SelectedProfileName. Please check the vault key file." + Set-AtlassianAPIHeaders + #Set-OpsgenieAPIHeaders + $env:AtlassianPowerKit_CloudID = $(Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/_edge/tenant_info").cloudId + Write-Debug "Profile $SelectedProfileName loaded successfully." } - Set-AtlassianAPIHeaders - #Set-OpsgenieAPIHeaders - $env:AtlassianPowerKit_CloudID = $(Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/_edge/tenant_info").cloudId - Write-Debug "Profile $SelectedProfileName loaded successfully." + $LOADED_PROFILE = Get-CurrentAtlassianPowerKitProfile + return $LOADED_PROFILE } - $LOADED_PROFILE = Get-CurrentAtlassianPowerKitProfile - return $LOADED_PROFILE + return $false } + # Function to test if AtlassianPowerKit profile authenticates successfully function Test-AtlassianPowerKitProfile { Write-Debug 'Testing Atlassian Cloud PowerKit Profile...' diff --git a/AtlassianPowerKit.psm1 b/AtlassianPowerKit.psm1 index f4b1021..32fe2d8 100644 --- a/AtlassianPowerKit.psm1 +++ b/AtlassianPowerKit.psm1 @@ -98,6 +98,35 @@ function Test-OSMHomeDir { return $ValidatedOSMHome } +# function Invoke-AtlassianPowerKitFunction { +# param ( +# [Parameter(Mandatory = $true)] +# [string] $FunctionName, +# [Parameter(Mandatory = $false)] +# [hashtable] $FunctionParameters +# ) +# $TEMP_DIR = "$env:OSM_HOME\$env:AtlassianPowerKit_PROFILE_NAME\.temp" +# if (-not (Test-Path $TEMP_DIR)) { +# New-Item -ItemType Directory -Path $TEMP_DIR -Force | Out-Null +# } +# $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() +# $stopwatch.Start() | Out-Null +# if ($FunctionParameters) { +# $singleLineDefinition = $FunctionParameters.Keys | ForEach-Object { "- -> $_ = $($FunctionParameters.($_))" } +# Write-Debug "Running function: $FunctionName with parameters: $singleLineDefinition" +# # Run the function with the parameters and capture the returned object +# $RETURN_OBJECT = $FunctionName @FunctionParameters | ConvertTo-Json -Depth 100 -Compress -EnumsAsStrings +# } +# else { +# $RETURN_OBJECT = $(Invoke-Expression "$FunctionName") +# } +# $stopwatch.Stop() | Out-Null +# Write-Debug "Function $FunctionName completed - execution time: $($stopwatch.Elapsed.TotalSeconds) seconds" +# $RETURN_JSON = $RETURN_OBJECT | ConvertTo-Json -Depth 100 -Compress +# Write-Debug "Returning JSON of size: $($RETURN_JSON.Length) characters" +# #$RETURN_JSON | ConvertTo-Json -Depth 50 | Write-Debug +# return $RETURN_JSON +# } function Invoke-AtlassianPowerKitFunction { param ( [Parameter(Mandatory = $true)] @@ -109,23 +138,38 @@ function Invoke-AtlassianPowerKitFunction { if (-not (Test-Path $TEMP_DIR)) { New-Item -ItemType Directory -Path $TEMP_DIR -Force | Out-Null } - $TIMESTAMP = Get-Date -Format 'yyyyMMdd-HHmmss' - $LOG_FILE = "$TEMP_DIR\$FunctionName-$TIMESTAMP.log" $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() - $stopwatch.Start() - if ($FunctionParameters) { - $singleLineDefinition = $FunctionParameters.Keys | ForEach-Object { "- -> $_ = $($FunctionParameters.($_))" } - Write-Debug "Running function: $FunctionName with parameters: $singleLineDefinition" - & $FunctionName @FunctionParameters + $stopwatch.Start() | Out-Null + + try { + if ($FunctionParameters) { + # Safely construct a debug message with hashtable keys and values + $singleLineDefinition = $FunctionParameters.Keys | ForEach-Object { '- ' + $_ + ": $($FunctionParameters[$_])" } + Write-Debug "Running function: $FunctionName with parameters: $singleLineDefinition" + + # Use Splatting (@) to pass parameters + $RETURN_OBJECT = & $FunctionName @FunctionParameters + } + else { + Write-Debug "Running function: $FunctionName without parameters" + $RETURN_OBJECT = & $FunctionName + } + + # Stop timing the function execution + $stopwatch.Stop() | Out-Null + Write-Debug "Function $FunctionName completed - execution time: $($stopwatch.Elapsed.TotalSeconds) seconds" + + # Convert the returned object to JSON + $RETURN_JSON = $RETURN_OBJECT + Write-Debug "Returning JSON of size: $($RETURN_JSON.Length) characters" } - else { - Invoke-Expression "$FunctionName" + catch { + Write-Debug "Error occurred while invoking function: $FunctionName" + Write-Debug $_ + $RETURN_JSON = "{'error': 'An error occurred while executing the function.', 'details': '$($_.Exception.Message)'}" } - $stopwatch.Stop() - Write-Output "Function $FunctionName completed - execution time: $($stopwatch.Elapsed.TotalSeconds) seconds" - Write-Output "Log file: $LOG_FILE" - Write-Output 'To run again, use the command: ' - Write-Output "Invoke-AtlassianPowerKitFunction -FunctionName $FunctionName -FunctionParameters @{ $singleLineDefinition }" + + return $RETURN_JSON } function Show-AdminFunctions { @@ -306,15 +350,15 @@ function AtlassianPowerKit { Write-Debug "OSM_HOME: $(Test-OSMHomeDir)" # If current directory is not the script root, push the script root to the stack if ($ResetVault) { - Clear-AtlassianPowerKitVault + Clear-AtlassianPowerKitVault | Out-Null return $true } if ($ArchiveProfileDirs) { - Clear-AtlassianPowerKitProfileDirs + Clear-AtlassianPowerKitProfileDirs | Out-Null return $true } if ($ClearProfile) { - Clear-AtlassianPowerKitProfile + Clear-AtlassianPowerKitProfile | Out-Null return $true } # If no profile name is provided, list the available profiles @@ -338,11 +382,13 @@ function AtlassianPowerKit { $FunctionParameters.GetEnumerator() | ForEach-Object { Write-Debug " -$($_.Key) $_.Value" } - Invoke-AtlassianPowerKitFunction -FunctionName $FunctionName -FunctionParameters $FunctionParameters + $RETURN_JSON = $(Invoke-AtlassianPowerKitFunction -FunctionName $FunctionName -FunctionParameters $FunctionParameters) + Write-Debug "AtlassianPowerKit Main: Received JSON of size: $($RETURN_JSON.Length) characters" } elseif ($FunctionName) { Write-Debug "AtlassianPowerKit Main: No parameters provided to the function, attempting to run the function without parameters: $FunctionName" - Invoke-AtlassianPowerKitFunction -FunctionName $FunctionName + $RETURN_JSON = $(Invoke-AtlassianPowerKitFunction -FunctionName $FunctionName) + Write-Debug "AtlassianPowerKit Main: Received JSON of size: $($RETURN_JSON.Length) characters" } } catch { @@ -353,10 +399,12 @@ function AtlassianPowerKit { Write-Debug '++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ AtlassianPowerKit Main: ' Write-Error $_.Exception.Message } - finally { - #Clear-AtlassianPowerKitProfile - Pop-Location - #Remove-Item 'env:AtlassianPowerKit_*' -ErrorAction Continue - Write-Debug 'Gracefully exited AtlassianPowerKit' - } + # finally { + # #Clear-AtlassianPowerKitProfile + # #Pop-Location + # #Remove-Item 'env:AtlassianPowerKit_*' -ErrorAction Continue + # #Write-Debug 'Gracefully exited AtlassianPowerKit' + # } + $RETURN_JSON | ConvertFrom-Json -Depth 100 | ConvertTo-Json -Depth 100 -Compress | Write-Debug + $RETURN_JSON } \ No newline at end of file diff --git a/AtlassinPowerKit-JIRAGRCosmDeploy/AtlassianPowerKit-JIRAGRCosmDeploy.psm1 b/AtlassinPowerKit-JIRAGRCosmDeploy/AtlassianPowerKit-JIRAGRCosmDeploy.psm1 index 4d21fac..9210021 100644 --- a/AtlassinPowerKit-JIRAGRCosmDeploy/AtlassianPowerKit-JIRAGRCosmDeploy.psm1 +++ b/AtlassinPowerKit-JIRAGRCosmDeploy/AtlassianPowerKit-JIRAGRCosmDeploy.psm1 @@ -17,3 +17,14 @@ GitHub: https://github.com/OrganisationServiceManagement/AtlassianPowerKit.git #> $ErrorActionPreference = 'Stop'; $DebugPreference = 'Continue' + +# Listing issue Types +function Get-MarkDownJIRAIssueTypes { + param ( + [P ] + [Parameter(Mandatory = $false)] + [string]$OUTPUT_PATH = "$($env:OSM_HOME)\$($env:AtlassianPowerKit_PROFILE_NAME)\JIRA" + ) +} + +Get-Content .\cpk-HROSM-IssueTypes-20241117-145239.json | ConvertFrom-Json -Depth 100 | ForEach-Object { Write-Host "- [$($_.Name)]($($_.self)) - [Show All Instances](https://cpksystems.atlassian.net/jira/servicedesk/projects/HROSM/issues/?jql=project%20%3D%20HROSM%20AND%20issuetype%20%3D%20%22$($_.name.replace(' ','%20'))%22)" From 8067d371efc3979ec24f8fdbc292c7d10d902c72 Mon Sep 17 00:00:00 2001 From: Mark Culhane Date: Fri, 10 Jan 2025 19:35:42 +1100 Subject: [PATCH 04/11] synching updates --- .../AtlassianPowerKit-Admin.psd1 | 19 +- .../AtlassianPowerKit-Admin.psm1 | 431 ++++++++++ .../AtlassianPowerKit-Confluence.psm1 | 79 +- .../AtlassianPowerKit-Jira.psd1 | 2 +- .../AtlassianPowerKit-Jira.psm1 | 252 +----- AtlassianPowerKit-Jira/Get-FixedADFJson.ps1 | 46 ++ AtlassianPowerKit-Jira/JIRA_ADF_README.md | 22 + .../JIRA_EXPORT_API_BROKEN_VS_ADF.json | 578 ++++++++++++++ AtlassianPowerKit-Jira/JiraADFJsonSchema.json | 0 AtlassianPowerKit-Jira/OP-999.json | 752 ++++++++++++++++++ .../OP-999_JQLResult_PartialFile.json | 565 +++++++++++++ AtlassianPowerKit-Jira/SSG_REQ_FIELDMAP.json | 5 + .../AtlassianPowerKit-Shared.psd1 | 1 + .../AtlassianPowerKit-Shared.psm1 | 89 ++- AtlassianPowerKit.psm1 | 35 +- .../AtlassianPowerKit-JIRAGRCosmDeploy.psm1 | 30 - Run.ps1 | 92 ++- 17 files changed, 2610 insertions(+), 388 deletions(-) rename AtlassinPowerKit-JIRAGRCosmDeploy/AtlassianPowerKit-JIRAGRCosmDeploy.psd1 => AtlassianPowerKit-Admin/AtlassianPowerKit-Admin.psd1 (90%) create mode 100644 AtlassianPowerKit-Admin/AtlassianPowerKit-Admin.psm1 create mode 100644 AtlassianPowerKit-Jira/Get-FixedADFJson.ps1 create mode 100644 AtlassianPowerKit-Jira/JIRA_ADF_README.md create mode 100644 AtlassianPowerKit-Jira/JIRA_EXPORT_API_BROKEN_VS_ADF.json create mode 100644 AtlassianPowerKit-Jira/JiraADFJsonSchema.json create mode 100644 AtlassianPowerKit-Jira/OP-999.json create mode 100644 AtlassianPowerKit-Jira/OP-999_JQLResult_PartialFile.json create mode 100644 AtlassianPowerKit-Jira/SSG_REQ_FIELDMAP.json delete mode 100644 AtlassinPowerKit-JIRAGRCosmDeploy/AtlassianPowerKit-JIRAGRCosmDeploy.psm1 diff --git a/AtlassinPowerKit-JIRAGRCosmDeploy/AtlassianPowerKit-JIRAGRCosmDeploy.psd1 b/AtlassianPowerKit-Admin/AtlassianPowerKit-Admin.psd1 similarity index 90% rename from AtlassinPowerKit-JIRAGRCosmDeploy/AtlassianPowerKit-JIRAGRCosmDeploy.psd1 rename to AtlassianPowerKit-Admin/AtlassianPowerKit-Admin.psd1 index abec0ef..e30b59e 100644 --- a/AtlassinPowerKit-JIRAGRCosmDeploy/AtlassianPowerKit-JIRAGRCosmDeploy.psd1 +++ b/AtlassianPowerKit-Admin/AtlassianPowerKit-Admin.psd1 @@ -9,7 +9,7 @@ @{ # Script module or binary module file associated with this manifest. - RootModule = 'AtlassianPowerKit-JIRAGRCosmDeploy.psm1' + RootModule = 'AtlassianPowerKit-Admin.psm1' # Version number of this module. ModuleVersion = '1.0.0' @@ -18,7 +18,7 @@ # CompatiblePSEditions = @() # ID used to uniquely identify this module - GUID = 'c5cbd86b-5b61-45e9-b4b9-4e30d2144270' + GUID = 'c93fe103-590b-4a69-b4e1-09fcc140ef99' # Author of this module Author = 'Mark Culhane' @@ -70,11 +70,16 @@ # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. FunctionsToExport = @( - 'Get-SharePointFileMetadata', - 'New-ConfluencePolicyViewerSharePoint', - 'Get-OSMPlaceholdersJira', - 'Get-OSMPlaceholdersConfluence', - 'Update-GRCosmConfRegister' + 'New-JiraIssueType', + 'Import-JiraIssueTypes', + 'Test-ExistingConfigJSON', + 'Import-JSONConfigExport', + 'Get-OSMDeploymentConfigsJIRA', + 'Get-OSMConfigAsMarkdown', + 'Get-JiraProjectIssueTypes', + 'Get-JiraCloudIssueTypeSchema' + 'Get-JiraCloudIssueTypeMetadata', + 'Get-JiraOSMFilterList' ) # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. diff --git a/AtlassianPowerKit-Admin/AtlassianPowerKit-Admin.psm1 b/AtlassianPowerKit-Admin/AtlassianPowerKit-Admin.psm1 new file mode 100644 index 0000000..efc9973 --- /dev/null +++ b/AtlassianPowerKit-Admin/AtlassianPowerKit-Admin.psm1 @@ -0,0 +1,431 @@ +<# +.SYNOPSIS + Atlassian Cloud PowerShell Module - AtlassianPowerKit-JIRAGRCosmDeploy - module for creating new issue types, workflows, fields, screens, screen schemes, issue type screen schemes, and issue type screen scheme associations using the Atlassian Jira Cloud REST API.See https://developer.atlassian.com/cloud/jira/platform/rest/v3/ for more information. + +.DESCRIPTION + Atlassian Cloud PowerShell Module - AtlassianPowerKit-JIRAGRCosmDeploy + - Dependencies: AtlassianPowerKit-Shared + - New-AtlassianAPIEndpoint + For list of functions and cmdlets, run Get-Command -Module AtlassianPowerKit-JIRAGRCosmDeploy.psm1 + +.EXAMPLE + New-JiraIssueType -JiraCloudProjectKey 'OSM' -JiraIssueTypeName 'Test Issue Type' -JiraIssueTypeDescription 'This is a test issue type.' -JiraIssueTypeAvatarId '10000' + +.LINK +GitHub: https://github.com/OrganisationServiceManagement/AtlassianPowerKit.git + +#> + +$ErrorActionPreference = 'Stop'; $DebugPreference = 'Continue' +# Directory of this file +Import-Module "$env:OSM_INSTALL\AtlassianPowerKit\AtlassianPowerKit-Shared\AtlassianPowerKit-Shared.psd1" -Force +Import-Module "$env:OSM_INSTALL\AtlassianPowerKit\AtlassianPowerKit-Jira\AtlassianPowerKit-Jira.psd1" -Force +$RETRY_AFTER = 10 + +# Function to create a new Jira Issue Typess +function New-JiraIssueType { + param ( + [Parameter(Mandatory = $true)] + [string]$JiraIssueTypeName, + [Parameter(Mandatory = $true)] + [string]$JiraIssueTypeDescription, + [Parameter(Mandatory = $true)] + [string]$JiraIssueTypeAvatarId, + [Parameter(Mandatory = $true)] + [int]$JiraIssueHierarchyLevel, + [Parameter(Mandatory = $false)] + [string]$ExistingJiraIssueTypeList = $null + ) + # First check if the issue type already exists by name + $ExistingJiraIssueType = $null + if ( ! $ExistingJiraIssueTypeList ) { + Write-Debug 'Getting existing Jira Issue Type list as it was not provided...' + $ExistingJiraIssueTypeList = Invoke-RestMethod -Method Get -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/issuetype" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) + } + Write-Debug "Existing Jira Issue Type Count: $($ExistingJiraIssueTypeList.Count)" + $ExistingJiraIssueType = $ExistingJiraIssueTypeList | ConvertFrom-Json | Where-Object { $_.name -eq "$JiraIssueTypeName" } + Write-Debug "Existing Jira Issue Type Count [MATCH]: $ExistingJiraIssueType" + if ($ExistingJiraIssueType) { + Write-Debug "Issue type $($JiraIssueType.name) already exists. Returning existing issue type." + # Ensure the ExistingJiraIssueType object is a single object + if ($ExistingJiraIssueType.Count -gt 1) { + Write-Warn "Multiple issue types found with the name: $JiraIssueTypeName. Returning the first issue type." + $ExistingJiraIssueType = $ExistingJiraIssueType[0] + } + } + else { + # Create a JSON object for the new issue type using the $JiraIssueType fields: name, description, hierarchyLevel, avatarId (removing the other fields) + $NewJiraIssueType = @{ + name = $JiraIssueTypeName + description = $JiraIssueTypeDescription + heirarchyLevel = $JiraIssueHierarchyLevel + } + Write-Debug "Creating issue type $($JiraIssueType.name)...: " + $NewJiraIssueType | ConvertTo-Json -Depth 10 | Write-Debug + $CreatedJiraIssueType = Invoke-RestMethod -Method Post -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/issuetype" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Body $($NewJiraIssueType | ConvertTo-Json -Depth 10) -ContentType 'application/json' + Write-Debug "Issue type $($CreatedJiraIssueType.name) created." + # Update the Avatar for the new issue type + $JiraIssueAvatarUpdateBody = @{ + avatarId = $JiraIssueTypeAvatarId + } + $JiraIssueAvatarUpdateBody = $JiraIssueAvatarUpdateBody | ConvertTo-Json -Depth 10 + $CreatedJiraIssueType = Invoke-RestMethod -Method Put -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/issuetype/$($CreatedJiraIssueType.id)" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Body $($NewJiraIssueType | ConvertTo-Json -Depth 10) -ContentType 'application/json' + Write-Debug "Issue type $JiraIssueTypeName avatar updated." + $ExistingJiraIssueType = $CreatedJiraIssueType + } + Return $ExistingJiraIssueType | ConvertTo-Json -Depth 100 -Compress +} + + +# Function to load issue types from a JSON file +function Import-JiraIssueTypes { + param ( + [Parameter(Mandatory = $true)] + [string]$JiraIssueTypesJSONFile + ) + $ExistingJiraIssueTypes = Invoke-RestMethod -Method Get -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/issuetype" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) | ConvertTo-Json -Depth 100 -Compress + $ImportIssueList = Get-Content -Path $JiraIssueTypesJSONFile | ConvertFrom-Json -AsHashtable + $DeployedIssueTypes = $ImportIssueList | ForEach-Object { + $NewIssueType = $_ + New-JiraIssueType -JiraIssueTypeName $NewIssueType.name -JiraIssueTypeDescription $NewIssueType.description -JiraIssueTypeAvatarId $NewIssueType.avatarId -JiraIssueHierarchyLevel $NewIssueType.heirarchyLevel -ExistingJiraIssueTypeList $ExistingJiraIssueTypes + } + Return $DeployedIssueTypes | ConvertFrom-Json -AsHashtable -NoEnumerate | ConvertTo-Json -Depth 100 -Compress +} + +# Function to create +function Test-ExistingConfigJSON { + param ( + [Parameter(Mandatory = $true)] + [string]$CONFIG_FILE_PATHPATTERN + ) + $CONFIG_FILE = Get-ChildItem -Path $CONFIG_FILE_PATHPATTERN | Where-Object { $_.LastWriteTime -gt (Get-Date).AddHours(-12) } + if ($CONFIG_FILE) { + $CONFIG_FILE + } + else { + $null + } +} +# Returns JSON path that can be loaded with Get-Content $(Import-JSONConfigExport) | ConvertFrom-Json -AsHashtable -NoEnumerate +function Import-JSONConfigExport { + $FULL_CONFIG_OUTPUT_JSONFILE = $null + # Advise user of the age of the existing JSON export and ask if they want to use it defaulting to 'Yes' + while (! $FULL_CONFIG_OUTPUT_JSONFILE) { + $EXISTING_JSON_EXPORT_LIST = Get-ChildItem -Path "$OUTPUT_PATH\FULL-$PROFILE_NAME-*.json" | Sort-Object -Property LastWriteTime -Descending + if ($EXISTING_JSON_EXPORT_LIST -and $EXISTING_JSON_EXPORT_LIST.Count -gt 0) { + # If LastWriteTime is less than 12 hours ago, use it + if ($EXISTING_JSON_EXPORT_LIST[0].LastWriteTime -gt (Get-Date).AddHours(-12)) { + $LATEST_EXISTING_JSON_EXPORT = $EXISTING_JSON_EXPORT_LIST[0] + Write-Debug "Fresh, existing JSON export: $($LATEST_EXISTING_JSON_EXPORT.FullName)" + $FULL_CONFIG_OUTPUT_JSONFILE = $LATEST_EXISTING_JSON_EXPORT.FullName + } + } + else { + Write-Debug "$($MyInvocation.MyCommand.Name): Creating new FULL DEPLOYMENT CONFIG json file using: Get-OSMDeploymentConfigsJIRA -PROFILE_NAME $PROFILE_NAME" + $RAW_CONFIG_JSON = Get-OSMDeploymentConfigsJIRA -PROFILE_NAME $PROFILE_NAME | ConvertFrom-Json -Depth 100 + $FULL_CONFIG_OUTPUT_JSONFILE = "$OUTPUT_PATH\FULL-$PROFILE_NAME-$(Get-Date -Format 'yyyyMMdd-HHmm').json" + $RAW_CONFIG_JSON | ConvertTo-Json -Depth 100 | Out-File -FilePath $FULL_CONFIG_OUTPUT_JSONFILE -Force | Out-Null + Write-Debug "Output written to $FULL_CONFIG_OUTPUT_JSONFILE" + } + } + Return $FULL_CONFIG_OUTPUT_JSONFILE +} + + +# Listing issue Types +function Get-OSMConfigAsMarkdown { + param ( + [Parameter(Mandatory = $false)] + [string]$OUTPUT_PATH = "$($env:OSM_HOME)\$($env:AtlassianPowerKit_PROFILE_NAME)\JIRA", + [Parameter(Mandatory = $false)] + [string]$PROFILE_NAME = $env:AtlassianPowerKit_PROFILE_NAME + ) + $OUTPUT_FILE = "$OUTPUT_PATH\$PROFILE_NAME-OSM-Config_$(Get-Date -Format 'yyyyMMdd-HHmm').md" + $INPUT_JSON_FILE = Import-JSONConfigExport + $RAW_CONFIG_JSON = Get-Content -Path "$INPUT_JSON_FILE" -Raw | ConvertFrom-Json -Depth 100 + # Write the markdown file + $RAW_CONFIG_JSON | ForEach-Object { + if ($_ -ne $null) { + $PROJECT_NAME = if ($null -ne $_.PROJECT_NAME) { $_.PROJECT_NAME } else { 'Unknown Project' } + $PROJECT_KEY = if ($null -ne $_.PROJECT_KEY) { $_.PROJECT_KEY } else { 'Unknown Key' } + $PROJECT_ISSUE_TYPE_SCHEMA = if ($null -ne $_.PROJECT_ISSUE_TYPE_SCHEMA -and $null -ne $_.PROJECT_ISSUE_TYPE_SCHEMA.self) { $null -ne $_.PROJECT_ISSUE_TYPE_SCHEMA } else { @{ self = '#' } } + $PROJECT_ISSUE_TYPES = if ($null -ne $_.PROJECT_ISSUE_TYPES) { $_.PROJECT_ISSUE_TYPES } else { @() } + $PROJECT_REQUEST_TYPES = if ($null -ne $_.PROJECT_REQUEST_TYPES) { $_.PROJECT_REQUEST_TYPES } else { @() } + $PROJECT_WORKFLOWS_SCHEMES = if ($null -ne $_.PROJECT_WORKFLOWS_SCHEMES) { $_.PROJECT_WORKFLOWS_SCHEMES } else { @() } + # Write output for project details + Write-Output "## [$PROJECT_NAME]($($PROJECT_ISSUE_TYPE_SCHEMA.self)) - [Show All Instances](https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/jira/servicedesk/projects/$PROJECT_KEY/issues/?jql=project%20%3D%20$PROJECT_KEY)" + # Write Issue Types + Write-Output '### Issue Types' + $PROJECT_ISSUE_TYPES | ForEach-Object { + if ($_ -ne $null -and $null -ne $_.Name -and $null -ne $_.self) { + Write-Output "- [$($_.Name)]($($_.self)) - [Show All Instances](https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/jira/servicedesk/projects/$PROJECT_KEY/issues/?jql=project%20%3D%20$PROJECT_KEY%20AND%20issuetype%20%3D%20%22$($_.name.replace(' ','%20'))%22)" + } + else { + Write-Output '- Invalid or missing issue type' + } + } + # Write Request Types + Write-Output '### Request Types' + $PROJECT_REQUEST_TYPES | ForEach-Object { + if ($_ -ne $null -and $null -ne $_.Name -and $null -ne $_.self) { + Write-Output "- [$($_.Name)]($($_.self)) - [Show All Instances](https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint).atlassian.net/jira/servicedesk/projects/$PROJECT_KEY/issues/?jql=project%20%3D%20$PROJECT_KEY%20AND%20issuetype%20%3D%20%22$($_.name.replace(' ','%20'))%22)" + } + else { + Write-Output '- Invalid or missing request type' + } + } + # Write Workflow Schemes + Write-Output '### Workflow Schemes' + $PROJECT_WORKFLOWS_SCHEMES | ForEach-Object { + if ($_ -ne $null -and $null -ne $_.Name -and $null -ne $_.self) { + Write-Output "- [$($_.Name)]($($_.self)) - [Show All Instances](https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint).atlassian.net/jira/servicedesk/projects/$PROJECT_KEY/issues/?jql=project%20%3D%20$PROJECT_KEY%20AND%20issuetype%20%3D%20%22$($_.name.replace(' ','%20'))%22)" + } + } + } + } | Out-File -FilePath $OUTPUT_FILE -Force + Write-Debug "Output written to $OUTPUT_FILE" + $JSON_RETURN = @{ OUTPUT_FILE = $OUTPUT_FILE + OUTPUT_PATH = $OUTPUT_PATH + STATUS = 'SUCCESS' + PROFILE_NAME = $PROFILE_NAME + } + + Return $JSON_RETURN | ConvertTo-Json -Depth 100 -Compress +} + +# Function Get Deployment +Function Get-OSMDeploymentConfigsJIRA { + param ( + [Parameter(Mandatory = $false)] + [string]$PROFILE_NAME = $env:AtlassianPowerKit_PROFILE_NAME, + [Parameter(Mandatory = $false)] + [string]$OUTPUT_PATH = "$($env:OSM_HOME)\$PROFILE_NAME\JIRA" + ) + $OUTPUT_FILE = "$OUTPUT_PATH\FULL-$PROFILE_NAME-$(Get-Date -Format 'yyyyMMdd-HHmm').json" + Write-Host "Processing profile: $PROFILE_NAME" + # If there is a env:AtlassianPowerKit_PROFILE_NAME-ProjectList-*.json that was created in the last 12 hours, use it + $PROFILE_PROJECT_LIST = Test-ExistingConfigJSON -CONFIG_FILE_PATHPATTERN "$($env:OSM_HOME)\$PROFILE_NAME\$PROFILE_NAME-ProjectList-*.json" + if ($PROFILE_PROJECT_LIST) { + $PROJECT_LIST = Get-Content $PROFILE_PROJECT_LIST.FullName | ConvertFrom-Json -AsHashtable -NoEnumerate + } + else { + $PROJECT_LIST = AtlassianPowerKit -Profile $PROFILE_NAME -FunctionName 'Get-JiraProjectList' | ConvertFrom-Json -AsHashtable -NoEnumerate + } + #$PROJECT_LIST | ForEach-Object { Write-Host "Project: $($_.name) - $($_.key)" } + #$PROJECT_LIST | ConvertTo-Json -Depth 100 | Write-Debug + $OSM_PROJECT_LIST = $PROJECT_LIST | Where-Object { $_.key -match '.*OSM.*' -and $_.key -notin @('CUBOSM') } + + $JIRA_PROJECTS = $OSM_PROJECT_LIST | ForEach-Object { + $PROJECT_NAME = $($_.name) + $PROJECT_KEY = $($_.key) + # PROJECT_PROPERTIES + $PROFILE_PROJECT_PROPERTIES = Test-ExistingConfigJSON -CONFIG_FILE_PATHPATTERN "$($env:OSM_HOME)\$PROFILE_NAME\$PROFILE_NAME-$PROJECT_KEY-ProjectProperties-*.json" + if ($PROFILE_PROJECT_PROPERTIES) { + $PROFILE_PROJECT_PROPERTIES = Get-Content $PROFILE_PROJECT_PROPERTIES.FullName | ConvertFrom-Json -AsHashtable -NoEnumerate + } + else { + $PROFILE_PROJECT_PROPERTIES = AtlassianPowerKit -Profile $PROFILE_NAME -FunctionName 'Get-JiraProjectProperties' -FunctionParameters @{ PROJECT_KEY = $PROJECT_KEY } | ConvertFrom-Json -AsHashtable -NoEnumerate + } + # PROJECT_ISSUE_TYPE_SCHEMA + $PROJECT_ISSUE_TYPE_SCHEMA = Test-ExistingConfigJSON -CONFIG_FILE_PATHPATTERN "$($env:OSM_HOME)\$PROFILE_NAME\$PROFILE_NAME-$PROJECT_KEY-IssueTypeSchema-*.json" + if ($PROJECT_ISSUE_TYPE_SCHEMA) { + $PROJECT_ISSUE_TYPE_SCHEMA = Get-Content $PROJECT_ISSUE_TYPE_SCHEMA.FullName | ConvertFrom-Json -AsHashtable -NoEnumerate + } + else { + $PROJECT_ISSUE_TYPE_SCHEMA = AtlassianPowerKit -Profile $PROFILE_NAME -FunctionName 'Get-JiraCloudIssueTypeSchema' -FunctionParameters @{ PROJECT_KEY = $PROJECT_KEY } | ConvertFrom-Json -AsHashtable -NoEnumerate + } + # + # PROJECT_ISSUE_TYPES + $PROJECT_ISSUE_TYPES = Test-ExistingConfigJSON -CONFIG_FILE_PATHPATTERN "$($env:OSM_HOME)\$PROFILE_NAME\$PROFILE_NAME-$PROJECT_KEY-ProjectIssueTypes-*.json" + if ($PROJECT_ISSUE_TYPES) { + $PROJECT_ISSUE_TYPES = Get-Content $PROJECT_ISSUE_TYPES.FullName | ConvertFrom-Json -AsHashtable -NoEnumerate + } + else { + $PROJECT_ISSUE_TYPES = AtlassianPowerKit -Profile $PROFILE_NAME -FunctionName 'Get-JiraProjectIssueTypes' -FunctionParameters @{ PROJECT_KEY_OR_ID = $PROJECT_KEY } | ConvertFrom-Json -AsHashtable -NoEnumerate + } + $PROJECT_REQUEST_TYPES = Test-ExistingConfigJSON -CONFIG_FILE_PATHPATTERN "$($env:OSM_HOME)\$PROFILE_NAME\$PROFILE_NAME-$PROJECT_KEY-RequestTypeSchema-*.json" + if ($PROJECT_REQUEST_TYPES) { + $PROJECT_REQUEST_TYPES = Get-Content $PROJECT_REQUEST_TYPES.FullName | ConvertFrom-Json -AsHashtable -NoEnumerate + } + else { + $PROJECT_REQUEST_TYPES = AtlassianPowerKit -Profile $PROFILE_NAME -FunctionName 'Get-JiraServiceDeskRequestTypes' -FunctionParameters @{ PROJECT_KEY = $PROJECT_KEY } | ConvertFrom-Json -AsHashtable -NoEnumerate + } + + # FORMS + $PROJECT_FORMS = Test-ExistingConfigJSON -CONFIG_FILE_PATHPATTERN "$($env:OSM_HOME)\$PROFILE_NAME\$PROFILE_NAME-$PROJECT_KEY-Forms-*.json" + if ($PROJECT_FORMS) { + $PROJECT_FORMS = Get-Content $PROJECT_FORMS.FullName | ConvertFrom-Json -AsHashtable -NoEnumerate + } + else { + $PROJECT_FORMS = AtlassianPowerKit -Profile $PROFILE_NAME -FunctionName 'Get-FormsForJiraProject' -FunctionParameters @{ PROJECT_KEY = $PROJECT_KEY } | ConvertFrom-Json -AsHashtable -NoEnumerate + } + + # $FORMS = AtlassianPowerKit -Profile $PROFILE_NAME -FunctionName 'Get-FormsForJiraProject' -FunctionParameters @{ PROJECT_KEY = $PROJECT_KEY } + # WORKFLOW_SCHEMES + $PROJECT_WORKFLOWS_SCHEMES = Test-ExistingConfigJSON -CONFIG_FILE_PATHPATTERN "$($env:OSM_HOME)\$PROFILE_NAME\$PROFILE_NAME-$PROJECT_KEY-ProjectWorkflowSchemes-*.json" + if ($PROJECT_WORKFLOWS_SCHEMES) { + $PROJECT_WORKFLOWS_SCHEMES = Get-Content $PROJECT_WORKFLOWS_SCHEMES.FullName | ConvertFrom-Json -AsHashtable -NoEnumerate + } + else { + $PROJECT_WORKFLOWS_SCHEMES = AtlassianPowerKit -Profile $PROFILE_NAME -FunctionName 'Get-JiraProjectWorkflowSchemes' -FunctionParameters @{ PROJECT_KEY = $PROJECT_KEY } | ConvertFrom-Json -AsHashtable -NoEnumerate + } + + # Return object + [PSCustomObject]@{ + PROJECT_NAME = $PROJECT_NAME + PROJECT_KEY = $PROJECT_KEY + PROJECT_ISSUE_TYPE_SCHEMA = $PROJECT_ISSUE_TYPE_SCHEMA + PROJECT_ISSUE_TYPES = $PROJECT_ISSUE_TYPES + PROJECT_REQUEST_TYPES = $PROJECT_REQUEST_TYPES + PROJECT_WORKFLOWS_SCHEMES = $PROJECT_WORKFLOWS_SCHEMES + } + } + $JIRA_PROJECTS | ConvertTo-Json -Depth 100 -Compress | Out-File -FilePath $OUTPUT_FILE -Force | Out-Null + Return $OUTPUT_FILE +} + +# Funtion to list project properties (JIRA entities) +function Get-JiraProjectIssueTypes { + param ( + [Parameter(Mandatory = $true)] + [string]$PROJECT_KEY_OR_ID, + [Parameter(Mandatory = $false)] + [string]$OUTPUT_PATH = "$($env:OSM_HOME)\$($env:AtlassianPowerKit_PROFILE_NAME)\JIRA" + ) + # If the AtlassianPowerKit-J + if ($PROJECT_KEY_OR_ID -match '^\d+$') { + $PROJECT_ID = $PROJECT_KEY_OR_ID + } + else { + # Get the most recent auda-ProjectList-*.json in the $OUTPUT_PATH or run Get-JiraProjectList and check again for the file + $PROJECT_LIST_FILE = Get-ChildItem -Path $OUTPUT_PATH -Filter "$env:AtlassianPowerKit_PROFILE_NAME-ProjectList-*.json" | Sort-Object -Property LastWriteTime -Descending | Select-Object -First 1 + While (-not $PROJECT_LIST_FILE) { + Write-Debug 'No Project List file found, running Get-JiraProjectList...' + Get-JiraProjectList -OUTPUT_PATH $OUTPUT_PATH + $PROJECT_LIST_FILE = Get-ChildItem -Path $OUTPUT_PATH -Filter "$env:AtlassianPowerKit_PROFILE_NAME-ProjectList-*.json" | Sort-Object -Property LastWriteTime -Descending | Select-Object -First 1 + } + $PROJECT_ID = (Get-Content -Path $PROJECT_LIST_FILE.FullName | ConvertFrom-Json | Where-Object { $_.key -eq $PROJECT_KEY_OR_ID }).id + } + $FILENAME = "$env:AtlassianPowerKit_PROFILE_NAME-$PROJECT_KEY_OR_ID-IssueTypes-$(Get-Date -Format 'yyyyMMdd-HHmmss').json" + if (-not (Test-Path $OUTPUT_PATH)) { + New-Item -ItemType Directory -Path $OUTPUT_PATH -Force | Out-Null + } + $OUTPUT_FILE = "$OUTPUT_PATH\$FILENAME" + # Use Get-PaginatedResults to get all issues types for the project + Write-Debug "Getting Jira Project Issue Types for project: $PROJECT_ID ..." + $REST_RESULTS = Get-PaginatedJSONResults -URI "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/issuetype/project?projectId=$PROJECT_ID" -Method Get + Write-Debug "Jira Project Issue Types for project: $PROJECT_ID received... writing to file..." + $REST_RESULTS | ConvertTo-Json -Depth 50 | Out-File -FilePath $OUTPUT_FILE + Write-Debug "Jira Project Issue Types written to: $OUTPUT_FILE" + return $REST_RESULTS | ConvertTo-Json -Depth 100 -Compress +} + +# Function to get issue type metadata for a Jira Cloud project +function Get-JiraCloudIssueTypeMetadata { + param ( + [Parameter(Mandatory = $true)] + [string]$PROJECT_KEY, + [Parameter(Mandatory = $false)] + [string]$OUTPUT_PATH = "$($env:OSM_HOME)\$($env:AtlassianPowerKit_PROFILE_NAME)\JIRA" + ) + $FILENAME = "$env:AtlassianPowerKit_PROFILE_NAME-$PROJECT_KEY-IssueTypeMetadata-$(Get-Date -Format 'yyyyMMdd-HHmmss').json" + $REST_RESULTS = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/issue/createmeta/$PROJECT_KEY&expand=projects.issuetypes.fields" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get + ConvertTo-Json $REST_RESULTS -Depth 50 | Out-File -FilePath "$OUTPUT_PATH\$FILENAME" + Write-Debug "Issue Type Metadata JSON file created: $OUTPUT_PATH\$FILENAME" + Return $REST_RESULTS +} + +# Fuction to get Issue Type schema for a Jira Cloud project +function Get-JiraCloudIssueTypeSchema { + param ( + [Parameter(Mandatory = $true)] + [string]$PROJECT_KEY_OR_ID, + [Parameter(Mandatory = $false)] + [string]$OUTPUT_PATH = "$($env:OSM_HOME)\$($env:AtlassianPowerKit_PROFILE_NAME)\JIRA" + ) + # if the project key is passed, get the project ID (key is Alpha-numeric, ID is numeric) + if ($PROJECT_KEY_OR_ID -match '^\d+$') { + Write-Debug "Project ID passed: $PROJECT_KEY_OR_ID" + $PROJECT_ID = $PROJECT_KEY_OR_ID + $PROJECT_KEY = (Get-JiraProjectByKey -PROJECT_KEY $PROJECT_KEY_OR_ID | ConvertFrom-Json -AsHashtable -NoEnumerate).key + } + else { + Write-Debug "Project Key passed: $PROJECT_KEY_OR_ID ... getting project ID..." + $PROJECT_OBJECT = Get-JiraProjectByKey -PROJECT_KEY $PROJECT_KEY_OR_ID | ConvertFrom-Json -AsHashtable -NoEnumerate + #ConvertTo-Json $PROJECT_OBJECT -Depth 50 | Write-Debug + if ($PROJECT_OBJECT.id) { + $PROJECT_ID = $PROJECT_OBJECT.id + } + else { + Write-Error "Project ID not found for project key: $PROJECT_KEY_OR_ID" + } + } + $FILENAME = "$env:AtlassianPowerKit_PROFILE_NAME-$PROJECT_KEY-IssueTypeSchema-$(Get-Date -Format 'yyyyMMdd-HHmmss').json" + $REST_RESULTS = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/issuetypescheme/project?projectId=$PROJECT_ID" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get + ConvertTo-Json $REST_RESULTS -Depth 50 | Out-File -FilePath "$OUTPUT_PATH\$FILENAME" + Write-Debug "Issue Type Schema JSON file created: $OUTPUT_PATH\$FILENAME" + return $REST_RESULTS.values | ConvertTo-Json -Depth 50 -Compress +} + +function Get-FilterJQL { + param ( + [Parameter(Mandatory = $true)] + [string]$FILTER_ID + ) + # While response code is 429, wait and try again + try { + $REST_RESPONSE = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/filter/$($FILTER_ID)" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get -ContentType 'application/json' + } + catch { + # Catch 429 errors and wait for the retry-after time + if ($_.Exception.Response.StatusCode -eq 429) { + Write-Warn "429 error, waiting for $RETRY_AFTER seconds..." + Start-Sleep -Seconds $RETRY_AFTER + $REST_RESPONSE = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/filter/$($FILTER_ID)" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get -ContentType 'application/json' + } + else { + Write-Debug "$($MyInvocation.MyCommand.Name): Error getting filter JQL: $($_.Exception.Message)" + Write-Error "Error getting filter JQL: $($_.Exception.Message)" + } + } + Return $REST_RESPONSE.jql +} + +function Get-JiraOSMFilterList { + param ( + [Parameter(Mandatory = $false)] + [string[]]$PROJECT_KEYS = @('GRCOSM') + ) + $FILTERS_SEARCH_URL = "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/filter/search" + $PROJECT_LIST = Get-JiraProjectList | ConvertFrom-Json + # Get Project ID project with key GRCOSM + $SEARCH_TERMS_FOR_FILTERS = @( + @{ 'Name' = 'filterName'; 'Value' = 'osm' }) + $PROJECT_ID_SEARCHES = $PROJECT_KEYS | ForEach-Object { + $PROJECT_KEY = $_ + $PROJECT_ID = $PROJECT_LIST | Where-Object { $_.key -eq $PROJECT_KEY } | Select-Object -ExpandProperty id + if ($PROJECT_ID) { + Return @{ 'Name' = 'projectId'; 'Value' = $PROJECT_ID } + } + } + $SEARCH_TERMS_FOR_FILTERS += $PROJECT_ID_SEARCHES + # Write-Debug 'Searching for filters with search terms: ' + # $SEARCH_TERMS_FOR_FILTERS | ConvertTo-Json -Depth 100 | Write-Debug + # Write-Debug 'Attempting to get results using Get-PaginatedJSONResults...' + $FILTER_RESULTS = $SEARCH_TERMS_FOR_FILTERS | ForEach-Object { + $ONE_FILTER_SEARCH_URL = "$FILTERS_SEARCH_URL" + '?' + $_.Name + '=' + $_.Value + Get-PaginatedJSONResults -URI $ONE_FILTER_SEARCH_URL -METHOD Get -RESPONSE_JSON_OBJECT_FILTER_KEY 'values' | ConvertFrom-Json -AsHashtable + } + Write-Debug 'Filter results received... processing...' + $i = 1 + $FILTER_RESULTS = $FILTER_RESULTS | Group-Object id | ForEach-Object { $_.Group | Select-Object -First 1 } + $FILTER_RESULTS | ForEach-Object { + $FILTER_ID = $_.id + $FILTER_NAME = $_.name + $FILTER_JQL = "'" + $(Get-FilterJQL -FILTER_ID $FILTER_ID) + "'" + Write-Debug "Filter in parsed results [$i]: $FILTER_NAME - $FILTER_ID - $FILTER_JQL" + $i++ + } + $FILTER_RESULTS_JSON = $FILTER_RESULTS | ConvertTo-Json -Depth 50 -Compress + return $FILTER_RESULTS_JSON +} diff --git a/AtlassianPowerKit-Confluence/AtlassianPowerKit-Confluence.psm1 b/AtlassianPowerKit-Confluence/AtlassianPowerKit-Confluence.psm1 index 634be51..5a83c35 100644 --- a/AtlassianPowerKit-Confluence/AtlassianPowerKit-Confluence.psm1 +++ b/AtlassianPowerKit-Confluence/AtlassianPowerKit-Confluence.psm1 @@ -41,7 +41,7 @@ function Export-ConfluencePageAllChildren { [Parameter(Mandatory = $true)] [string]$CONFLUENCE_PAGE_FORMAT, [Parameter(Mandatory = $false)] - [int]$DepthLimit = 0, + [int]$DepthLimit = 10, [Parameter(Mandatory = $false)] [int]$DepthCount = 0 ) @@ -246,7 +246,7 @@ function Export-ConfluencePageStorageFormatForChildren { [Parameter(Mandatory = $true)] [string]$CONFLUENCE_PARENT_PAGE_TITLE, [Parameter(Mandatory = $false)] - [int]$DepthLimit = 0, + [int]$DepthLimit = 10, [Parameter(Mandatory = $false)] [int]$DepthCount = 0 ) @@ -434,81 +434,6 @@ function Set-ConfluenceSpacePropertyByID { $REST_RESULTS } -## INPROGRESS -# Function set Confluence page content using a storage format file and page ID -# function Set-ConfluencePageContent { -# param ( -# [Parameter(Mandatory = $true)] -# [string]$CONFLUENCE_SPACE_KEY, -# [Parameter(Mandatory = $true)] -# [int64]$CONFLUENCE_PAGE_ID, -# [Parameter(Mandatory = $false)] -# [string]$CONFLUENCE_PAGE_STORAGE_FILE -# ) -# $MyFunctionName = (Get-PSCallStack)[0].FunctionName -# $VERSION_MESSAGE = "Updated via AtlassianPowerKit $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" -# Get-ChildItem -Path . -Recurse -Filter 'Naive-ConflunceStorageValidator.psd1' | Import-Module -Force -# $CONFLUENCE_PAGE_ENDPOINT = "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/wiki/api/v2/pages/$($CONFLUENCE_PAGE_ID)" -# if ($CONFLUENCE_PAGE_STORAGE_FILE) { -# Write-Debug "$MyFunctionName- Using file: $CONFLUENCE_PAGE_STORAGE_FILE to update page ID: $CONFLUENCE_PAGE_ID...ignoring -CONFLUENCE_PAGE_STORAGE_FILE_CONTENT if it was provided..." -# } -# else { -# Write-Error 'You must provide either a file path to a storage format file or the content of the storage format file to update the page with parameter -CONFLUENCE_PAGE_STORAGE_FILE or -CONFLUENCE_PAGE_STORAGE_FILE_CONTENT' -# } -# Write-Debug "$MyFunctionName Getting existing page title and version number..." -# $REST_RESULTS = Invoke-RestMethod -Uri $CONFLUENCE_PAGE_ENDPOINT -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get -# #$REST_RESULTS | ConvertTo-Json -Depth 40 | Write-Debug -# $CONFLUENCE_PAGE_TITLE = $REST_RESULTS.title -# $CURRENT_VERSION = $REST_RESULTS.version.number -# $CLEANED_FILE_CONTENT = Get-Content -Path $CONFLUENCE_PAGE_STORAGE_FILE -Raw -Encoding UTF8 -# #Write-Debug "Making backup of current page ID: $CONFLUENCE_PAGE_ID..." -# # Remove pretty formatting, whitepassed, and newlines -# function Get-ConfluenceStoragePayload { -# param ( -# [Parameter(Mandatory = $true)] -# [string]$CleanedXmlContent, -# [Parameter(Mandatory = $true)] -# [string]$Title, -# [Parameter(Mandatory = $true)] -# [int]$Version, -# [Parameter(Mandatory = $true)] -# [int64]$PageId -# ) - -# $body = @{ -# representation = 'storage' -# value = $CleanedXmlContent -# } - -# $payload = @{ -# id = $PageId -# title = $Title -# version = @{ -# number = $Version -# message = $VERSION_MESSAGE -# } -# body = @{ -# storage = $body -# } -# status = 'current' -# } - -# return $payload | ConvertTo-Json -Depth 10 -Compress -# } -# $NEW_VERSION = $CURRENT_VERSION + 1 -# Write-Debug "$MyFunctionName - Page Payload: $PAGE_PAYLOAD" -# try { -# #Invoke-RestMethod -Uri $CONFLUENCE_PAGE_ENDPOINT -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get -# Invoke-RestMethod -Uri $CONFLUENCE_PAGE_ENDPOINT -Method Put -ContentType 'application/json' -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Body $(Get-ConfluenceStoragePayload -CleanedXmlContent $CLEANED_FILE_CONTENT -Title $CONFLUENCE_PAGE_TITLE -Version $NEW_VERSION -PageId $CONFLUENCE_PAGE_ID) -# } -# catch { -# Write-Debug $(Prepare-ConfluenceStoragePayload -CleanedXmlContent $CLEANED_FILE_CONTENT -Title $CONFLUENCE_PAGE_TITLE -Version $NEW_VERSION -PageId $CONFLUENCE_PAGE_ID) -# Write-Debug "$MyFunctionName - StatusCode: $($_.Exception.Response.StatusCode.value__)" -# Write-Debug ($_ | Select-Object -Property * -ExcludeProperty psobject | Out-String) -# Write-Error "Error updating field: $($_.Exception.Message)" -# } -# } -# Function to take a CONFLUENCE_PAGE_ID, validate it is in 'YYYY (.*)' format, get a list of Child Pages, and if it doesn't already exist, create a new child page with the title 'YYYY[1-12] (.*)', move any existing child pages that title match 'YYYYMM.*' to the new child page, and then move the new child page to the top of the list function Set-ConfluenceYearMonthStructure { param ( [Parameter(Mandatory = $true)] diff --git a/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psd1 b/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psd1 index a725913..d1b8cf4 100644 --- a/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psd1 +++ b/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psd1 @@ -86,10 +86,10 @@ 'Get-JiraIssue', 'Get-JiraIssueChangeNullsFromJQL', 'Get-JiraIssueLinks', + 'Get-JiraStatuses', 'Get-JiraProjectList', 'Get-JiraProjectProperties', 'Get-JiraProjectProperty', - 'Get-JiraProjectIssuesTypes', 'Get-JiraProjectWorkflowSchemes', 'Get-JiraProjectStatuses', 'Get-JSMService', diff --git a/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psm1 b/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psm1 index 4f87ddf..47cfb93 100644 --- a/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psm1 +++ b/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psm1 @@ -22,7 +22,6 @@ - Get-JiraProjectProperties - Set-JiraProjectProperty - Clear-JiraProjectProperty - - Get-JiraProjectIssuesTypes - Other - Get-OpsgenieServices -Output ready for Set-JiraProjectProperty - Users and Groups @@ -68,9 +67,7 @@ GitHub: https://github.com/markz0r/AtlassianPowerKit #> $ErrorActionPreference = 'Stop'; $DebugPreference = 'Continue' # Directory of this file -Import-Module "$env:OSM_INSTALL\AtlassianPowerKit\AtlassianPowerKit-Shared\AtlassianPowerKit-Shared.psd1" -Force -$REQ_SLEEP_SEC = 1 -$REQ_SLEEP_SEC_LONG = 10 +$RETRY_AFTER = 10 function Convert-JiraIssueToTableRow { param ( [Parameter(Mandatory = $true)] @@ -160,7 +157,6 @@ function Import-JIRAIssueFromJSONBackup { if ($FIELD_MAP_JSON) { Write-Debug "Field map provided: $FIELD_MAP_JSON" - $FIELD_MAP = Get-Content -Path $FIELD_MAP_JSON -Raw | ConvertFrom-Json -NoEnumerate -Depth 100 } else { Write-Debug 'Using manual mapping...' @@ -268,80 +264,6 @@ function Import-JIRAIssueFromJSONBackup { # Add comment Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/issue/$NEW_ISSUE_KEY/comment" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Post -ContentType 'application/json' -Body $POST_COMMENT_JSON } -function Get-PaginatedJSONResults { - param ( - [Parameter(Mandatory = $true)] - [string]$URI, - [Parameter(Mandatory = $true)] - [string]$METHOD, - [Parameter(Mandatory = $false)] - [string]$POST_BODY, - [Parameter(Mandatory = $false)] - [string]$RESPONSE_JSON_OBJECT_FILTER_KEY, - [Parameter(Mandatory = $false)] - [string]$API_HEADERS = $env:AtlassianPowerKit_AtlassianAPIHeaders - ) - $RESULTS_ARRAY = @() - try { - $NEXT_PAGE = $false - do { - if ($NEXT_PAGE) { - Write-Debug "There are more results, getting next page with token: $NEXT_PAGE" - $POST_BODY.nextPageToken = $NEXT_PAGE - } - Write-Debug "Getting results from: $URI using $METHOD..." - # If method is Get, don't include body (case insensitive) - if ($METHOD -ieq 'Get') { - $REST_RESPONSE = Invoke-RestMethod -Uri $URI -Headers $(ConvertFrom-Json -AsHashtable $API_HEADERS) -Method $METHOD -ContentType 'application/json' -StatusCodeVariable 'scv' - } - elseif ($METHOD -ieq 'Post') { - $REST_RESPONSE = Invoke-RestMethod -Uri $URI -Headers $(ConvertFrom-Json -AsHashtable $API_HEADERS) -Method $METHOD -Body $($POST_BODY | ConvertTo-Json -Depth 30) -ContentType 'application/json' -StatusCodeVariable 'scv' - } - else { - Throw "Unsupported method: $METHOD" - } - Write-Debug "REST_RESPONSE received: $($REST_RESPONSE.GetType()), StatusCode: $scv" - #Write-Debug "REST_RESPONSE: $($REST_RESPONSE | ConvertTo-Json -Depth 10)" - if ($scv -eq 429) { - Write-Debug "429 response, waiting $REQ_SLEEP_SEC_LONG seconds..." - Start-Sleep -Seconds $REQ_SLEEP_SEC_LONG - $COMPLETE = $false - } - # elseif rest response contains nextPageToken that is not empty or whitespace or tab or newline - elseif ($REST_RESPONSE.PSObject.Properties.Match('nextPageToken') -and [string]::IsNullOrWhiteSpace($REST_RESPONSE.nextPageToken) -eq $false) { - #$REST_RESPONSE | ConvertTo-Json -Depth 50 | Write-Debug - Write-Debug "Next page token found: $($REST_RESPONSE.nextPageToken), adding this page to the RESULTS_ARRAY..." - $NEXT_PAGE = $REST_RESPONSE.nextPageToken - $COMPLETE = $false - } - else { - Write-Debug 'No more pages, returning results...' - $COMPLETE = $true - } - if ($RESPONSE_JSON_OBJECT_FILTER_KEY) { - $RESULTS_ARRAY += $REST_RESPONSE.$RESPONSE_JSON_OBJECT_FILTER_KEY - } - else { - $RESULTS_ARRAY += $REST_RESPONSE - } - Write-Debug "RESULTS_ARRAY Count: $($RESULTS_ARRAY.Count)" - #Write-Debug "$($MyInvocation.MyCommand.Name): End of loop, Collected Issue count: $($RESULTS_ARRAY.Count)...Completed?: $COMPLETE" - } while (! $COMPLETE) - } - catch { - Write-Debug "Failed to get paginated results from: $URI" - Write-Debug '##############################################' - Write-Debug "-Uri $URI" - Write-Debug " -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders), -Method $METHOD, -Headers $(ConvertFrom-Json -AsHashtable $API_HEADERS)" - if ($POST_BODY) { - Write-Debug "-Body $($POST_BODY | ConvertTo-Json -Depth 30)" - } - Write-Debug ($_ | Select-Object -Property * -ExcludeProperty psobject | Out-String) - Write-Error "$($MyInvocation.MyCommand.Name): Error getting results: $($_.Exception.Message)" - Write-Debug '##############################################' - } - return $RESULTS_ARRAY -} function Get-JiraFilterResultsAsConfluenceTable { param ( @@ -396,51 +318,6 @@ function Get-JiraFilterResultsAsConfluenceTable { return $CONFLUENCE_STORAGE_RAW } -# Funtion to list JIRA issue filters including ID, name, and JQL query -function Get-JiraOSMFilterList { - param ( - [Parameter(Mandatory = $false)] - [string]$PROJECT_KEY = 'GRCOSM' - ) - $FILTERS_SEARCH_URL = "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/filter/search" - # Get Project ID project with key GRCOSM - $PROJECT_ID = Get-JiraProjectList -PROJECT_KEY $PROJECT_KEY | ConvertFrom-Json | Select-Object -ExpandProperty id - Write-Debug "Project ID: $PROJECT_ID" - $SEARCH_TERMS_FOR_FILTERS = @( - @{ 'Name' = 'filterName'; 'Value' = 'osm' }, - @{ 'Name' = 'projectId'; 'Value' = $PROJECT_ID } - ) - $FILTER_RESULTS = @() - $SEARCH_TERMS_FOR_FILTERS | ForEach-Object { - Write-Debug "Searching for filters with: $($_.Name) = $($_.Value)" - $REQUEST_RESPONSE = Invoke-RestMethod -Uri ($FILTERS_SEARCH_URL + '?' + $_.Name + '=' + $_.Value) -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get -ContentType 'application/json' - $FILTER_RESULTS += $REQUEST_RESPONSE.values - While (!$REQUEST_RESPONSE.isLast) { - $REQUEST_RESPONSE = Invoke-RestMethod -Uri $REQUEST_RESPONSE.nextPage -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get -ContentType 'application/json' - $FILTER_RESULTS += $REQUEST_RESPONSE.values - } - } - # Remove duplicates - $FILTER_RESULTS = $FILTER_RESULTS | Select-Object -Property * | Sort-Object -Property name -Unique - # For each filter, append the JQL query to the filter object - $FILTER_RESULTS | ForEach-Object { - $FILTER_ID = $_.id - # While response code is 429, wait and try again - $REST_RESPONSE = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/filter/$($FILTER_ID)" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get -ContentType 'application/json' - # If response is 429, wait and try again - while ($REST_RESPONSE -eq 429) { - Write-Debug "429 response, waiting $REQ_SLEEP_SEC_LONG seconds..." - Start-Sleep -Seconds $REQ_SLEEP_SEC_LONG - $REST_RESPONSE = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/filter/$($FILTER_ID)" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get -ContentType 'application/json' - } - $_ | Add-Member -MemberType NoteProperty -Name 'jql' -Value $REST_RESPONSE.jql - - } - # Write all details to terminal - $FILTER_RESULTS_JSON = $FILTER_RESULTS | ConvertTo-Json -Depth 50 - return $FILTER_RESULTS_JSON -} - function Get-JiraIssueChangeNullsFromJQL { param ( [Parameter(Mandatory = $true)] @@ -775,8 +652,8 @@ function Get-JiraCloudJQLQueryResult { Write-Debug "REST_RESPONSE received: $($REST_RESPONSE.GetType()), StatusCode: $scv" #Write-Debug "REST_RESPONSE: $($REST_RESPONSE | ConvertTo-Json -Depth 10)" if ($scv -eq 429) { - Write-Debug "429 response, waiting $REQ_SLEEP_SEC_LONG seconds..." - Start-Sleep -Seconds $REQ_SLEEP_SEC_LONG + Write-Debug "429 response, waiting $RETRY_AFTER seconds..." + Start-Sleep -Seconds $RETRY_AFTER $COMPLETE = $false } # elseif rest response contains nextPageToken, set $NEXT_PAGE to nextPageToken and continue @@ -1031,7 +908,9 @@ function Get-JiraIssueChangeNulls { function Get-JiraStatuses { param ( [Parameter(Mandatory = $false)] - [switch]$WriteOutput = $false + [switch]$JSONOnly = $false, + [Parameter(Mandatory = $false)] + [switch]$GetDuplicates = $false ) $REST_RESULTS = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/status" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get -ContentType 'application/json' function Get-DuplicateJiraStatusNames { @@ -1079,37 +958,24 @@ function Get-JiraStatuses { } return $JIRA_STATUSES } - if ($WriteOutput) { - $OUTPUT_FILE = "$env:OSM_HOME\$env:AtlassianPowerKit_PROFILE_NAME\JIRA\$env:AtlassianPowerKit_PROFILE_NAME-JIRAStatuses-$(Get-Date -Format 'yyyyMMdd-HHmmss').xlsx" + if (! $JSONOnly) { + $OUTPUT_FILE = "$env:OSM_HOME\$env:AtlassianPowerKit_PROFILE_NAME\JIRA\$env:AtlassianPowerKit_PROFILE_NAME-JIRAStatuses-$(Get-Date -Format 'yyyyMMdd-HHmmss').json" if (-not (Test-Path $OUTPUT_FILE)) { New-Item -ItemType File -Path $OUTPUT_FILE -Force | Out-Null } - $REST_RESULTS | ConvertTo-Csv -UseQuotes Never -Delimiter '-' -NoHeader | Out-File -FilePath $OUTPUT_FILE + $REST_RESULTS | ConvertTo-Json -Depth 100 | Out-File -FilePath $OUTPUT_FILE Write-Debug "Jira Statuses written to: $OUTPUT_FILE" } - $DUPLICATES = (Get-DuplicateJiraStatusNames -JIRA_STATUSES $REST_RESULTS | Where-Object { $_.duplicate -eq $true } | Sort-Object -Property name) - Write-Debug "Jira Statuses with duplicates: $($DUPLICATES.Count)" - Write-Debug 'Dulplicate list: ' - $DUPLICATES | ForEach-Object { - Write-Debug "$($_.name) - $($_.id) - $($_.duplicate) - $($_.duplicate_ids)" - } - return $REST_RESULTS -} -function Get-JiraProjectStatuses { - param ( - [Parameter(Mandatory = $false)] - [string]$PROJECT_KEY = $false, - [Parameter(Mandatory = $false)] - [string]$OUTPUT_DIR = "$env:OSM_HOME\$env:AtlassianPowerKit_PROFILE_NAME\JIRA" - ) - if ($PROJECT_KEY) { - $OUTPUT_FILE = "$OUTPUT_DIR\$env:AtlassianPowerKit_PROFILE_NAME-$PROJECT_KEY-JIRAProjectStatuses-$(Get-Date -Format 'yyyyMMdd-HHmmss').json" - URL = "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/project/$PROJECT_KEY/statuses" - } - else { - $OUTPUT_FILE = "$OUTPUT_DIR\$env:AtlassianPowerKit_PROFILE_NAME-ALL-JIRAProjectStatuses-$(Get-Date -Format 'yyyyMMdd-HHmmss').json" - URL = "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/status" + if ($GetDuplicates) { + $DUPLICATES = (Get-DuplicateJiraStatusNames -JIRA_STATUSES $REST_RESULTS | Where-Object { $_.duplicate -eq $true } | Sort-Object -Property name) + Write-Debug "Jira Statuses with duplicates: $($DUPLICATES.Count)" + $DUPLICATES | ForEach-Object { + Write-Debug "$($_.name) - $($_.id) - $($_.duplicate) - $($_.duplicate_ids)" + } | Out-File -FilePath "$env:OSM_HOME\$env:AtlassianPowerKit_PROFILE_NAME\JIRA\$env:AtlassianPowerKit_PROFILE_NAME-JIRAStatusesDuplicates-$(Get-Date -Format 'yyyyMMdd-HHmmss').txt" + Write-Debug 'Dulplicate list: ' + } + return $REST_RESULTS | ConvertTo-Json -Depth 100 -Compress } function Get-JiraProjectWorkflowSchemes { @@ -1276,88 +1142,6 @@ function Get-JSMService { } } - -# Funtion to list project properties (JIRA entities) -function Get-JiraProjectIssuesTypes { - param ( - [Parameter(Mandatory = $true)] - [string]$PROJECT_KEY_OR_ID, - [Parameter(Mandatory = $false)] - [string]$OUTPUT_PATH = "$($env:OSM_HOME)\$($env:AtlassianPowerKit_PROFILE_NAME)\JIRA" - ) - if ($PROJECT_KEY_OR_ID -match '^\d+$') { - $PROJECT_ID = $PROJECT_KEY_OR_ID - } - else { - # Get the most recent auda-ProjectList-*.json in the $OUTPUT_PATH or run Get-JiraProjectList and check again for the file - $PROJECT_LIST_FILE = Get-ChildItem -Path $OUTPUT_PATH -Filter "$env:AtlassianPowerKit_PROFILE_NAME-ProjectList-*.json" | Sort-Object -Property LastWriteTime -Descending | Select-Object -First 1 - While (-not $PROJECT_LIST_FILE) { - Write-Debug 'No Project List file found, running Get-JiraProjectList...' - Get-JiraProjectList -OUTPUT_PATH $OUTPUT_PATH - $PROJECT_LIST_FILE = Get-ChildItem -Path $OUTPUT_PATH -Filter "$env:AtlassianPowerKit_PROFILE_NAME-ProjectList-*.json" | Sort-Object -Property LastWriteTime -Descending | Select-Object -First 1 - } - $PROJECT_ID = (Get-Content -Path $PROJECT_LIST_FILE.FullName | ConvertFrom-Json | Where-Object { $_.key -eq $PROJECT_KEY_OR_ID }).id - } - $FILENAME = "$env:AtlassianPowerKit_PROFILE_NAME-$PROJECT_KEY_OR_ID-IssueTypes-$(Get-Date -Format 'yyyyMMdd-HHmmss').json" - if (-not (Test-Path $OUTPUT_PATH)) { - New-Item -ItemType Directory -Path $OUTPUT_PATH -Force | Out-Null - } - $OUTPUT_FILE = "$OUTPUT_PATH\$FILENAME" - # Use Get-PaginatedResults to get all issues types for the project - Write-Debug "Getting Jira Project Issue Types for project: $PROJECT_ID ..." - $REST_RESULTS = Get-PaginatedJSONResults -URI "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/issuetype/project?projectId=$PROJECT_ID" -Method Get - Write-Debug "Jira Project Issue Types for project: $PROJECT_ID received... writing to file..." - $REST_RESULTS | ConvertTo-Json -Depth 50 | Out-File -FilePath $OUTPUT_FILE - Write-Debug "Jira Project Issue Types written to: $OUTPUT_FILE" - return $REST_RESULTS | ConvertTo-Json -Depth 100 -Compress -} - -# Function to get issue type metadata for a Jira Cloud project -function Get-JiraCloudIssueTypeMetadata { - param ( - [Parameter(Mandatory = $true)] - [string]$PROJECT_KEY, - [Parameter(Mandatory = $false)] - [string]$OUTPUT_PATH = "$($env:OSM_HOME)\$($env:AtlassianPowerKit_PROFILE_NAME)\JIRA" - ) - $FILENAME = "$env:AtlassianPowerKit_PROFILE_NAME-$PROJECT_KEY-IssueTypeMetadata-$(Get-Date -Format 'yyyyMMdd-HHmmss').json" - $REST_RESULTS = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/issue/createmeta/$PROJECT_KEY&expand=projects.issuetypes.fields" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get - ConvertTo-Json $REST_RESULTS -Depth 50 | Out-File -FilePath "$OUTPUT_PATH\$FILENAME" - Write-Debug "Issue Type Metadata JSON file created: $OUTPUT_PATH\$FILENAME" - Return $REST_RESULTS -} - -# Fuction to get Issue Type schema for a Jira Cloud project -function Get-JiraCloudIssueTypeSchema { - param ( - [Parameter(Mandatory = $true)] - [string]$PROJECT_KEY_OR_ID, - [Parameter(Mandatory = $false)] - [string]$OUTPUT_PATH = "$($env:OSM_HOME)\$($env:AtlassianPowerKit_PROFILE_NAME)\JIRA" - ) - $FILENAME = "$env:AtlassianPowerKit_PROFILE_NAME-$PROJECT_KEY-IssueTypeSchema-$(Get-Date -Format 'yyyyMMdd-HHmmss').json" - # if the project key is passed, get the project ID (key is Alpha-numeric, ID is numeric) - if ($PROJECT_KEY_OR_ID -match '^\d+$') { - Write-Debug "Project ID passed: $PROJECT_KEY_OR_ID" - $PROJECT_ID = $PROJECT_KEY_OR_ID - } - else { - Write-Debug "Project Key passed: $PROJECT_KEY_OR_ID ... getting project ID..." - $PROJECT_OBJECT = Get-JiraProjectByKey -PROJECT_KEY $PROJECT_KEY_OR_ID | ConvertFrom-Json -AsHashtable -NoEnumerate - #ConvertTo-Json $PROJECT_OBJECT -Depth 50 | Write-Debug - if ($PROJECT_OBJECT.id) { - $PROJECT_ID = $PROJECT_OBJECT.id - } - else { - Write-Error "Project ID not found for project key: $PROJECT_KEY_OR_ID" - } - } - $REST_RESULTS = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/issuetypescheme/project?projectId=$PROJECT_ID" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get - ConvertTo-Json $REST_RESULTS -Depth 50 | Out-File -FilePath "$OUTPUT_PATH\$FILENAME" - Write-Debug "Issue Type Schema JSON file created: $OUTPUT_PATH\$FILENAME" - return $REST_RESULTS.values | ConvertTo-Json -Depth 50 -Compress -} - function Get-JiraProjectByKey { param ( [Parameter(Mandatory = $true)] diff --git a/AtlassianPowerKit-Jira/Get-FixedADFJson.ps1 b/AtlassianPowerKit-Jira/Get-FixedADFJson.ps1 new file mode 100644 index 0000000..30f3ee7 --- /dev/null +++ b/AtlassianPowerKit-Jira/Get-FixedADFJson.ps1 @@ -0,0 +1,46 @@ +param ( + [Parameter(Mandatory = $true)] + [string]$INPUT_FILE_PATH, + [Parameter(Mandatory = $false)] + [switch]$DownloadSchema = $false +) +$ErrorActionPreference = 'Stop'; $DebugPreference = 'Continue' + +$JIRA_ADF_SCHEMA_URL = 'http://go.atlassian.com/adf-json-schema' +$JIRA_ADF_SCHEMA_FILE = "$env:OSM_INSTALL\AtlassianPowerKit\AtlassianPowerKit-Jira\JiraADFJsonSchema.json" + +function Get-SchemaDefinition { + param( + [string]$SchemaUrl + ) + Invoke-RestMethod -Uri $SchemaUrl -OutFile $JIRA_ADF_SCHEMA_FILE +} + +function Test-JSONFileAgainstSchemaFile { + param( + [Parameter(Mandatory = $true)] + [string]$JsonInputFilePath, + [Parameter(Mandatory = $true)] + [string]$SchemaFilePath + ) + Write-Debug "Validating JSON file: $JsonInputFilePath ..." + if (! (Test-Json -Path $JsonInputFilePath)) { + throw "Invalid JSON file: $JsonInputFilePath" + } + Write-Debug "`t ... $JsonInputFilePath is a valid JSON file." + Write-Debug "Validating JSON file against schema file: $SchemaFilePath ..." + if (! (Test-Json -Path $SchemaFilePath)) { + throw "Invalid JSON schema file: $SchemaFilePath" + } + Write-Debug "`t ... $SchemaFilePath is a valid JSON schema file." + Write-Debug 'Validating JSON file against schema file ...' + Test-Json -Path "$JsonInputFilePath" -SchemaFile "$SchemaFilePath" +} + +#################### MAIN #################### +if ($DownloadSchema) { + Get-SchemaDefinition -SchemaUrl $JIRA_ADF_SCHEMA_URL +} +if ($INPUT_FILE_PATH) { + Test-JSONFileAgainstSchemaFile -JsonInputFilePath $INPUT_FILE_PATH -SchemaFilePath $JIRA_ADF_SCHEMA_FILE +} diff --git a/AtlassianPowerKit-Jira/JIRA_ADF_README.md b/AtlassianPowerKit-Jira/JIRA_ADF_README.md new file mode 100644 index 0000000..119bd4e --- /dev/null +++ b/AtlassianPowerKit-Jira/JIRA_ADF_README.md @@ -0,0 +1,22 @@ +# Atlassian ADF + +- Atlassian Document Format (ADF) is a powerful, human-readable document format for representing structured content that is easy to write and to read. It is designed to be both easy to read as a human and easy to parse as a machine. ADF is used in Atlassian products like Confluence and Jira. + +## Key References + +- [ADF Schema Definition](http://go.atlassian.com/adf-json-schema) +- [ADF Structure Doc](https://developer.atlassian.com/cloud/jira/platform/apis/document/structure/) +- [ADF Web-Builder](https://developer.atlassian.com/cloud/jira/platform/apis/document/playground/) + +## VSCode JSON Schema Validation + +```json + "json.schemas": [ + { + "fileMatch": [ + "/OP-*.json" + ], + "url": "http://go.atlassian.com/adf-json-schema" + } + ] +``` diff --git a/AtlassianPowerKit-Jira/JIRA_EXPORT_API_BROKEN_VS_ADF.json b/AtlassianPowerKit-Jira/JIRA_EXPORT_API_BROKEN_VS_ADF.json new file mode 100644 index 0000000..bbbcfc0 --- /dev/null +++ b/AtlassianPowerKit-Jira/JIRA_EXPORT_API_BROKEN_VS_ADF.json @@ -0,0 +1,578 @@ +{ + "type": "doc", + "version": 1, + "content": [ + { + "type": "paragraph", + "content": { + "type": "text", + "text": "Oracle has deprecated support for Oracle 12c Database. As such, we need to upgrade all numbering DB’s to 19c." + } + }, + { + "type": "mediaSingle", + "attrs": { + "layout": "align-start" + }, + "content": { + "type": "media", + "attrs": { + "type": "file", + "id": "54e68b45-6041-4c63-b4b2-ea818eaa9fbd", + "height": 410, + "width": 1833 + } + } + }, + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "Ref: " + }, + { + "type": "text", + "text": "https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Oracle.Concepts.database-versions.html#Oracle.Concepts.FeatureSupport.12c", + "marks": { + "type": "link", + "attrs": { + "href": "https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Oracle.Concepts.database-versions.html#Oracle.Concepts.FeatureSupport.12c" + } + } + } + ] + }, + { + "type": "paragraph", + "content": { + "type": "text", + "text": "Starting June 1, AWS will start auto upgrading these DB’s to 19c. We should take this opportunity to manually upgrade and do testing." + } + }, + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "AWS provides comprehensive documentation to upgrade these Oracle versions, which covers all manual steps outlined for an on-premise upgrade - " + }, + { + "type": "text", + "text": "https://aws.amazon.com/blogs/database/best-practices-for-upgrading-amazon-rds-for-oracle-db-instances-from-12c-to-19c/#:~:text=Amazon RDS for Oracle will,existing RDS for Oracle 12.1.", + "marks": { + "type": "link", + "attrs": { + "href": "https://aws.amazon.com/blogs/database/best-practices-for-upgrading-amazon-rds-for-oracle-db-instances-from-12c-to-19c/#:~:text=Amazon%20RDS%20for%20Oracle%20will,existing%20RDS%20for%20Oracle%2012.1." + } + } + } + ] + }, + { + "type": "paragraph", + "content": { + "type": "text", + "text": "Following DB’s are in scope:" + } + }, + { + "type": "table", + "attrs": { + "layout": "default", + "localId": "8b8f6ef9-8fee-4274-ba8d-926eba4cef2b" + }, + "content": [ + { + "type": "tableRow", + "content": [ + { + "type": "tableCell", + "attrs": {}, + "content": { + "type": "paragraph", + "content": { + "type": "text", + "text": "Region" + } + } + }, + { + "type": "tableCell", + "attrs": {}, + "content": { + "type": "paragraph", + "content": { + "type": "text", + "text": "AWS accountid" + } + } + }, + { + "type": "tableCell", + "attrs": {}, + "content": { + "type": "paragraph", + "content": { + "type": "text", + "text": "DBInstanceSize" + } + } + }, + { + "type": "tableCell", + "attrs": {}, + "content": { + "type": "paragraph", + "content": { + "type": "text", + "text": "AutoMinorVersionUpgrade" + } + } + }, + { + "type": "tableCell", + "attrs": {}, + "content": { + "type": "paragraph", + "content": { + "type": "text", + "text": "DBInstanceIdentifier" + } + } + }, + { + "type": "tableCell", + "attrs": {}, + "content": { + "type": "paragraph", + "content": { + "type": "text", + "text": "Engine" + } + } + }, + { + "type": "tableCell", + "attrs": {}, + "content": { + "type": "paragraph", + "content": { + "type": "text", + "text": "EngineVersion" + } + } + } + ] + }, + { + "type": "tableRow", + "content": [ + { + "type": "tableCell", + "attrs": {}, + "content": { + "type": "paragraph", + "content": { + "type": "text", + "text": "ap-southeast-2" + } + } + }, + { + "type": "tableCell", + "attrs": {}, + "content": { + "type": "paragraph", + "content": { + "type": "text", + "text": "numbering-legacy" + } + } + }, + { + "type": "tableCell", + "attrs": {}, + "content": { + "type": "paragraph", + "content": { + "type": "text", + "text": "db.t3.xlarge" + } + } + }, + { + "type": "tableCell", + "attrs": {}, + "content": { + "type": "paragraph", + "content": { + "type": "text", + "text": "TRUE" + } + } + }, + { + "type": "tableCell", + "attrs": {}, + "content": { + "type": "paragraph", + "content": { + "type": "text", + "text": "devnumdb-2021-09-14-17-10" + } + } + }, + { + "type": "tableCell", + "attrs": {}, + "content": { + "type": "paragraph", + "content": { + "type": "text", + "text": "oracle-se2" + } + } + }, + { + "type": "tableCell", + "attrs": {}, + "content": { + "type": "paragraph", + "content": { + "type": "text", + "text": "12.1.0.2.v26" + } + } + } + ] + }, + { + "type": "tableRow", + "content": [ + { + "type": "tableCell", + "attrs": {}, + "content": { + "type": "paragraph", + "content": { + "type": "text", + "text": "ap-southeast-2" + } + } + }, + { + "type": "tableCell", + "attrs": {}, + "content": { + "type": "paragraph", + "content": { + "type": "text", + "text": "numbering-legacy" + } + } + }, + { + "type": "tableCell", + "attrs": {}, + "content": { + "type": "paragraph", + "content": { + "type": "text", + "text": "db.r5.large" + } + } + }, + { + "type": "tableCell", + "attrs": {}, + "content": { + "type": "paragraph", + "content": { + "type": "text", + "text": "TRUE" + } + } + }, + { + "type": "tableCell", + "attrs": {}, + "content": { + "type": "paragraph", + "content": { + "type": "text", + "text": "num-ote-db" + } + } + }, + { + "type": "tableCell", + "attrs": {}, + "content": { + "type": "paragraph", + "content": { + "type": "text", + "text": "oracle-se2" + } + } + }, + { + "type": "tableCell", + "attrs": {}, + "content": { + "type": "paragraph", + "content": { + "type": "text", + "text": "12.1.0.2.v26" + } + } + } + ] + }, + { + "type": "tableRow", + "content": [ + { + "type": "tableCell", + "attrs": {}, + "content": { + "type": "paragraph", + "content": { + "type": "text", + "text": "ap-southeast-2" + } + } + }, + { + "type": "tableCell", + "attrs": {}, + "content": { + "type": "paragraph", + "content": { + "type": "text", + "text": "numbering-legacy" + } + } + }, + { + "type": "tableCell", + "attrs": {}, + "content": { + "type": "paragraph", + "content": { + "type": "text", + "text": "db.r5.large" + } + } + }, + { + "type": "tableCell", + "attrs": {}, + "content": { + "type": "paragraph", + "content": { + "type": "text", + "text": "TRUE" + } + } + }, + { + "type": "tableCell", + "attrs": {}, + "content": { + "type": "paragraph", + "content": { + "type": "text", + "text": "otenumdb-20220130" + } + } + }, + { + "type": "tableCell", + "attrs": {}, + "content": { + "type": "paragraph", + "content": { + "type": "text", + "text": "oracle-se2" + } + } + }, + { + "type": "tableCell", + "attrs": {}, + "content": { + "type": "paragraph", + "content": { + "type": "text", + "text": "12.1.0.2.v26" + } + } + } + ] + }, + { + "type": "tableRow", + "content": [ + { + "type": "tableCell", + "attrs": {}, + "content": { + "type": "paragraph", + "content": { + "type": "text", + "text": "ap-southeast-2" + } + } + }, + { + "type": "tableCell", + "attrs": {}, + "content": { + "type": "paragraph", + "content": { + "type": "text", + "text": "numbering-legacy" + } + } + }, + { + "type": "tableCell", + "attrs": {}, + "content": { + "type": "paragraph", + "content": { + "type": "text", + "text": "db.r4.large" + } + } + }, + { + "type": "tableCell", + "attrs": {}, + "content": { + "type": "paragraph", + "content": { + "type": "text", + "text": "FALSE" + } + } + }, + { + "type": "tableCell", + "attrs": {}, + "content": { + "type": "paragraph", + "content": { + "type": "text", + "text": "prodnumdb" + } + } + }, + { + "type": "tableCell", + "attrs": {}, + "content": { + "type": "paragraph", + "content": { + "type": "text", + "text": "oracle-se2" + } + } + }, + { + "type": "tableCell", + "attrs": {}, + "content": { + "type": "paragraph", + "content": { + "type": "text", + "text": "12.1.0.2.v18" + } + } + } + ] + } + ] + }, + { + "type": "paragraph", + "content": { + "type": "text", + "text": "Recommendation:" + } + }, + { + "type": "orderedList", + "content": [ + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": { + "type": "text", + "text": "Manually upgrade DEV database, and perform application PVT and performance testing" + } + }, + { + "type": "orderedList", + "content": [ + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": { + "type": "text", + "text": "If issues arise, consider the following:" + } + }, + { + "type": "codeBlock", + "attrs": {}, + "content": { + "type": "text", + "text": "After the upgrade, if the SQL statements perform poorly due to the change in the plans by the 19c optimizer, you can use OPTIMIZER_FEATURES_ENABLE. This parameter is alterable at the session level and system level. For example, if you upgrade your database from release 12c to release 19c, but you want to keep the release 12c optimizer behavior, you can do so by setting this parameter to 12c. At a later time, you can try the enhancements introduced in releases up to and including release 19c by setting the parameter to 19.0.0." + } + } + ] + }, + { + "type": "listItem", + "content": { + "type": "paragraph", + "content": { + "type": "text", + "text": "If issues still occur, application-level code fixes would be required. This might be a good oppurtunity to migrate the app to use Aurora Postgres or Aurora MySQL instead, which AWS provide tools to hlep migrate code and data." + } + } + } + ] + } + ] + }, + { + "type": "listItem", + "content": { + "type": "paragraph", + "content": { + "type": "text", + "text": "If all successful, upgrade OTE" + } + } + }, + { + "type": "listItem", + "content": { + "type": "paragraph", + "content": { + "type": "text", + "text": "If all successful, upgrade PROD and enable ā€œauto minor version upgradeā€" + } + } + } + ] + } + ] +} diff --git a/AtlassianPowerKit-Jira/JiraADFJsonSchema.json b/AtlassianPowerKit-Jira/JiraADFJsonSchema.json new file mode 100644 index 0000000..e69de29 diff --git a/AtlassianPowerKit-Jira/OP-999.json b/AtlassianPowerKit-Jira/OP-999.json new file mode 100644 index 0000000..007d825 --- /dev/null +++ b/AtlassianPowerKit-Jira/OP-999.json @@ -0,0 +1,752 @@ +{ + "type": "doc", + "version": 1, + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "Oracle has deprecated support for Oracle 12c Database. As such, we need to upgrade all numbering DB’s to 19c." + } + ] + }, + { + "type": "mediaSingle", + "attrs": { + "layout": "align-start" + }, + "content": [ + { + "type": "media", + "attrs": { + "type": "file", + "id": "54e68b45-6041-4c63-b4b2-ea818eaa9fbd", + "collection": "", + "height": 410, + "width": 1833 + } + } + ] + }, + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "Ref: " + }, + { + "type": "text", + "text": "https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Oracle.Concepts.database-versions.html#Oracle.Concepts.FeatureSupport.12c", + "marks": [ + { + "type": "link", + "attrs": { + "href": "https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Oracle.Concepts.database-versions.html#Oracle.Concepts.FeatureSupport.12c" + } + } + ] + } + ] + }, + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "Starting June 1, AWS will start auto upgrading these DB’s to 19c. We should take this opportunity to manually upgrade and do testing." + } + ] + }, + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "AWS provides comprehensive documentation to upgrade these Oracle versions, which covers all manual steps outlined for an on-premise upgrade - " + }, + { + "type": "text", + "text": "https://aws.amazon.com/blogs/database/best-practices-for-upgrading-amazon-rds-for-oracle-db-instances-from-12c-to-19c/#:~:text=Amazon RDS for Oracle will,existing RDS for Oracle 12.1.", + "marks": [ + { + "type": "link", + "attrs": { + "href": "https://aws.amazon.com/blogs/database/best-practices-for-upgrading-amazon-rds-for-oracle-db-instances-from-12c-to-19c/#:~:text=Amazon%20RDS%20for%20Oracle%20will,existing%20RDS%20for%20Oracle%2012.1." + } + } + ] + } + ] + }, + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "Following DB’s are in scope:" + } + ] + }, + { + "type": "table", + "attrs": { + "isNumberColumnEnabled": false, + "layout": "default", + "localId": "8b8f6ef9-8fee-4274-ba8d-926eba4cef2b" + }, + "content": [ + { + "type": "tableRow", + "content": [ + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "Region" + } + ] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "AWS accountid" + } + ] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "DBInstanceSize" + } + ] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "AutoMinorVersionUpgrade" + } + ] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "DBInstanceIdentifier" + } + ] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "Engine" + } + ] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "EngineVersion" + } + ] + } + ] + } + ] + }, + { + "type": "tableRow", + "content": [ + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "ap-southeast-2" + } + ] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "numbering-legacy" + } + ] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "db.t3.xlarge" + } + ] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "TRUE" + } + ] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "devnumdb-2021-09-14-17-10" + } + ] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "oracle-se2" + } + ] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "12.1.0.2.v26" + } + ] + } + ] + } + ] + }, + { + "type": "tableRow", + "content": [ + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "ap-southeast-2" + } + ] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "numbering-legacy" + } + ] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "db.r5.large" + } + ] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "TRUE" + } + ] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "num-ote-db" + } + ] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "oracle-se2" + } + ] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "12.1.0.2.v26" + } + ] + } + ] + } + ] + }, + { + "type": "tableRow", + "content": [ + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "ap-southeast-2" + } + ] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "numbering-legacy" + } + ] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "db.r5.large" + } + ] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "TRUE" + } + ] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "otenumdb-20220130" + } + ] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "oracle-se2" + } + ] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "12.1.0.2.v26" + } + ] + } + ] + } + ] + }, + { + "type": "tableRow", + "content": [ + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "ap-southeast-2" + } + ] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "numbering-legacy" + } + ] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "db.r4.large" + } + ] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "FALSE" + } + ] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "prodnumdb" + } + ] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "oracle-se2" + } + ] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "12.1.0.2.v18" + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "Recommendation:" + } + ] + }, + { + "type": "orderedList", + "content": [ + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "Manually upgrade DEV database, and perform application PVT and performance testing" + } + ] + }, + { + "type": "orderedList", + "content": [ + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "If issues arise, consider the following:" + } + ] + }, + { + "type": "codeBlock", + "attrs": {}, + "content": [ + { + "type": "text", + "text": "After the upgrade, if the SQL statements perform poorly due to the change in the plans by the 19c optimizer, you can use OPTIMIZER_FEATURES_ENABLE. This parameter is alterable at the session level and system level. For example, if you upgrade your database from release 12c to release 19c, but you want to keep the release 12c optimizer behavior, you can do so by setting this parameter to 12c. At a later time, you can try the enhancements introduced in releases up to and including release 19c by setting the parameter to 19.0.0." + } + ] + } + ] + }, + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "If issues still occur, application-level code fixes would be required. This might be a good oppurtunity to migrate the app to use Aurora Postgres or Aurora MySQL instead, which AWS provide tools to hlep migrate code and data." + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "If all successful, upgrade OTE" + } + ] + } + ] + }, + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "If all successful, upgrade PROD and enable ā€œauto minor version upgradeā€" + } + ] + } + ] + } + ] + } + ] +} diff --git a/AtlassianPowerKit-Jira/OP-999_JQLResult_PartialFile.json b/AtlassianPowerKit-Jira/OP-999_JQLResult_PartialFile.json new file mode 100644 index 0000000..750b11c --- /dev/null +++ b/AtlassianPowerKit-Jira/OP-999_JQLResult_PartialFile.json @@ -0,0 +1,565 @@ +{ + "type": "doc", + "version": 1, + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "Oracle has deprecated support for Oracle 12c Database. As such, we need to upgrade all numbering DB’s to 19c." + } + ] + }, + { + "type": "mediaSingle", + "attrs": { "layout": "align-start" }, + "content": [ + { + "type": "media", + "attrs": { + "type": "file", + "id": "54e68b45-6041-4c63-b4b2-ea818eaa9fbd", + "collection": "", + "height": 410, + "width": 1833 + } + } + ] + }, + { + "type": "paragraph", + "content": [ + { "type": "text", "text": "Ref: " }, + { + "type": "text", + "text": "https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Oracle.Concepts.database-versions.html#Oracle.Concepts.FeatureSupport.12c", + "marks": [ + { + "type": "link", + "attrs": { + "href": "https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Oracle.Concepts.database-versions.html#Oracle.Concepts.FeatureSupport.12c" + } + } + ] + } + ] + }, + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "Starting June 1, AWS will start auto upgrading these DB’s to 19c. We should take this opportunity to manually upgrade and do testing." + } + ] + }, + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "AWS provides comprehensive documentation to upgrade these Oracle versions, which covers all manual steps outlined for an on-premise upgrade - " + }, + { + "type": "text", + "text": "https://aws.amazon.com/blogs/database/best-practices-for-upgrading-amazon-rds-for-oracle-db-instances-from-12c-to-19c/#:~:text=Amazon RDS for Oracle will,existing RDS for Oracle 12.1.", + "marks": [ + { + "type": "link", + "attrs": { + "href": "https://aws.amazon.com/blogs/database/best-practices-for-upgrading-amazon-rds-for-oracle-db-instances-from-12c-to-19c/#:~:text=Amazon%20RDS%20for%20Oracle%20will,existing%20RDS%20for%20Oracle%2012.1." + } + } + ] + } + ] + }, + { + "type": "paragraph", + "content": [{ "type": "text", "text": "Following DB’s are in scope:" }] + }, + { + "type": "table", + "attrs": { + "isNumberColumnEnabled": false, + "layout": "default", + "localId": "8b8f6ef9-8fee-4274-ba8d-926eba4cef2b" + }, + "content": [ + { + "type": "tableRow", + "content": [ + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [{ "type": "text", "text": "Region" }] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [{ "type": "text", "text": "AWS accountid" }] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [{ "type": "text", "text": "DBInstanceSize" }] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [ + { "type": "text", "text": "AutoMinorVersionUpgrade" } + ] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [ + { "type": "text", "text": "DBInstanceIdentifier" } + ] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [{ "type": "text", "text": "Engine" }] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [{ "type": "text", "text": "EngineVersion" }] + } + ] + } + ] + }, + { + "type": "tableRow", + "content": [ + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [{ "type": "text", "text": "ap-southeast-2" }] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [{ "type": "text", "text": "numbering-legacy" }] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [{ "type": "text", "text": "db.t3.xlarge" }] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [{ "type": "text", "text": "TRUE" }] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [ + { "type": "text", "text": "devnumdb-2021-09-14-17-10" } + ] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [{ "type": "text", "text": "oracle-se2" }] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [{ "type": "text", "text": "12.1.0.2.v26" }] + } + ] + } + ] + }, + { + "type": "tableRow", + "content": [ + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [{ "type": "text", "text": "ap-southeast-2" }] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [{ "type": "text", "text": "numbering-legacy" }] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [{ "type": "text", "text": "db.r5.large" }] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [{ "type": "text", "text": "TRUE" }] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [{ "type": "text", "text": "num-ote-db" }] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [{ "type": "text", "text": "oracle-se2" }] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [{ "type": "text", "text": "12.1.0.2.v26" }] + } + ] + } + ] + }, + { + "type": "tableRow", + "content": [ + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [{ "type": "text", "text": "ap-southeast-2" }] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [{ "type": "text", "text": "numbering-legacy" }] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [{ "type": "text", "text": "db.r5.large" }] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [{ "type": "text", "text": "TRUE" }] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [{ "type": "text", "text": "otenumdb-20220130" }] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [{ "type": "text", "text": "oracle-se2" }] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [{ "type": "text", "text": "12.1.0.2.v26" }] + } + ] + } + ] + }, + { + "type": "tableRow", + "content": [ + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [{ "type": "text", "text": "ap-southeast-2" }] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [{ "type": "text", "text": "numbering-legacy" }] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [{ "type": "text", "text": "db.r4.large" }] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [{ "type": "text", "text": "FALSE" }] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [{ "type": "text", "text": "prodnumdb" }] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [{ "type": "text", "text": "oracle-se2" }] + } + ] + }, + { + "type": "tableCell", + "attrs": {}, + "content": [ + { + "type": "paragraph", + "content": [{ "type": "text", "text": "12.1.0.2.v18" }] + } + ] + } + ] + } + ] + }, + { + "type": "paragraph", + "content": [{ "type": "text", "text": "Recommendation:" }] + }, + { + "type": "orderedList", + "content": [ + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "Manually upgrade DEV database, and perform application PVT and performance testing" + } + ] + }, + { + "type": "orderedList", + "content": [ + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "If issues arise, consider the following:" + } + ] + }, + { + "type": "codeBlock", + "attrs": {}, + "content": [ + { + "type": "text", + "text": "After the upgrade, if the SQL statements perform poorly due to the change in the plans by the 19c optimizer, you can use OPTIMIZER_FEATURES_ENABLE. This parameter is alterable at the session level and system level. For example, if you upgrade your database from release 12c to release 19c, but you want to keep the release 12c optimizer behavior, you can do so by setting this parameter to 12c. At a later time, you can try the enhancements introduced in releases up to and including release 19c by setting the parameter to 19.0.0." + } + ] + } + ] + }, + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "If issues still occur, application-level code fixes would be required. This might be a good oppurtunity to migrate the app to use Aurora Postgres or Aurora MySQL instead, which AWS provide tools to hlep migrate code and data." + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { "type": "text", "text": "If all successful, upgrade OTE" } + ] + } + ] + }, + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "If all successful, upgrade PROD and enable ā€œauto minor version upgradeā€" + } + ] + } + ] + } + ] + } + ] +} diff --git a/AtlassianPowerKit-Jira/SSG_REQ_FIELDMAP.json b/AtlassianPowerKit-Jira/SSG_REQ_FIELDMAP.json new file mode 100644 index 0000000..cb2935c --- /dev/null +++ b/AtlassianPowerKit-Jira/SSG_REQ_FIELDMAP.json @@ -0,0 +1,5 @@ +'{ + "Applicability Justification": "is caused by", + "Security Requirements": "addressed by", + "Dependencies": "met by" +}' \ No newline at end of file diff --git a/AtlassianPowerKit-Shared/AtlassianPowerKit-Shared.psd1 b/AtlassianPowerKit-Shared/AtlassianPowerKit-Shared.psd1 index ae16d46..ed96f82 100644 --- a/AtlassianPowerKit-Shared/AtlassianPowerKit-Shared.psd1 +++ b/AtlassianPowerKit-Shared/AtlassianPowerKit-Shared.psd1 @@ -74,6 +74,7 @@ 'Clear-AtlassianPowerKitProfileDirs', 'Clear-AtlassianPowerKitVault', 'Get-AtlassianPowerKitProfileList', + 'Get-PaginatedJSONResults', 'Get-CurrentAtlassianPowerKitProfile', 'Get-LevenshteinDistance', 'Get-PopulatedTemplate', diff --git a/AtlassianPowerKit-Shared/AtlassianPowerKit-Shared.psm1 b/AtlassianPowerKit-Shared/AtlassianPowerKit-Shared.psm1 index 1872c9d..06c61e6 100644 --- a/AtlassianPowerKit-Shared/AtlassianPowerKit-Shared.psm1 +++ b/AtlassianPowerKit-Shared/AtlassianPowerKit-Shared.psm1 @@ -36,6 +36,7 @@ GitHub: https://github.com/markz0r/AtlassianPowerKit $ErrorActionPreference = 'Stop'; $DebugPreference = 'Continue' $VAULT_NAME = 'AtlassianPowerKitProfileVault' $VAULT_KEY_PATH = "$($env:OSM_HOME)\vault_key.xml" +$RETRY_AFTER = 60 function Clear-AtlassianPowerKitProfile { # Clear all environment variables starting with AtlassianPowerKit_ @@ -103,6 +104,91 @@ function Get-CurrentAtlassianPowerKitProfile { return $false } } +function Get-PaginatedJSONResults { + param ( + [Parameter(Mandatory = $true)] + [string]$URI, + [Parameter(Mandatory = $true)] + [string]$METHOD, + [Parameter(Mandatory = $false)] + [string]$POST_BODY, + [Parameter(Mandatory = $false)] + [string]$RESPONSE_JSON_OBJECT_FILTER_KEY, + [Parameter(Mandatory = $false)] + [string]$API_HEADERS = $env:AtlassianPowerKit_AtlassianAPIHeaders + ) + + function Get-PageResult { + param ( + [Parameter(Mandatory = $true)] + [string]$URI, + [Parameter(Mandatory = $false)] + [string]$METHOD = 'GET', + [Parameter(Mandatory = $false)] + [string]$ONE_POST_BODY + ) + try { + if ($METHOD -eq 'POST') { + $PAGE_RESULTS = Invoke-RestMethod -Uri $URI -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method $METHOD -Body $ONE_POST_BODY -ContentType 'application/json' + } + else { + $PAGE_RESULTS = Invoke-RestMethod -Uri $URI -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method $METHOD -ContentType 'application/json' + #$PAGE_RESULTS | ConvertTo-Json -Depth 100 | Write-Debug + } + } + catch { + # Catch 429 errors and wait for the retry-after time + if ($_.Exception.Response.StatusCode -eq 429) { + Write-Warn "429 error, waiting for $RETRY_AFTER seconds..." + Start-Sleep -Seconds $RETRY_AFTER + Get-PageResult -URI $URI -ONE_POST_BODY $ONE_POST_BODY + } + else { + Write-Error "Error: $($_.Exception.Message)" + throw 'Get-PageResult failed' + } + } + if ($PAGE_RESULTS.isLast -eq $false) { + # if PAGE_RESULTS has a value for key 'nextPageToken' then set it + #Write-Debug 'More pages to get, getting next page...' + if ($PAGE_RESULTS.nextPageToken) { + #Write-Debug "Next page token: $($PAGE_RESULTS.nextPageToken)" + # Update if the method is POST, update the ONE_POST_BODY with the nextPageToken + if ($METHOD -eq 'POST') { + $ONE_POST_BODY = $ONE_POST_BODY | ConvertFrom-Json + $ONE_POST_BODY.nextPageToken = $PAGE_RESULTS.nextPageToken + #$ONE_POST_BODY = $ONE_POST_BODY | ConvertTo-Json + } + else { + $URI = $URI + "&nextPageToken=$($PAGE_RESULTS.nextPageToken)" + } + } + elseif ($PAGE_RESULTS.nextPage) { + Write-Debug "Next page: $($PAGE_RESULTS.nextPage)" + if ($METHOD -eq 'POST') { + Write-Error "$($MyInvocation.MyCommand) does not support POST method with nextPage. Exiting..." + } + else { + $URI = $PAGE_RESULTS.nextPage + } + } + Get-PageResult -URI $URI -METHOD $METHOD + } + if ($RESPONSE_JSON_OBJECT_FILTER_KEY) { + $PAGE_RESULTS = $PAGE_RESULTS.$RESPONSE_JSON_OBJECT_FILTER_KEY + } + $PAGE_RESULTS + } + if ($POST_BODY) { + $RESULTS_ARRAY = Get-PageResult -URI $URI -METHOD $METHOD -ONE_POST_BODY $POST_BODY + } + else { + $RESULTS_ARRAY = Get-PageResult -URI $URI -METHOD $METHOD + } + Write-Debug "$($MyInvocation.MyCommand) results:" + # $RESULTS_ARRAY | ConvertTo-Json -Depth 100 -Compress | Write-Debug + return $RESULTS_ARRAY | ConvertTo-Json -Depth 100 -Compress +} function Get-AtlassianPowerKitProfileList { Write-Debug 'Getting AtlassianPowerKit Profile List...' @@ -341,7 +427,6 @@ function Register-AtlassianPowerKitProfile { $LOADED_PROFILE = Set-AtlassianPowerKitProfile -SelectedProfileName $ProfileName return $LOADED_PROFILE } - # Function to set the Atlassian Cloud API headers function Set-AtlassianAPIHeaders { # check if there is a profile loaded @@ -421,7 +506,7 @@ function Set-AtlassianPowerKitProfile { # Function to test if AtlassianPowerKit profile authenticates successfully function Test-AtlassianPowerKitProfile { Write-Debug 'Testing Atlassian Cloud PowerKit Profile...' - #Write-Debug "API Headers: $($script:AtlassianAPIHeaders | Format-List * | Out-String)" + #Write-Debug "API Headers: $($script:AtlassianAPIHeaders | Format-List * | Out-String)' Write-Debug "API Endpoint: $($env:AtlassianPowerKit_AtlassianAPIEndpoint) ..." Write-Debug "API Headers: $($env:AtlassianPowerKit_AtlassianAPIHeaders) ..." $HEADERS = ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders diff --git a/AtlassianPowerKit.psm1 b/AtlassianPowerKit.psm1 index 32fe2d8..22d95b1 100644 --- a/AtlassianPowerKit.psm1 +++ b/AtlassianPowerKit.psm1 @@ -1,3 +1,4 @@ +$ErrorActionPreference = 'Stop'; $DebugPreference = 'Continue' <# .SYNOPSIS Atlassian Cloud PowerKit module for interacting with Atlassian Cloud REST API. @@ -20,7 +21,6 @@ GitHub: #> -$ErrorActionPreference = 'Stop'; $DebugPreference = 'Continue' function Get-RequisitePowerKitModules { $AtlassianPowerKitRequiredModules = @('PowerShellGet', 'Microsoft.PowerShell.SecretManagement', 'Microsoft.PowerShell.SecretStore') $AtlassianPowerKitRequiredModules | ForEach-Object { @@ -98,35 +98,6 @@ function Test-OSMHomeDir { return $ValidatedOSMHome } -# function Invoke-AtlassianPowerKitFunction { -# param ( -# [Parameter(Mandatory = $true)] -# [string] $FunctionName, -# [Parameter(Mandatory = $false)] -# [hashtable] $FunctionParameters -# ) -# $TEMP_DIR = "$env:OSM_HOME\$env:AtlassianPowerKit_PROFILE_NAME\.temp" -# if (-not (Test-Path $TEMP_DIR)) { -# New-Item -ItemType Directory -Path $TEMP_DIR -Force | Out-Null -# } -# $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() -# $stopwatch.Start() | Out-Null -# if ($FunctionParameters) { -# $singleLineDefinition = $FunctionParameters.Keys | ForEach-Object { "- -> $_ = $($FunctionParameters.($_))" } -# Write-Debug "Running function: $FunctionName with parameters: $singleLineDefinition" -# # Run the function with the parameters and capture the returned object -# $RETURN_OBJECT = $FunctionName @FunctionParameters | ConvertTo-Json -Depth 100 -Compress -EnumsAsStrings -# } -# else { -# $RETURN_OBJECT = $(Invoke-Expression "$FunctionName") -# } -# $stopwatch.Stop() | Out-Null -# Write-Debug "Function $FunctionName completed - execution time: $($stopwatch.Elapsed.TotalSeconds) seconds" -# $RETURN_JSON = $RETURN_OBJECT | ConvertTo-Json -Depth 100 -Compress -# Write-Debug "Returning JSON of size: $($RETURN_JSON.Length) characters" -# #$RETURN_JSON | ConvertTo-Json -Depth 50 | Write-Debug -# return $RETURN_JSON -# } function Invoke-AtlassianPowerKitFunction { param ( [Parameter(Mandatory = $true)] @@ -212,7 +183,7 @@ function Show-AtlassianPowerKitFunctions { $colorIndex++ #Write-Debug $MODULE_NAME #Get-Module -Name $MODULE_NAME - $FunctionList = (Get-Module -Name $MODULE_NAME).ExportedFunctions.Keys + $FunctionList = (Get-Module -Name $MODULE_NAME -All).ExportedFunctions.Keys $FunctionList | ForEach-Object { $functionReferences += $_ Write-Host ' ' -NoNewline -BackgroundColor "Dark$color" @@ -343,7 +314,7 @@ function AtlassianPowerKit { $env:AtlassianPowerKit_RequisiteModules = Get-RequisitePowerKitModules Write-Debug 'AtlassianPowerKit_RequisiteModules - Required modules imported' } - $NESTED_MODULES = Import-NestedModules -NESTED_MODULES @('AtlassianPowerKit-Shared', 'AtlassianPowerKit-Jira', 'AtlassianPowerKit-Confluence', 'AtlassianPowerKit-GRCosm', 'AtlassianPowerKit-JSM', 'AtlassianPowerKit-UsersAndGroups') + $NESTED_MODULES = Import-NestedModules -NESTED_MODULES @('AtlassianPowerKit-Shared', 'AtlassianPowerKit-Jira', 'AtlassianPowerKit-Confluence', 'AtlassianPowerKit-GRCosm', 'AtlassianPowerKit-JSM', 'AtlassianPowerKit-UsersAndGroups', 'AtlassianPowerKit-Admin') try { #Push-Location -Path $PSScriptRoot -ErrorAction Continue Write-Debug "Starting AtlassianPowerKit, running from $((Get-Item -Path $PSScriptRoot).FullName)" diff --git a/AtlassinPowerKit-JIRAGRCosmDeploy/AtlassianPowerKit-JIRAGRCosmDeploy.psm1 b/AtlassinPowerKit-JIRAGRCosmDeploy/AtlassianPowerKit-JIRAGRCosmDeploy.psm1 deleted file mode 100644 index 9210021..0000000 --- a/AtlassinPowerKit-JIRAGRCosmDeploy/AtlassianPowerKit-JIRAGRCosmDeploy.psm1 +++ /dev/null @@ -1,30 +0,0 @@ -<# -.SYNOPSIS - Atlassian Cloud PowerShell Module - AtlassianPowerKit-JIRAGRCosmDeploy - module for creating new issue types, workflows, fields, screens, screen schemes, issue type screen schemes, and issue type screen scheme associations using the Atlassian Jira Cloud REST API.See https://developer.atlassian.com/cloud/jira/platform/rest/v3/ for more information. - -.DESCRIPTION - Atlassian Cloud PowerShell Module - AtlassianPowerKit-JIRAGRCosmDeploy - - Dependencies: AtlassianPowerKit-Shared - - New-AtlassianAPIEndpoint - For list of functions and cmdlets, run Get-Command -Module AtlassianPowerKit-JIRAGRCosmDeploy.psm1 - -.EXAMPLE - New-JiraIssueType -JiraCloudProjectKey 'OSM' -JiraIssueTypeName 'Test Issue Type' -JiraIssueTypeDescription 'This is a test issue type.' -JiraIssueTypeAvatarId '10000' - -.LINK -GitHub: https://github.com/OrganisationServiceManagement/AtlassianPowerKit.git - -#> - -$ErrorActionPreference = 'Stop'; $DebugPreference = 'Continue' - -# Listing issue Types -function Get-MarkDownJIRAIssueTypes { - param ( - [P ] - [Parameter(Mandatory = $false)] - [string]$OUTPUT_PATH = "$($env:OSM_HOME)\$($env:AtlassianPowerKit_PROFILE_NAME)\JIRA" - ) -} - -Get-Content .\cpk-HROSM-IssueTypes-20241117-145239.json | ConvertFrom-Json -Depth 100 | ForEach-Object { Write-Host "- [$($_.Name)]($($_.self)) - [Show All Instances](https://cpksystems.atlassian.net/jira/servicedesk/projects/HROSM/issues/?jql=project%20%3D%20HROSM%20AND%20issuetype%20%3D%20%22$($_.name.replace(' ','%20'))%22)" diff --git a/Run.ps1 b/Run.ps1 index c960753..e1cee57 100644 --- a/Run.ps1 +++ b/Run.ps1 @@ -6,11 +6,10 @@ $env:OSM_INSTALL = '/opt/osm' # Import necessary modules Import-Module -Name Microsoft.PowerShell.SecretManagement, Microsoft.PowerShell.SecretStore -Force -Set-Location /opt/osm/AtlassianPowerKit -Import-Module /opt/osm/AtlassianPowerKit/AtlassianPowerKit.psd1 -Force +Set-Location "$env:OSM_INSTALL/AtlassianPowerKit" +Import-Module "$env:OSM_INSTALL/AtlassianPowerKit/AtlassianPowerKit.psd1" -Force $env:SECRETSTORE_PATH = $env:OSM_HOME - # Check if arguments were passed to the script if ($args.Count -gt 0) { # Run AtlassianPowerKit with the provided arguments @@ -22,5 +21,88 @@ else { AtlassianPowerKit } -## We can override the default command by passing the command as an argument to the script, e.g.: -## docker run -v osm_home:/mnt/osm --rm --mount osm_home:/mnt/osm -ti markz0r/atlassian-powerkit:latest -FunctionName Get-JiraCloudJQLQueryResult -FunctionParameters @{"JQLQuery"="project in (GRCOSM, HROSM)"}' \ No newline at end of file +Function Get-DeploymentConfigs { + param ( + [Parameter(Mandatory = $true)] + [string]$PROFILE_NAME + ) + Write-Host "Processing profile: $PROFILE_NAME" + # If there is a env:AtlassianPowerKit_PROFILE_NAME-ProjectList-*.json that was created in the last 12 hours, use it + $PROFILE_PROJECT_LIST = Test-ExistingConfigJSON -CONFIG_FILE_PATHPATTERN "$($env:OSM_HOME)\$PROFILE_NAME\$PROFILE_NAME-ProjectList-*.json" + if ($PROFILE_PROJECT_LIST) { + $PROJECT_LIST = Get-Content $PROFILE_PROJECT_LIST.FullName | ConvertFrom-Json -AsHashtable -NoEnumerate + } + else { + $PROJECT_LIST = AtlassianPowerKit -Profile $PROFILE_NAME -FunctionName 'Get-JiraProjectList' | ConvertFrom-Json -AsHashtable -NoEnumerate + } + #$PROJECT_LIST | ForEach-Object { Write-Host "Project: $($_.name) - $($_.key)" } + #$PROJECT_LIST | ConvertTo-Json -Depth 100 | Write-Debug + $OSM_PROJECT_LIST = $PROJECT_LIST | Where-Object { $_.key -match '.*OSM.*' -and $_.key -notin @('CUBOSM') } + + $JIRA_PROJECTS = $OSM_PROJECT_LIST | ForEach-Object { + $PROJECT_NAME = $($_.name) + $PROJECT_KEY = $($_.key) + # PROJECT_PROPERTIES + $PROFILE_PROJECT_PROPERTIES = Test-ExistingConfigJSON -CONFIG_FILE_PATHPATTERN "$($env:OSM_HOME)\$PROFILE_NAME\$PROFILE_NAME-$PROJECT_KEY-ProjectProperties-*.json" + if ($PROFILE_PROJECT_PROPERTIES) { + $PROFILE_PROJECT_PROPERTIES = Get-Content $PROFILE_PROJECT_PROPERTIES.FullName | ConvertFrom-Json -AsHashtable -NoEnumerate + } + else { + $PROFILE_PROJECT_PROPERTIES = AtlassianPowerKit -Profile $PROFILE_NAME -FunctionName 'Get-JiraProjectProperties' -FunctionParameters @{ PROJECT_KEY = $PROJECT_KEY } | ConvertFrom-Json -AsHashtable -NoEnumerate + } + # PROJECT_ISSUE_TYPE_SCHEMA + $PROJECT_ISSUE_TYPE_SCHEMA = Test-ExistingConfigJSON -CONFIG_FILE_PATHPATTERN "$($env:OSM_HOME)\$PROFILE_NAME\$PROFILE_NAME-$PROJECT_KEY-IssueTypeSchema-*.json" + if ($PROJECT_ISSUE_TYPE_SCHEMA) { + $PROJECT_ISSUE_TYPE_SCHEMA = Get-Content $PROJECT_ISSUE_TYPE_SCHEMA.FullName | ConvertFrom-Json -AsHashtable -NoEnumerate + } + else { + $PROJECT_ISSUE_TYPE_SCHEMA = AtlassianPowerKit -Profile $PROFILE_NAME -FunctionName 'Get-JiraCloudIssueTypeSchema' -FunctionParameters @{ PROJECT_KEY = $PROJECT_KEY } + } + # + # PROJECT_ISSUE_TYPES + $PROJECT_ISSUE_TYPES = Test-ExistingConfigJSON -CONFIG_FILE_PATHPATTERN "$($env:OSM_HOME)\$PROFILE_NAME\$PROFILE_NAME-$PROJECT_KEY-ProjectIssuesTypes-*.json" + if ($PROJECT_ISSUE_TYPES) { + $PROJECT_ISSUE_TYPES = Get-Content $PROJECT_ISSUE_TYPES.FullName | ConvertFrom-Json -AsHashtable -NoEnumerate + } + else { + $PROJECT_ISSUE_TYPES = AtlassianPowerKit -Profile $PROFILE_NAME -FunctionName 'Get-JiraProjectIssuesTypes' -FunctionParameters @{ PROJECT_KEY_OR_ID = $PROJECT_KEY } | ConvertFrom-Json -AsHashtable -NoEnumerate + } + $PROJECT_REQUEST_TYPES = Test-ExistingConfigJSON -CONFIG_FILE_PATHPATTERN "$($env:OSM_HOME)\$PROFILE_NAME\$PROFILE_NAME-$PROJECT_KEY-RequestTypeSchema-*.json" + if ($PROJECT_REQUEST_TYPES) { + $PROJECT_REQUEST_TYPES = Get-Content $PROJECT_REQUEST_TYPES.FullName | ConvertFrom-Json -AsHashtable -NoEnumerate + } + else { + $PROJECT_REQUEST_TYPES = AtlassianPowerKit -Profile $PROFILE_NAME -FunctionName 'Get-JiraServiceDeskRequestTypes' -FunctionParameters @{ PROJECT_KEY = $PROJECT_KEY } | ConvertFrom-Json -AsHashtable -NoEnumerate + } + + # FORMS + $PROJECT_FORMS = Test-ExistingConfigJSON -CONFIG_FILE_PATHPATTERN "$($env:OSM_HOME)\$PROFILE_NAME\$PROFILE_NAME-$PROJECT_KEY-Forms-*.json" + if ($PROJECT_FORMS) { + $PROJECT_FORMS = Get-Content $PROJECT_FORMS.FullName | ConvertFrom-Json -AsHashtable -NoEnumerate + } + else { + $PROJECT_FORMS = AtlassianPowerKit -Profile $PROFILE_NAME -FunctionName 'Get-FormsForJiraProject' -FunctionParameters @{ PROJECT_KEY = $PROJECT_KEY } | ConvertFrom-Json -AsHashtable -NoEnumerate + } + + # $FORMS = AtlassianPowerKit -Profile $PROFILE_NAME -FunctionName 'Get-FormsForJiraProject' -FunctionParameters @{ PROJECT_KEY = $PROJECT_KEY } + # WORKFLOW_SCHEMES + $PROJECT_WORKFLOWS_SCHEMES = Test-ExistingConfigJSON -CONFIG_FILE_PATHPATTERN "$($env:OSM_HOME)\$PROFILE_NAME\$PROFILE_NAME-$PROJECT_KEY-ProjectWorkflowSchemes-*.json" + if ($PROJECT_WORKFLOWS_SCHEMES) { + $PROJECT_WORKFLOWS_SCHEMES = Get-Content $PROJECT_WORKFLOWS_SCHEMES.FullName | ConvertFrom-Json -AsHashtable -NoEnumerate + } + else { + $PROJECT_WORKFLOWS_SCHEMES = AtlassianPowerKit -Profile $PROFILE_NAME -FunctionName 'Get-JiraProjectWorkflowSchemes' -FunctionParameters @{ PROJECT_KEY = $PROJECT_KEY } | ConvertFrom-Json -AsHashtable -NoEnumerate + } + + # Return object + [PSCustomObject]@{ + PROJECT_NAME = $PROJECT_NAME + PROJECT_KEY = $PROJECT_KEY + PROJECT_ISSUE_TYPE_SCHEMA = $PROJECT_ISSUE_TYPE_SCHEMA + PROJECT_ISSUE_TYPES = $PROJECT_ISSUE_TYPES + PROJECT_REQUEST_TYPES = $PROJECT_REQUEST_TYPES + PROJECT_WORKFLOWS_SCHEMES = $PROJECT_WORKFLOWS_SCHEMES + } + } + Return $JIRA_PROJECTS | ConvertTo-Json -Depth 100 -Compress +} From 56dff21c441483e7b40a546db6035e25523dd520 Mon Sep 17 00:00:00 2001 From: Mark Culhane Date: Fri, 10 Jan 2025 19:39:56 +1100 Subject: [PATCH 05/11] removed test files --- .../JIRA_EXPORT_API_BROKEN_VS_ADF.json | 578 -------------- AtlassianPowerKit-Jira/OP-999.json | 752 ------------------ .../OP-999_JQLResult_PartialFile.json | 565 ------------- 3 files changed, 1895 deletions(-) delete mode 100644 AtlassianPowerKit-Jira/JIRA_EXPORT_API_BROKEN_VS_ADF.json delete mode 100644 AtlassianPowerKit-Jira/OP-999.json delete mode 100644 AtlassianPowerKit-Jira/OP-999_JQLResult_PartialFile.json diff --git a/AtlassianPowerKit-Jira/JIRA_EXPORT_API_BROKEN_VS_ADF.json b/AtlassianPowerKit-Jira/JIRA_EXPORT_API_BROKEN_VS_ADF.json deleted file mode 100644 index bbbcfc0..0000000 --- a/AtlassianPowerKit-Jira/JIRA_EXPORT_API_BROKEN_VS_ADF.json +++ /dev/null @@ -1,578 +0,0 @@ -{ - "type": "doc", - "version": 1, - "content": [ - { - "type": "paragraph", - "content": { - "type": "text", - "text": "Oracle has deprecated support for Oracle 12c Database. As such, we need to upgrade all numbering DB’s to 19c." - } - }, - { - "type": "mediaSingle", - "attrs": { - "layout": "align-start" - }, - "content": { - "type": "media", - "attrs": { - "type": "file", - "id": "54e68b45-6041-4c63-b4b2-ea818eaa9fbd", - "height": 410, - "width": 1833 - } - } - }, - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "Ref: " - }, - { - "type": "text", - "text": "https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Oracle.Concepts.database-versions.html#Oracle.Concepts.FeatureSupport.12c", - "marks": { - "type": "link", - "attrs": { - "href": "https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Oracle.Concepts.database-versions.html#Oracle.Concepts.FeatureSupport.12c" - } - } - } - ] - }, - { - "type": "paragraph", - "content": { - "type": "text", - "text": "Starting June 1, AWS will start auto upgrading these DB’s to 19c. We should take this opportunity to manually upgrade and do testing." - } - }, - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "AWS provides comprehensive documentation to upgrade these Oracle versions, which covers all manual steps outlined for an on-premise upgrade - " - }, - { - "type": "text", - "text": "https://aws.amazon.com/blogs/database/best-practices-for-upgrading-amazon-rds-for-oracle-db-instances-from-12c-to-19c/#:~:text=Amazon RDS for Oracle will,existing RDS for Oracle 12.1.", - "marks": { - "type": "link", - "attrs": { - "href": "https://aws.amazon.com/blogs/database/best-practices-for-upgrading-amazon-rds-for-oracle-db-instances-from-12c-to-19c/#:~:text=Amazon%20RDS%20for%20Oracle%20will,existing%20RDS%20for%20Oracle%2012.1." - } - } - } - ] - }, - { - "type": "paragraph", - "content": { - "type": "text", - "text": "Following DB’s are in scope:" - } - }, - { - "type": "table", - "attrs": { - "layout": "default", - "localId": "8b8f6ef9-8fee-4274-ba8d-926eba4cef2b" - }, - "content": [ - { - "type": "tableRow", - "content": [ - { - "type": "tableCell", - "attrs": {}, - "content": { - "type": "paragraph", - "content": { - "type": "text", - "text": "Region" - } - } - }, - { - "type": "tableCell", - "attrs": {}, - "content": { - "type": "paragraph", - "content": { - "type": "text", - "text": "AWS accountid" - } - } - }, - { - "type": "tableCell", - "attrs": {}, - "content": { - "type": "paragraph", - "content": { - "type": "text", - "text": "DBInstanceSize" - } - } - }, - { - "type": "tableCell", - "attrs": {}, - "content": { - "type": "paragraph", - "content": { - "type": "text", - "text": "AutoMinorVersionUpgrade" - } - } - }, - { - "type": "tableCell", - "attrs": {}, - "content": { - "type": "paragraph", - "content": { - "type": "text", - "text": "DBInstanceIdentifier" - } - } - }, - { - "type": "tableCell", - "attrs": {}, - "content": { - "type": "paragraph", - "content": { - "type": "text", - "text": "Engine" - } - } - }, - { - "type": "tableCell", - "attrs": {}, - "content": { - "type": "paragraph", - "content": { - "type": "text", - "text": "EngineVersion" - } - } - } - ] - }, - { - "type": "tableRow", - "content": [ - { - "type": "tableCell", - "attrs": {}, - "content": { - "type": "paragraph", - "content": { - "type": "text", - "text": "ap-southeast-2" - } - } - }, - { - "type": "tableCell", - "attrs": {}, - "content": { - "type": "paragraph", - "content": { - "type": "text", - "text": "numbering-legacy" - } - } - }, - { - "type": "tableCell", - "attrs": {}, - "content": { - "type": "paragraph", - "content": { - "type": "text", - "text": "db.t3.xlarge" - } - } - }, - { - "type": "tableCell", - "attrs": {}, - "content": { - "type": "paragraph", - "content": { - "type": "text", - "text": "TRUE" - } - } - }, - { - "type": "tableCell", - "attrs": {}, - "content": { - "type": "paragraph", - "content": { - "type": "text", - "text": "devnumdb-2021-09-14-17-10" - } - } - }, - { - "type": "tableCell", - "attrs": {}, - "content": { - "type": "paragraph", - "content": { - "type": "text", - "text": "oracle-se2" - } - } - }, - { - "type": "tableCell", - "attrs": {}, - "content": { - "type": "paragraph", - "content": { - "type": "text", - "text": "12.1.0.2.v26" - } - } - } - ] - }, - { - "type": "tableRow", - "content": [ - { - "type": "tableCell", - "attrs": {}, - "content": { - "type": "paragraph", - "content": { - "type": "text", - "text": "ap-southeast-2" - } - } - }, - { - "type": "tableCell", - "attrs": {}, - "content": { - "type": "paragraph", - "content": { - "type": "text", - "text": "numbering-legacy" - } - } - }, - { - "type": "tableCell", - "attrs": {}, - "content": { - "type": "paragraph", - "content": { - "type": "text", - "text": "db.r5.large" - } - } - }, - { - "type": "tableCell", - "attrs": {}, - "content": { - "type": "paragraph", - "content": { - "type": "text", - "text": "TRUE" - } - } - }, - { - "type": "tableCell", - "attrs": {}, - "content": { - "type": "paragraph", - "content": { - "type": "text", - "text": "num-ote-db" - } - } - }, - { - "type": "tableCell", - "attrs": {}, - "content": { - "type": "paragraph", - "content": { - "type": "text", - "text": "oracle-se2" - } - } - }, - { - "type": "tableCell", - "attrs": {}, - "content": { - "type": "paragraph", - "content": { - "type": "text", - "text": "12.1.0.2.v26" - } - } - } - ] - }, - { - "type": "tableRow", - "content": [ - { - "type": "tableCell", - "attrs": {}, - "content": { - "type": "paragraph", - "content": { - "type": "text", - "text": "ap-southeast-2" - } - } - }, - { - "type": "tableCell", - "attrs": {}, - "content": { - "type": "paragraph", - "content": { - "type": "text", - "text": "numbering-legacy" - } - } - }, - { - "type": "tableCell", - "attrs": {}, - "content": { - "type": "paragraph", - "content": { - "type": "text", - "text": "db.r5.large" - } - } - }, - { - "type": "tableCell", - "attrs": {}, - "content": { - "type": "paragraph", - "content": { - "type": "text", - "text": "TRUE" - } - } - }, - { - "type": "tableCell", - "attrs": {}, - "content": { - "type": "paragraph", - "content": { - "type": "text", - "text": "otenumdb-20220130" - } - } - }, - { - "type": "tableCell", - "attrs": {}, - "content": { - "type": "paragraph", - "content": { - "type": "text", - "text": "oracle-se2" - } - } - }, - { - "type": "tableCell", - "attrs": {}, - "content": { - "type": "paragraph", - "content": { - "type": "text", - "text": "12.1.0.2.v26" - } - } - } - ] - }, - { - "type": "tableRow", - "content": [ - { - "type": "tableCell", - "attrs": {}, - "content": { - "type": "paragraph", - "content": { - "type": "text", - "text": "ap-southeast-2" - } - } - }, - { - "type": "tableCell", - "attrs": {}, - "content": { - "type": "paragraph", - "content": { - "type": "text", - "text": "numbering-legacy" - } - } - }, - { - "type": "tableCell", - "attrs": {}, - "content": { - "type": "paragraph", - "content": { - "type": "text", - "text": "db.r4.large" - } - } - }, - { - "type": "tableCell", - "attrs": {}, - "content": { - "type": "paragraph", - "content": { - "type": "text", - "text": "FALSE" - } - } - }, - { - "type": "tableCell", - "attrs": {}, - "content": { - "type": "paragraph", - "content": { - "type": "text", - "text": "prodnumdb" - } - } - }, - { - "type": "tableCell", - "attrs": {}, - "content": { - "type": "paragraph", - "content": { - "type": "text", - "text": "oracle-se2" - } - } - }, - { - "type": "tableCell", - "attrs": {}, - "content": { - "type": "paragraph", - "content": { - "type": "text", - "text": "12.1.0.2.v18" - } - } - } - ] - } - ] - }, - { - "type": "paragraph", - "content": { - "type": "text", - "text": "Recommendation:" - } - }, - { - "type": "orderedList", - "content": [ - { - "type": "listItem", - "content": [ - { - "type": "paragraph", - "content": { - "type": "text", - "text": "Manually upgrade DEV database, and perform application PVT and performance testing" - } - }, - { - "type": "orderedList", - "content": [ - { - "type": "listItem", - "content": [ - { - "type": "paragraph", - "content": { - "type": "text", - "text": "If issues arise, consider the following:" - } - }, - { - "type": "codeBlock", - "attrs": {}, - "content": { - "type": "text", - "text": "After the upgrade, if the SQL statements perform poorly due to the change in the plans by the 19c optimizer, you can use OPTIMIZER_FEATURES_ENABLE. This parameter is alterable at the session level and system level. For example, if you upgrade your database from release 12c to release 19c, but you want to keep the release 12c optimizer behavior, you can do so by setting this parameter to 12c. At a later time, you can try the enhancements introduced in releases up to and including release 19c by setting the parameter to 19.0.0." - } - } - ] - }, - { - "type": "listItem", - "content": { - "type": "paragraph", - "content": { - "type": "text", - "text": "If issues still occur, application-level code fixes would be required. This might be a good oppurtunity to migrate the app to use Aurora Postgres or Aurora MySQL instead, which AWS provide tools to hlep migrate code and data." - } - } - } - ] - } - ] - }, - { - "type": "listItem", - "content": { - "type": "paragraph", - "content": { - "type": "text", - "text": "If all successful, upgrade OTE" - } - } - }, - { - "type": "listItem", - "content": { - "type": "paragraph", - "content": { - "type": "text", - "text": "If all successful, upgrade PROD and enable ā€œauto minor version upgradeā€" - } - } - } - ] - } - ] -} diff --git a/AtlassianPowerKit-Jira/OP-999.json b/AtlassianPowerKit-Jira/OP-999.json deleted file mode 100644 index 007d825..0000000 --- a/AtlassianPowerKit-Jira/OP-999.json +++ /dev/null @@ -1,752 +0,0 @@ -{ - "type": "doc", - "version": 1, - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "Oracle has deprecated support for Oracle 12c Database. As such, we need to upgrade all numbering DB’s to 19c." - } - ] - }, - { - "type": "mediaSingle", - "attrs": { - "layout": "align-start" - }, - "content": [ - { - "type": "media", - "attrs": { - "type": "file", - "id": "54e68b45-6041-4c63-b4b2-ea818eaa9fbd", - "collection": "", - "height": 410, - "width": 1833 - } - } - ] - }, - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "Ref: " - }, - { - "type": "text", - "text": "https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Oracle.Concepts.database-versions.html#Oracle.Concepts.FeatureSupport.12c", - "marks": [ - { - "type": "link", - "attrs": { - "href": "https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Oracle.Concepts.database-versions.html#Oracle.Concepts.FeatureSupport.12c" - } - } - ] - } - ] - }, - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "Starting June 1, AWS will start auto upgrading these DB’s to 19c. We should take this opportunity to manually upgrade and do testing." - } - ] - }, - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "AWS provides comprehensive documentation to upgrade these Oracle versions, which covers all manual steps outlined for an on-premise upgrade - " - }, - { - "type": "text", - "text": "https://aws.amazon.com/blogs/database/best-practices-for-upgrading-amazon-rds-for-oracle-db-instances-from-12c-to-19c/#:~:text=Amazon RDS for Oracle will,existing RDS for Oracle 12.1.", - "marks": [ - { - "type": "link", - "attrs": { - "href": "https://aws.amazon.com/blogs/database/best-practices-for-upgrading-amazon-rds-for-oracle-db-instances-from-12c-to-19c/#:~:text=Amazon%20RDS%20for%20Oracle%20will,existing%20RDS%20for%20Oracle%2012.1." - } - } - ] - } - ] - }, - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "Following DB’s are in scope:" - } - ] - }, - { - "type": "table", - "attrs": { - "isNumberColumnEnabled": false, - "layout": "default", - "localId": "8b8f6ef9-8fee-4274-ba8d-926eba4cef2b" - }, - "content": [ - { - "type": "tableRow", - "content": [ - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "Region" - } - ] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "AWS accountid" - } - ] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "DBInstanceSize" - } - ] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "AutoMinorVersionUpgrade" - } - ] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "DBInstanceIdentifier" - } - ] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "Engine" - } - ] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "EngineVersion" - } - ] - } - ] - } - ] - }, - { - "type": "tableRow", - "content": [ - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "ap-southeast-2" - } - ] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "numbering-legacy" - } - ] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "db.t3.xlarge" - } - ] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "TRUE" - } - ] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "devnumdb-2021-09-14-17-10" - } - ] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "oracle-se2" - } - ] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "12.1.0.2.v26" - } - ] - } - ] - } - ] - }, - { - "type": "tableRow", - "content": [ - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "ap-southeast-2" - } - ] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "numbering-legacy" - } - ] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "db.r5.large" - } - ] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "TRUE" - } - ] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "num-ote-db" - } - ] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "oracle-se2" - } - ] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "12.1.0.2.v26" - } - ] - } - ] - } - ] - }, - { - "type": "tableRow", - "content": [ - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "ap-southeast-2" - } - ] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "numbering-legacy" - } - ] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "db.r5.large" - } - ] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "TRUE" - } - ] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "otenumdb-20220130" - } - ] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "oracle-se2" - } - ] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "12.1.0.2.v26" - } - ] - } - ] - } - ] - }, - { - "type": "tableRow", - "content": [ - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "ap-southeast-2" - } - ] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "numbering-legacy" - } - ] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "db.r4.large" - } - ] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "FALSE" - } - ] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "prodnumdb" - } - ] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "oracle-se2" - } - ] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "12.1.0.2.v18" - } - ] - } - ] - } - ] - } - ] - }, - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "Recommendation:" - } - ] - }, - { - "type": "orderedList", - "content": [ - { - "type": "listItem", - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "Manually upgrade DEV database, and perform application PVT and performance testing" - } - ] - }, - { - "type": "orderedList", - "content": [ - { - "type": "listItem", - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "If issues arise, consider the following:" - } - ] - }, - { - "type": "codeBlock", - "attrs": {}, - "content": [ - { - "type": "text", - "text": "After the upgrade, if the SQL statements perform poorly due to the change in the plans by the 19c optimizer, you can use OPTIMIZER_FEATURES_ENABLE. This parameter is alterable at the session level and system level. For example, if you upgrade your database from release 12c to release 19c, but you want to keep the release 12c optimizer behavior, you can do so by setting this parameter to 12c. At a later time, you can try the enhancements introduced in releases up to and including release 19c by setting the parameter to 19.0.0." - } - ] - } - ] - }, - { - "type": "listItem", - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "If issues still occur, application-level code fixes would be required. This might be a good oppurtunity to migrate the app to use Aurora Postgres or Aurora MySQL instead, which AWS provide tools to hlep migrate code and data." - } - ] - } - ] - } - ] - } - ] - }, - { - "type": "listItem", - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "If all successful, upgrade OTE" - } - ] - } - ] - }, - { - "type": "listItem", - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "If all successful, upgrade PROD and enable ā€œauto minor version upgradeā€" - } - ] - } - ] - } - ] - } - ] -} diff --git a/AtlassianPowerKit-Jira/OP-999_JQLResult_PartialFile.json b/AtlassianPowerKit-Jira/OP-999_JQLResult_PartialFile.json deleted file mode 100644 index 750b11c..0000000 --- a/AtlassianPowerKit-Jira/OP-999_JQLResult_PartialFile.json +++ /dev/null @@ -1,565 +0,0 @@ -{ - "type": "doc", - "version": 1, - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "Oracle has deprecated support for Oracle 12c Database. As such, we need to upgrade all numbering DB’s to 19c." - } - ] - }, - { - "type": "mediaSingle", - "attrs": { "layout": "align-start" }, - "content": [ - { - "type": "media", - "attrs": { - "type": "file", - "id": "54e68b45-6041-4c63-b4b2-ea818eaa9fbd", - "collection": "", - "height": 410, - "width": 1833 - } - } - ] - }, - { - "type": "paragraph", - "content": [ - { "type": "text", "text": "Ref: " }, - { - "type": "text", - "text": "https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Oracle.Concepts.database-versions.html#Oracle.Concepts.FeatureSupport.12c", - "marks": [ - { - "type": "link", - "attrs": { - "href": "https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Oracle.Concepts.database-versions.html#Oracle.Concepts.FeatureSupport.12c" - } - } - ] - } - ] - }, - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "Starting June 1, AWS will start auto upgrading these DB’s to 19c. We should take this opportunity to manually upgrade and do testing." - } - ] - }, - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "AWS provides comprehensive documentation to upgrade these Oracle versions, which covers all manual steps outlined for an on-premise upgrade - " - }, - { - "type": "text", - "text": "https://aws.amazon.com/blogs/database/best-practices-for-upgrading-amazon-rds-for-oracle-db-instances-from-12c-to-19c/#:~:text=Amazon RDS for Oracle will,existing RDS for Oracle 12.1.", - "marks": [ - { - "type": "link", - "attrs": { - "href": "https://aws.amazon.com/blogs/database/best-practices-for-upgrading-amazon-rds-for-oracle-db-instances-from-12c-to-19c/#:~:text=Amazon%20RDS%20for%20Oracle%20will,existing%20RDS%20for%20Oracle%2012.1." - } - } - ] - } - ] - }, - { - "type": "paragraph", - "content": [{ "type": "text", "text": "Following DB’s are in scope:" }] - }, - { - "type": "table", - "attrs": { - "isNumberColumnEnabled": false, - "layout": "default", - "localId": "8b8f6ef9-8fee-4274-ba8d-926eba4cef2b" - }, - "content": [ - { - "type": "tableRow", - "content": [ - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [{ "type": "text", "text": "Region" }] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [{ "type": "text", "text": "AWS accountid" }] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [{ "type": "text", "text": "DBInstanceSize" }] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [ - { "type": "text", "text": "AutoMinorVersionUpgrade" } - ] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [ - { "type": "text", "text": "DBInstanceIdentifier" } - ] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [{ "type": "text", "text": "Engine" }] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [{ "type": "text", "text": "EngineVersion" }] - } - ] - } - ] - }, - { - "type": "tableRow", - "content": [ - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [{ "type": "text", "text": "ap-southeast-2" }] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [{ "type": "text", "text": "numbering-legacy" }] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [{ "type": "text", "text": "db.t3.xlarge" }] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [{ "type": "text", "text": "TRUE" }] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [ - { "type": "text", "text": "devnumdb-2021-09-14-17-10" } - ] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [{ "type": "text", "text": "oracle-se2" }] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [{ "type": "text", "text": "12.1.0.2.v26" }] - } - ] - } - ] - }, - { - "type": "tableRow", - "content": [ - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [{ "type": "text", "text": "ap-southeast-2" }] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [{ "type": "text", "text": "numbering-legacy" }] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [{ "type": "text", "text": "db.r5.large" }] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [{ "type": "text", "text": "TRUE" }] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [{ "type": "text", "text": "num-ote-db" }] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [{ "type": "text", "text": "oracle-se2" }] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [{ "type": "text", "text": "12.1.0.2.v26" }] - } - ] - } - ] - }, - { - "type": "tableRow", - "content": [ - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [{ "type": "text", "text": "ap-southeast-2" }] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [{ "type": "text", "text": "numbering-legacy" }] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [{ "type": "text", "text": "db.r5.large" }] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [{ "type": "text", "text": "TRUE" }] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [{ "type": "text", "text": "otenumdb-20220130" }] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [{ "type": "text", "text": "oracle-se2" }] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [{ "type": "text", "text": "12.1.0.2.v26" }] - } - ] - } - ] - }, - { - "type": "tableRow", - "content": [ - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [{ "type": "text", "text": "ap-southeast-2" }] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [{ "type": "text", "text": "numbering-legacy" }] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [{ "type": "text", "text": "db.r4.large" }] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [{ "type": "text", "text": "FALSE" }] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [{ "type": "text", "text": "prodnumdb" }] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [{ "type": "text", "text": "oracle-se2" }] - } - ] - }, - { - "type": "tableCell", - "attrs": {}, - "content": [ - { - "type": "paragraph", - "content": [{ "type": "text", "text": "12.1.0.2.v18" }] - } - ] - } - ] - } - ] - }, - { - "type": "paragraph", - "content": [{ "type": "text", "text": "Recommendation:" }] - }, - { - "type": "orderedList", - "content": [ - { - "type": "listItem", - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "Manually upgrade DEV database, and perform application PVT and performance testing" - } - ] - }, - { - "type": "orderedList", - "content": [ - { - "type": "listItem", - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "If issues arise, consider the following:" - } - ] - }, - { - "type": "codeBlock", - "attrs": {}, - "content": [ - { - "type": "text", - "text": "After the upgrade, if the SQL statements perform poorly due to the change in the plans by the 19c optimizer, you can use OPTIMIZER_FEATURES_ENABLE. This parameter is alterable at the session level and system level. For example, if you upgrade your database from release 12c to release 19c, but you want to keep the release 12c optimizer behavior, you can do so by setting this parameter to 12c. At a later time, you can try the enhancements introduced in releases up to and including release 19c by setting the parameter to 19.0.0." - } - ] - } - ] - }, - { - "type": "listItem", - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "If issues still occur, application-level code fixes would be required. This might be a good oppurtunity to migrate the app to use Aurora Postgres or Aurora MySQL instead, which AWS provide tools to hlep migrate code and data." - } - ] - } - ] - } - ] - } - ] - }, - { - "type": "listItem", - "content": [ - { - "type": "paragraph", - "content": [ - { "type": "text", "text": "If all successful, upgrade OTE" } - ] - } - ] - }, - { - "type": "listItem", - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "If all successful, upgrade PROD and enable ā€œauto minor version upgradeā€" - } - ] - } - ] - } - ] - } - ] -} From 117287e6afd7ef3e9a3b4898663c6e1d1b09da35 Mon Sep 17 00:00:00 2001 From: Mark Culhane Date: Sat, 25 Jan 2025 22:43:30 +1100 Subject: [PATCH 06/11] synch --- .../Updating-Confluence-Pages.md | 4 +- .../AtlassianPowerKit-Jira.psd1 | 4 +- .../AtlassianPowerKit-Jira.psm1 | 156 ++++-- .../AtlassianPowerKit-Shared.psd1 | 5 +- .../AtlassianPowerKit-Shared.psm1 | 494 +++++++++--------- AtlassianPowerKit.psm1 | 351 ++++++++----- Dockerfile | 24 +- README.md | 24 +- Run.ps1 | 7 +- osm_home/tmp | 1 - 10 files changed, 625 insertions(+), 445 deletions(-) delete mode 100644 osm_home/tmp diff --git a/AtlassianPowerKit-Confluence/Updating-Confluence-Pages.md b/AtlassianPowerKit-Confluence/Updating-Confluence-Pages.md index 792d600..0c02c7d 100644 --- a/AtlassianPowerKit-Confluence/Updating-Confluence-Pages.md +++ b/AtlassianPowerKit-Confluence/Updating-Confluence-Pages.md @@ -1 +1,3 @@ -# +# Notes on updating Confluence pages + +- Using the Confluence REST API to update pages is a bit tricky. The API is not very well documented and the examples are not very helpful. Here are some notes on how to update Confluence pages using the REST API. diff --git a/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psd1 b/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psd1 index d1b8cf4..ec19b4a 100644 --- a/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psd1 +++ b/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psd1 @@ -103,7 +103,9 @@ 'Set-JiraIssueFieldForJQLQueryResults', 'Set-JiraProjectProperty', 'Set-AttachedFormsExternalJQLQuery', - 'Set-AttachedFormsExternal' + 'Set-AttachedFormsExternal', + 'Set-OSMRelationFieldBulkSQL', + 'Set-OSMRelationFieldIssueKey' ) # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. diff --git a/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psm1 b/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psm1 index 47cfb93..97cd297 100644 --- a/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psm1 +++ b/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psm1 @@ -15,7 +15,7 @@ - Get-JiraIssueChangeNulls -Key $Key - Get-JiraIssueChangeLog -Key $Key - Get-JiraFields - - Set-JiraIssueField -ISSUE_KEY $ISSUE_KEY -Field_Ref $Field_Ref -New_Value $New_Value -FieldType $FieldType + - Set-JiraIssueField -ISSUE_KEY $ISSUE_KEY -Field_Ref $Field_Ref -New_Value $New_Value -FIELD_TYPE $FIELD_TYPE - Set-JiraCustomField -FIELD_NAME $FIELD_NAME -FIELD_TYPE $FIELD_TYPE - Project - Get-JiraProjectProperty @@ -88,6 +88,30 @@ function Convert-JiraIssueToTableRow { return $TABLE_ROW } + +function ConvertTo-JSONMarkdownList { + param ( + [Parameter(Mandatory = $true)] + [string]$JSON_DATA_STRING + ) + # Convert JSON to PowerShell Object + $data = $JSON_DATA_STRING | ConvertFrom-Json + + # Initialize Markdown content + $markdown = '' + + # Iterate over JSON keys and build Markdown + foreach ($key in $data.PSObject.Properties.Name) { + $markdown += "* *$($key)*:`n" # Add the top-level heading + foreach ($item in $data.$key) { + $markdown += "** [$item|$item|smart-link] `n" # Add the nested bullet point with a link + } + } + Write-Debug "Markdown: $markdown" + # Output Markdown + return $markdown +} + function Export-RestorableJiraBackupJQL { param ( [Parameter(Mandatory = $true)] @@ -342,8 +366,8 @@ function Get-JiraIssueChangeNullsFromJQL { } # Write formated list of null changes to terminal $NULL_CHANGE_ITEMS | ForEach-Object { - Write-Debug "$($_.key) - Field: $($_.field) (ID: $($_.fieldId)), Type: $($_.fieldtype) --- Value nulled: $($_.from) [Created: $($_.created) - Author: $($_.author)]' - #Write-Debug 'Restore with: Set-JiraIssueField -ISSUE_KEY $($_.key) -Field_Ref $($_.fieldId) -New_Value $($_.from) -FieldType $($_.fieldtype)" + Write-Debug "$($_.key) - Field: $($_.field) (ID: $($_.fieldId)), Type: $($_.FIELD_TYPE) --- Value nulled: $($_.from) [Created: $($_.created) - Author: $($_.author)]' + #Write-Debug 'Restore with: Set-JiraIssueField -ISSUE_KEY $($_.key) -Field_Ref $($_.fieldId) -New_Value $($_.from) -FIELD_TYPE $($_.fieldtype)" } $ATTEMPT_RESTORE = Read-Host 'Do you want to attempt to restore the nulled values? Y/N [N]' if ($ATTEMPT_RESTORE -eq 'Y') { @@ -369,7 +393,7 @@ function Get-JiraIssueChangeNullsFromJQL { $TARGET_FIELD = $_.fieldId } - Set-JiraIssueField -ISSUE_KEY $_.key -Field_Ref $TARGET_FIELD -New_Value $New_Value -FieldType $_.fieldtype + Set-JiraIssueField -ISSUE_KEY $_.key -Field_Ref $TARGET_FIELD -New_Value $New_Value -FIELD_TYPE $_.fieldtype Write-Debug "Updated: $($_.issue) - Field: $($_.field): Value restored: $($_.fromString) --- data_val:[$($_.from)]" } @@ -534,7 +558,7 @@ function Get-JiraIssueLinks { Write-Error "Error updating field: $($_.Exception.Message)" } $ISSUE_LINKS_JSON_ARRAY = $ISSUE_LINKS.fields.issuelinks - return $ISSUE_LINKS_JSON_ARRAY + return $ISSUE_LINKS_JSON_ARRAY | ConvertTo-Json -Depth 60 } function Clear-EmptyFields { @@ -592,10 +616,6 @@ function Get-JiraCloudJQLQueryResult { [Parameter(Mandatory = $false)] [switch]$ReturnJSONOnly = $false ) - $OSM_TEMP_DIR = "$($env:OSM_HOME)\$($env:AtlassianPowerKit_PROFILE_NAME)\JIRA\.temp" - if (-not (Test-Path $OSM_TEMP_DIR)) { - New-Item -ItemType Directory -Path $OSM_TEMP_DIR -Force | Out-Null - } if (! $ReturnJSONOnly) { $OUTPUT_DIR = "$($env:OSM_HOME)\$($env:AtlassianPowerKit_PROFILE_NAME)\JIRA" $OUTPUT_FILE = "$OUTPUT_DIR\JIRA-Query-Results-$(Get-Date -Format 'yyyyMMdd-HHmmss').json" @@ -626,7 +646,7 @@ function Get-JiraCloudJQLQueryResult { } } $POST_BODY.expand = 'names' - $POST_BODY.maxResults = 5000 + $POST_BODY.maxResults = 1000 if ($RETURN_FIELDS -and $null -ne $RETURN_FIELDS -and $RETURN_FIELDS.Count -gt 0) { $POST_BODY.fields = $RETURN_FIELDS } @@ -634,20 +654,17 @@ function Get-JiraCloudJQLQueryResult { Write-Debug 'RETURN_FIELDS not provided, using default fields...' $POST_BODY.fields = @('*all', '-issuelinks', '-subtasks', '-worklog', '-changelog', '-comment') } - # sequence for 0 to $VALIDATE_QUERY.total in increments of 100 - # Set contents of $OUTPUT_FILE '[ - #'[' | Out-File -FilePath $OUTPUT_FILE - # $OUTPUT_FILE_LIST = 0..($DYN_LIMIT / 100) | ForEach-Object -Parallel { + Write-Debug 'Getting JQL results via /rest/api/3/search/jql...' + $NEXT_PAGE = $null + $COMPLETE = $false $ISSUE_ARRAY = @() + $PAGE_COUNT = 0 try { - $NEXT_PAGE = $false do { + $PAGE_COUNT++ if ($NEXT_PAGE) { - Write-Debug "There are more results, getting next page with token: $NEXT_PAGE" $POST_BODY.nextPageToken = $NEXT_PAGE - $OUTPUT_FILE = $OUTPUT_FILE.Replace('.json', "-$NEXT_PAGE.json") } - Write-Debug 'Getting JQL results via /rest/api/3/search/jql...' $REST_RESPONSE = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/search/jql" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Post -Body $($POST_BODY | ConvertTo-Json -Depth 10) -ContentType 'application/json' -StatusCodeVariable 'scv' Write-Debug "REST_RESPONSE received: $($REST_RESPONSE.GetType()), StatusCode: $scv" #Write-Debug "REST_RESPONSE: $($REST_RESPONSE | ConvertTo-Json -Depth 10)" @@ -663,9 +680,12 @@ function Get-JiraCloudJQLQueryResult { $COMPLETE = $false } else { - Write-Debug "$($MyInvocation.MyCommand.Name): No more results, adding $($REST_RESPONSE.issues.Count) issues to the results..." + $NEXT_PAGE = $null $COMPLETE = $true } + #$REST_RESPONSE.issues | ForEach-Object { + # Write-Output $_ | ConvertTo-Json -Depth 100 | Out-File -FilePath "$($OUTPUT_FILE)-$($PAGE_COUNT)-stream.json" -Append + #} $ISSUE_ARRAY += $REST_RESPONSE.issues Write-Debug "ISSUE_ARRAY Count: $($ISSUE_ARRAY.Count)" Write-Debug "$($MyInvocation.MyCommand.Name): End of loop, Collected Issue count: $($ISSUE_ARRAY.Count)...Completed?: $COMPLETE" @@ -688,7 +708,7 @@ function Get-JiraCloudJQLQueryResult { #$ISSUE | ConvertTo-Json -Depth 100 | Write-Debug $FIELDS_ARRAY = $ISSUE.fields #Write-Debug "FIELDS ARRAY TYPE IS: $($FIELDS_ARRAY.GetType())' - #Write-Debug 'FIELD COUNT FOR ISSUE: $($FIELDS_ARRAY.Count)" + #Write-Debug 'FIELD COUNT FOR ISSUE: $($FIELDS_ARRAY.Count)' Write-Debug "Cleaning fields for issue: $($ISSUE.key)" Write-Debug "FIELDS_ARRAY: $($FIELDS_ARRAY.GetType())" Write-Debug "FIELDS_ARRAY Count: $($FIELDS_ARRAY.Count)" @@ -733,13 +753,16 @@ function Get-JiraCloudJQLQueryResult { else { $ISSUE_ARRAY | ConvertTo-Json -Depth 100 -Compress | Out-File -FilePath $OUTPUT_FILE Write-Debug "JIRA COMBINED Query results written to: $OUTPUT_FILE" - $OUTPUT_FILE_LIST | ForEach-Object { - Remove-Item -Path $_ -Force - } #Write-Debug '########## Get-JiraCloudJQLQueryResult completed, OUTPUT_FILE_LIST: ' #$OUTPUT_FILE_LIST | Write-Debug # Combine raw, compressed JSON files into a single JSON file that is valid JSON - return $OUTPUT_FILE + $RESULT_JSON = @{ + result = 'success' + issue_count = $ISSUE_ARRAY.Count + output_file = $OUTPUT_FILE + output_dir = $OUTPUT_DIR + } + return $RESULT_JSON | ConvertTo-Json } } @@ -783,8 +806,8 @@ function Set-JiraIssueField { } #$FIELD_PAYLOAD = $FIELD_PAYLOAD | ConvertTo-Json -Depth 10 Write-Debug "### UPDATING ISSUE: https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/browse/$ISSUE_KEY" - Write-Debug "Field Type: $FieldType" - switch -regex ($FieldType) { + Write-Debug "Field Type: $FIELD_TYPE" + switch -regex ($FIELD_TYPE) { 'custom' { $FIELD_PAYLOAD = $(Set-MutliSelectPayload) } 'multi-select' { $FIELD_PAYLOAD = $(Set-MutliSelectPayload) } 'single-select' { $FIELD_PAYLOAD = @{fields = @{"$Field_Ref" = @{value = "$New_Value" } } } } @@ -794,17 +817,16 @@ function Set-JiraIssueField { } $REQUEST_URL = "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/2/issue/$($ISSUE_KEY)" # Run the REST API call to update the field with verbose debug output - Write-Debug "Field Payload: $FIELD_PAYLOAD" + Write-Debug "Field Payload: $($FIELD_PAYLOAD | ConvertTo-Json -Depth 10)" #Write-Debug "Trying: Invoke-RestMethod -Uri $REQUEST_URL -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Put -Body $FIELD_PAYLOAD -ContentType 'application/json'" try { - $UPDATE_ISSUE_RESPONSE = Invoke-RestMethod -Uri $REQUEST_URL -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Put -Body $FIELD_PAYLOAD -ContentType 'application/json' + $UPDATE_ISSUE_RESPONSE = Invoke-RestMethod -Uri $REQUEST_URL -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Put -Body $($FIELD_PAYLOAD | ConvertTo-Json -Depth 30) -ContentType 'application/json' } catch { - Write-Debug ($_ | Select-Object -Property * -ExcludeProperty psobject | Out-String) - Write-Error "Error updating field: $($_.Exception.Message)' - } - Write-Debug '$UPDATE_ISSUE_RESPONSE" + $_ | Select-Object -Property * -ExcludeProperty psobject | Out-String | Write-Debug + Write-Error "Error updating field: $($_.Exception.Message)" } + return $UPDATE_ISSUE_RESPONSE } # Function to set-jiraissuefield for a Jira issue field for all issues in JQL query results gibven the JQL query string, field name, and new value @@ -831,7 +853,7 @@ function Set-JiraIssueFieldForJQLQueryResults { $ISSUE_SUMMARY = $ISSUE.fields.summary Write-Debug "Updating fields for issue: $($_.key - $ISSUE_SUMMARY)" if (! $DryRun) { - Set-JiraIssueField -ISSUE_KEY $ISSUE_KEY -Field_Ref $FIELD_REF -New_Value $NEW_VALUE -FieldType $FIELD_TYPE + Set-JiraIssueField -ISSUE_KEY $ISSUE_KEY -Field_Ref $FIELD_REF -New_Value $NEW_VALUE -FIELD_TYPE $FIELD_TYPE } else { Write-Debug "Dry Run: Set-JiraIssueField -ISSUE_KEY $ISSUE_KEY -Field_Ref $FIELD_REF -New_Value $NEW_VALUE" @@ -839,6 +861,64 @@ function Set-JiraIssueFieldForJQLQueryResults { } } +function Set-OSMRelationFieldBulkSQL { + param ( + [Parameter(Mandatory = $true)] + [string]$JQL_STRING, + [Parameter(Mandatory = $true)] + [string]$FieldRef + ) + + $ISSUES = Get-JiraCloudJQLQueryResult -JQL_STRING $JQL_STRING -ReturnJSONOnly -RETURN_FIELDS @('key', 'summary') + $ISSUES = $ISSUES | ConvertFrom-Json + $ISSUES | ForEach-Object { + $ISSUE_KEY = $_.key + $ISSUE_SUMMARY = $_.fields.summary + Write-Debug "Updating fields for issue: $ISSUE_KEY - $ISSUE_SUMMARY" + Set-OSMRelationFieldIssueKey -IssueKey $ISSUE_KEY -FieldRef $FieldRef + } +} + +function Set-OSMRelationFieldIssueKey { + param ( + [Parameter(Mandatory = $true)] + [string]$IssueKey, + [Parameter(Mandatory = $true)] + [string]$FieldRef + ) + $textInfo = (Get-Culture).TextInfo + $LINKED_ISSUES_HASHTABLE = @{} + $ISSUE_LINKS = Get-JiraIssueLinks -IssueKey $IssueKey + # For each link type, create a nested list of linked issues + $ISSUE_LINKS | ConvertFrom-Json | ForEach-Object { + # if the $_ contains an inwardIssue, the linked issue is the inwardIssue, otherwise it is the outwardIssue + if ($_.inwardIssue) { + $LINK_TYPE_NAME = $_.type.inward + $LINKED_ISSUE = $_.inwardIssue.key + } + else { + $LINK_TYPE_NAME = $_.type.outward + $LINKED_ISSUE = $_.outwardIssue.key + } + $LINK_TYPE_NAME = $textInfo.ToTitleCase($LINK_TYPE_NAME.ToLower()) + Write-Debug "Link Type: $LINK_TYPE_NAME, Linked Issue: $LINKED_ISSUE" + if ($LINKED_ISSUES_HASHTABLE.ContainsKey($LINK_TYPE_NAME)) { + $LINKED_ISSUES_HASHTABLE[$LINK_TYPE_NAME] += @("https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/browse/$LINKED_ISSUE") + } + else { + $LINKED_ISSUES_HASHTABLE[$LINK_TYPE_NAME] = @("https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/browse/$LINKED_ISSUE") + } + } + # Output the linked issues hashtable sorted by link type and convert to JSON + $JSON_STRING = $LINKED_ISSUES_HASHTABLE | ConvertTo-Json -Depth 10 + Write-Debug "JSON_STRING: $JSON_STRING" + $MARKDOWN_TEXT = ConvertTo-JSONMarkdownList -JSON_DATA_STRING $JSON_STRING + Write-Debug "MARKDOWN_TEXT: $MARKDOWN_TEXT" + #$MARKDOWN_TEXT | Write-Debug + $UPDATE_RESPONSE = Set-JiraIssueField -ISSUE_KEY $IssueKey -Field_Ref $FieldRef -New_Value $MARKDOWN_TEXT -FIELD_TYPE 'text' + return $UPDATE_RESPONSE +} + # function to get changes from a Jira issue change log that are from a value to null function Get-JiraIssueChangeNulls { param ( @@ -891,14 +971,14 @@ function Get-JiraIssueChangeNulls { $_ | Add-Member -MemberType NoteProperty -Name 'author' -Value $MAMMA.author.emailAddress #Write-Debug $_ | Select-Object -Property * -ExcludeProperty psobject $FINAL_ITEMS += $_ - # $fieldType = '' + # $FIELD_TYPE = '' # $fieldRef = '' # switch -regex ($_.field) { - # 'Service Categories' { $fieldType = 'multi-select'; $fieldRef = 'customfield_10316' } - # 'Sensitivity Classification' { $fieldType = 'single-select'; $fieldRef = 'customfield_10275' } - # Default { $fieldType = 'text' } + # 'Service Categories' { $FIELD_TYPE = 'multi-select'; $fieldRef = 'customfield_10316' } + # 'Sensitivity Classification' { $FIELD_TYPE = 'single-select'; $fieldRef = 'customfield_10275' } + # Default { $FIELD_TYPE = 'text' } # } - # Write-Debug "Set-JiraIssueField -ISSUE_KEY $($_.key) -Field_Ref $fieldRef -New_Value $($_.fromString) -FieldType $fieldType" + # Write-Debug "Set-JiraIssueField -ISSUE_KEY $($_.key) -Field_Ref $fieldRef -New_Value $($_.fromString) -FIELD_TYPE $FIELD_TYPE" } } $FINAL_ITEMS diff --git a/AtlassianPowerKit-Shared/AtlassianPowerKit-Shared.psd1 b/AtlassianPowerKit-Shared/AtlassianPowerKit-Shared.psd1 index ed96f82..bb340ea 100644 --- a/AtlassianPowerKit-Shared/AtlassianPowerKit-Shared.psd1 +++ b/AtlassianPowerKit-Shared/AtlassianPowerKit-Shared.psd1 @@ -75,11 +75,8 @@ 'Clear-AtlassianPowerKitVault', 'Get-AtlassianPowerKitProfileList', 'Get-PaginatedJSONResults', - 'Get-CurrentAtlassianPowerKitProfile', 'Get-LevenshteinDistance', - 'Get-PopulatedTemplate', - 'Get-RequisitePowerKitModules', - 'Register-AtlassianPowerKitProfile', + 'Register-AtlassianPowerKitProfileInVault', 'Set-AtlassianPowerKitProfile', 'Remove-AtlasianPowerKitProfile', 'Unlock-Vault' diff --git a/AtlassianPowerKit-Shared/AtlassianPowerKit-Shared.psm1 b/AtlassianPowerKit-Shared/AtlassianPowerKit-Shared.psm1 index 06c61e6..9ad3da3 100644 --- a/AtlassianPowerKit-Shared/AtlassianPowerKit-Shared.psm1 +++ b/AtlassianPowerKit-Shared/AtlassianPowerKit-Shared.psm1 @@ -37,6 +37,8 @@ $ErrorActionPreference = 'Stop'; $DebugPreference = 'Continue' $VAULT_NAME = 'AtlassianPowerKitProfileVault' $VAULT_KEY_PATH = "$($env:OSM_HOME)\vault_key.xml" $RETRY_AFTER = 60 +$ENVAR_PREFIX = 'AtlassianPowerKit_' +$REQUIRED_ENV_VARS = @('AtlassianAPIEndpoint', 'AtlassianAPIUserName', 'AtlassianAPIAuthString', 'PROFILE_NAME') function Clear-AtlassianPowerKitProfile { # Clear all environment variables starting with AtlassianPowerKit_ @@ -79,8 +81,8 @@ function Clear-AtlassianPowerKitProfileDirs { } function Clear-AtlassianPowerKitVault { - Unregister-SecretVault -Name $script:VAULT_NAME - Write-Debug "Vault $script:VAULT_NAME cleared." + Unregister-SecretVault -Name $VAULT_NAME -ErrorAction Continue + Write-Debug "Vault $VAULT_NAME cleared." $VAULT_KEY = Get-VaultKey $storeConfiguration = @{ Authentication = 'Password' @@ -92,18 +94,6 @@ function Clear-AtlassianPowerKitVault { Clear-AtlassianPowerKitProfile } -# Function to check if a profile is already loaded -function Get-CurrentAtlassianPowerKitProfile { - if ($env:AtlassianPowerKit_PROFILE_NAME -and $env:AtlassianPowerKit_CloudID) { - Write-Debug "Profile $($env:AtlassianPowerKit_PROFILE_NAME) appears loaded..." - #Write-Debug "Profile $($env:AtlassianPowerKit_PROFILE_NAME) is loaded." - return $env:AtlassianPowerKit_PROFILE_NAME - } - else { - Write-Debug 'No profile loaded.' - return $false - } -} function Get-PaginatedJSONResults { param ( [Parameter(Mandatory = $true)] @@ -166,7 +156,7 @@ function Get-PaginatedJSONResults { elseif ($PAGE_RESULTS.nextPage) { Write-Debug "Next page: $($PAGE_RESULTS.nextPage)" if ($METHOD -eq 'POST') { - Write-Error "$($MyInvocation.MyCommand) does not support POST method with nextPage. Exiting..." + Write-Error "$($MyInvocation.InvocationName) does not support POST method with nextPage. Exiting..." } else { $URI = $PAGE_RESULTS.nextPage @@ -185,27 +175,24 @@ function Get-PaginatedJSONResults { else { $RESULTS_ARRAY = Get-PageResult -URI $URI -METHOD $METHOD } - Write-Debug "$($MyInvocation.MyCommand) results:" + Write-Debug "$($MyInvocation.InvocationName) results:" # $RESULTS_ARRAY | ConvertTo-Json -Depth 100 -Compress | Write-Debug return $RESULTS_ARRAY | ConvertTo-Json -Depth 100 -Compress } function Get-AtlassianPowerKitProfileList { - Write-Debug 'Getting AtlassianPowerKit Profile List...' - if (!$(Get-SecretVault -Name $script:VAULT_NAME -ErrorAction SilentlyContinue)) { + Write-Debug "$($MyInvocation.InvocationName) getting profile list from vault: $VAULT_NAME..." + if (!$(Get-SecretVault -Name $VAULT_NAME -ErrorAction SilentlyContinue)) { + Write-Debug "$($MyInvocation.InvocationName) vault not found, registering..." Register-AtlassianPowerKitVault + Write-Debug "$($MyInvocation.InvocationName) vault registered successfully." + $PROFILE_LIST = @() } else { - Write-Debug 'Vault already registered, getting profiles...' - unlock-vault -VaultName $script:VAULT_NAME - $PROFILE_LIST = (Get-SecretInfo -Vault $script:VAULT_NAME -Name '*').Name - if ($PROFILE_LIST.Count -eq 0) { - Write-Debug 'No profiles found. Please create a new profile.' - return $false - } + #Write-Debug 'Vault already registered, getting profiles...' + unlock-vault -VaultName $VAULT_NAME | Write-Debug + $PROFILE_LIST = (Get-SecretInfo -Vault $VAULT_NAME -Name '*').Name } - $env:AtlassianPowerKit_PROFILE_LIST_STRING = $PROFILE_LIST - Write-Debug "Profiles found: $($env:AtlassianPowerKit_PROFILE_LIST_STRING)" return $PROFILE_LIST } @@ -237,37 +224,6 @@ function Get-LevenshteinDistance { } # Function Update-ContentPlaceholderAll, takes a file path and a hashtable of placeholders and values, returning the content with the placeholders replaced (not updating the file) -function Get-PopulatedTemplate { - param ( - [Parameter(Mandatory = $true)] - [string]$TemplateFilePath, - [Parameter(Mandatory = $false)] - [string[]]$InputJSON, - [Parameter(Mandatory = $false)] - [string[]]$InputConfStorageString - ) - # If neither JSON or ConfStorageString is provided error - if (-not $InputJSON -and -not $InputConfStorageString) { - Write-Debug 'No InputJSON or InputConfStorageString provided to , one is required. Exiting...' - Write-Error 'Get-PopulatedTemplate failed. Exiting...' - return $false - } - $TemplateContent = Get-Content -Path $TemplateFilePath -Raw | ConvertFrom-Json -Depth 20 - foreach ($item in $TemplateContent.placeholderMap) { - foreach ($key in $item.Keys) { - $value = $item[$key] - $valueDataType = $value.GetType().Name - - # Handle different types of values - if ($value -is [System.Collections.IEnumerable] -and -not ($value -is [string])) { - $value = $value -join ', ' - } - - Write-Output "Key: $key, ValueDataType: $valueDataType, Value: $value" - } - } - return $Content -} function Get-VaultKey { if (-not (Test-Path $VAULT_KEY_PATH)) { @@ -278,51 +234,87 @@ function Get-VaultKey { return $VAULT_KEY } -function New-AtlassianPowerKitProfile { - # Ask user to enter the profile name - $ProfileName = Read-Host 'Enter a profile name:' - $ProfileName = $ProfileName.ToLower().Trim() - if (!$ProfileName -or $ProfileName -eq '' -or $ProfileName.Length -gt 100) { - Write-Error 'Profile name cannot be empty, or more than 100 characters, Please try again.' - # Load the selected profile or create a new profile - Write-Debug "Profile name entered: $ProfileName" - Throw 'Profile name cannot be empty, taken or mor than 100 characters, Please try again.' - } - else { - try { - $REGISTERED_PROFILE = Register-AtlassianPowerKitProfile($ProfileName) +function Unlock-Vault { + param ( + [Parameter(Mandatory = $true)] + [string]$VaultName + ) + try { + if ((Get-SecretVault | Where-Object IsDefault).Name -ne $VAULT_NAME) { + Write-Debug "$VAULT_NAME is not the default vault. Setting as default..." + Set-SecretVaultDefault -Name $VAULT_NAME | Write-Debug } - catch { - Write-Debug "Error: $($_.Exception.Message)" - throw "Register-AtlassianPowerKitProfile $ProfileName failed. Exiting." + # Attempt to get a non-existent secret. If the vault is locked, this will throw an error. + $VAULT_KEY = Get-VaultKey + if (! $VAULT_KEY -or $VAULT_KEY -eq $false) { + Write-Debug 'Unlock-Vault failed. Exiting.' + throw 'Unlock-Vault failed. Exiting.' } + Unlock-SecretStore -Password $VAULT_KEY | Write-Debug } - return $REGISTERED_PROFILE + catch { + # If an error is thrown, the vault is locked. + Write-Debug "Unlock-Vault failed: $_ ..." + throw 'Unlock-Vault failed Exiting' + } + # If no error is thrown, the vault is unlocked. + Write-Debug 'Vault is unlocked.' + Return $true } +# Function to update the vault with the new profile data +function Update-AtlassianPowerKitVault { + param ( + [Parameter(Mandatory = $true)] + [string]$ProfileName, + [Parameter(Mandatory = $true)] + [hashtable]$ProfileData + ) + Write-Debug "Writing profile data to vault for $ProfileName..." + Unlock-Vault -VaultName $VAULT_NAME | Write-Debug + try { + Set-Secret -Name $ProfileName -Secret $ProfileData -Vault $VAULT_NAME | Write-Debug + } + catch { + Write-Debug "Update of vault failed for $ProfileName." + throw "Update of vault failed for $ProfileName." + } + Write-Debug "Vault entruy for $ProfileName updated successfully." +} function Register-AtlassianPowerKitVault { + param ( + [Parameter(Mandatory = $false)] + [Int]$ATTEMPT = 0 + ) # Register the secret vault # Cheking if the vault is already registered while (-not (Test-Path $VAULT_KEY_PATH)) { Write-Debug 'No vault key file found. Removing any existing vaults and re-creating...' - Unregister-SecretVault -Name $script:VAULT_NAME -ErrorAction SilentlyContinue + Unregister-SecretVault -Name $VAULT_NAME -ErrorAction SilentlyContinue # Create a random secure key to use as the vault key as protected data $VAULT_KEY = $null while (-not $VAULT_KEY -or $VAULT_KEY.Length -lt 16) { - $VAULT_KEY = [System.Convert]::ToBase64String([System.Security.Cryptography.RNGCryptoServiceProvider]::GetBytes(32)) - # Convert the key to a secure string and export it to a file - $VAULT_KEY = ConvertTo-SecureString -String $VAULT_KEY -AsPlainText -Force - $VAULT_KEY | Export-Clixml -Path $VAULT_KEY_PATH + # Generate a random byte array + $randomBytes = New-Object byte[] 32 + [System.Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($randomBytes) + + # Convert the byte array to a secure string + $secureString = New-Object -TypeName System.Security.SecureString + foreach ($byte in $randomBytes) { + $secureString.AppendChar([char]$byte) + } + $VAULT_KEY = $secureString } + $VAULT_KEY | Export-Clixml -Path $VAULT_KEY_PATH # Write the vault key to a temporary file Write-Debug 'Vault key file created successfully.' } - if (Get-SecretVault -Name $script:VAULT_NAME -ErrorAction SilentlyContinue) { - Write-Debug "Vault $script:VAULT_NAME already exists." + if (Get-SecretVault -Name $VAULT_NAME -ErrorAction SilentlyContinue) { + Write-Debug "Vault $VAULT_NAME already exists." } else { - Write-Debug "Registering vault $script:VAULT_NAME..." + Write-Debug "Registering vault $VAULT_NAME..." $VAULT_KEY = Get-VaultKey $storeConfiguration = @{ Authentication = 'Password' @@ -332,239 +324,196 @@ function Register-AtlassianPowerKitVault { } Set-SecretVaultDefault -ClearDefault Reset-SecretStore @storeConfiguration -Force - Register-SecretVault -Name $script:VAULT_NAME -ModuleName Microsoft.PowerShell.SecretStore -VaultParameters $storeConfiguration -DefaultVault -AllowClobber - Write-Debug "Vault $script:VAULT_NAME registered successfully." - Write-Debug "Checking if vault $script:VAULT_NAME is the default vault..." - if ((Get-SecretVault | Where-Object IsDefault).Name -ne $script:VAULT_NAME) { - Write-Debug "$script:VAULT_NAME is not the default vault. Setting as default..." - Set-SecretVaultDefault -Name $script:VAULT_NAME + Register-SecretVault -Name $VAULT_NAME -ModuleName Microsoft.PowerShell.SecretStore -VaultParameters $storeConfiguration -DefaultVault -AllowClobber + Write-Debug "Vault $VAULT_NAME registered successfully." + Write-Debug "Checking if vault $VAULT_NAME is the default vault..." + if ((Get-SecretVault | Where-Object IsDefault).Name -ne $VAULT_NAME) { + Write-Debug "$VAULT_NAME is not the default vault. Setting as default..." + Set-SecretVaultDefault -Name $VAULT_NAME } - #Set-SecretStoreConfiguration @storeConfiguration - #try { - # Set-SecretStorePassword -NewPassword $VAULT_KEY - #} - # catch { - # Write-Debug "Failed to set SecretStorePassword for $script:VAULT_NAME. Please check the vault key file." - # throw "ERROR: Failed to set SecretStorePassword for $script:VAULT_NAME. Please run again, if error continues please raise an issue ..." - # } - Write-Debug "Vault $script:VAULT_NAME configured successfully." + Write-Debug "Vault $VAULT_NAME configured successfully." } # Unlock the vault if it is locked try { $VAULT_KEY = Get-VaultKey - Unlock-Vault -VaultName $script:VAULT_NAME + Unlock-Vault -VaultName $VAULT_NAME | Write-Debug } catch { - Write-Debug "Failed to unlock vault $script:VAULT_NAME. Please check the vault key file." - Write-Debug "De-registering vault $script:VAULT_NAME... and resetting vault key file." - Unregister-SecretVault -Name $script:VAULT_NAME - Remove-Item -Path $VAULT_KEY_PATH -Force - Write-Debug "Vault $script:VAULT_NAME de-registered and vault key file removed, starting from scratch..." - throw "ERROR: Failed to unlock vault $script:VAULT_NAME. Please run again, if error continues please raise an issue ..." + Write-Debug "Failed to unlock vault $VAULT_NAME. Please check the vault key file." + Write-Debug "De-registering vault $VAULT_NAME... and resetting vault key file." + Unregister-SecretVault -Name $VAULT_NAME | Write-Debug + Remove-Item -Path $VAULT_KEY_PATH -Force | Write-Debug + Write-Debug "Vault $VAULT_NAME de-registered and vault key file removed, starting from scratch..." + Write-Debug "$($MyInvocation.InvocationName) failed retrying, attempt: $ATTEMPT" } + if ($ATTEMPT -gt 5) { + Write-Debug "$($MyInvocation.InvocationName) failed after $ATTEMPT attempts. Exiting..." + throw "$($MyInvocation.InvocationName) failed!" + } + else { + Register-AtlassianPowerKitVault -ATTEMPT ($ATTEMPT + 1) + } + Return $true } -function Register-AtlassianPowerKitProfile { +function Register-AtlassianPowerKitProfileInVault { param( - [Parameter(Mandatory = $true)] - [string] $ProfileName, - [Parameter(Mandatory = $true)] - [string] $AtlassianAPIEndpoint, - [Parameter(Mandatory = $true)] - [PSCredential] $AtlassianAPICredential + [Parameter(Mandatory = $false)] + [string]$ProfileName, + [Parameter(Mandatory = $false)] + [string]$AtlassianAPIEndpoint, + [Parameter(Mandatory = $false)] + [PSCredential]$AtlassianAPICredentialPair ) - if (!$script:REGISTER_VAULT) { - Register-AtlassianPowerKitVault + + try { + Register-AtlassianPowerKitVault | Write-Debug + } + catch { + Write-Debug "$($MyInvocation.InvocationName) failed to register vault. Exiting" + throw "$($MyInvocation.InvocationName) failed to register vault. Exiting" } - # Function to write profile data to the vault - function Set-AtlassianPowerKitProfileData { - param ( - [Parameter(Mandatory = $true)] - [string] $ProfileName, - [Parameter(Mandatory = $true)] - [hashtable] $ProfileData - ) - Write-Debug "Writing profile data to vault for $ProfileName..." - Unlock-Vault -VaultName $script:VAULT_NAME - try { - Set-Secret -Name $ProfileName -Secret $ProfileData -Vault $script:VAULT_NAME - } - catch { - Write-Debug "Update of vault failed for $ProfileName." - throw "Update of vault failed for $ProfileName." + Write-Debug "$($MyInvocation.InvocationName) vault registered successfully." + $VAULT_PROFILES_LIST = Get-AtlassianPowerKitProfileList + # Check if the profile already exists in the secret vault + if ($null -ne $VAULT_PROFILES_LIST -and $VAULT_PROFILES_LIST.Count -gt 0 -and $VAULT_PROFILES_LIST.Contains($ProfileName)) { + Write-Debug "$($MyInvocation.InvocationName) Profile $ProfileName already exists in the vault. You must run AtlssianPowerKit -RemoveVaultProfile $ProfileName or AtlssianPowerKit -ResetVault to remove it first." + throw "$($MyInvocation.InvocationName) Profile $ProfileName already exists in the vault. You must run AtlssianPowerKit -RemoveVaultProfile $ProfileName or AtlssianPowerKit -ResetVault to remove it first." + } + else { + #Write-Debug "Profile $ProfileName does not exist. Creating..." + Write-Debug "Preparing profile data for $ProfileName..." + $CredPair = "$($AtlassianAPICredentialPair.UserName):$($AtlassianAPICredentialPair.GetNetworkCredential().password)" + Write-Debug "CredPair: $CredPair" + $AtlassianAPIAuthToken = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($CredPair)) + $ProfileData = @{ + 'PROFILE_NAME' = $ProfileName + 'AtlassianAPIEndpoint' = $AtlassianAPIEndpoint + 'AtlassianAPIUserName' = $AtlassianAPICredential.UserName + 'AtlassianAPIAuthString' = $AtlassianAPIAuthToken } - Write-Debug "Vault entruy for $ProfileName updated successfully." + Write-Debug "$($MyInvocation.InvocationName) profile data prepared for $ProfileName, updating vault..." + Set-Secret -Name $ProfileName -Secret $ProfileData -Vault $VAULT_NAME | Write-Debug + Write-Debug "$($MyInvocation.InvocationName) profile data updated for $ProfileName." + Clear-AtlassianPowerKitProfile | Out-Null } - # Check if the profile already exists in the secret vault - if ($null -ne $env:AtlassianPowerKit_PROFILE_LIST_STRING -and $env:AtlassianPowerKit_PROFILE_LIST_STRING.Count -gt 0 -and $env:AtlassianPowerKit_PROFILE_LIST_STRING.Contains($ProfileName)) { - Write-Debug "Profile $ProfileName already exists." - # Ask user if they want to overwrite the profile - $overwrite = Read-Host -Prompt "Profile $ProfileName already exists. Do you want to overwrite it? (Y/N)" - if ($overwrite -eq 'Y') { - Write-Debug "Overwriting profile $ProfileName..." + return $PROFILE_NAME +} + +# Function to set the Atlassian Cloud API headers +function Test-VaultProfileLoaded { + # Check if all of the $REQUIRED_ENV_VARS are set + $PROFILE_LOADED = $true + foreach ($envVar in $REQUIRED_ENV_VARS) { + $envVarNameKey = $ENVAR_PREFIX + $envVar + $ENV_STATE = Get-Item -Path "env:$envVarNameKey" -ErrorAction SilentlyContinue + if (!$ENV_STATE) { + Write-Debug "Profile is missing required environment variable: $envVarNameKey" + $PROFILE_LOADED = $false + break } else { - Write-Debug "Profile $ProfileName already exists." - return $false + #Write-Debug "Found required environment variable: $envVarNameKey already set." + $ENV_STATE = $null } } - #Write-Debug "Profile $ProfileName does not exist. Creating..." - Write-Debug "Preparing profile data for $ProfileName..." - $CredPair = "$($AtlassianAPICredential.UserName):$($AtlassianAPICredential.GetNetworkCredential().password)" - Write-Debug "CredPair: $CredPair" - $AtlassianAPIAuthToken = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($CredPair)) - $ProfileData = @{ - 'PROFILE_NAME' = $ProfileName - 'AtlassianAPIEndpoint' = $AtlassianAPIEndpoint - 'AtlassianAPIUserName' = $AtlassianAPICredential.UserName - 'AtlassianAPIAuthString' = $AtlassianAPIAuthToken - } - Write-Debug "Creating profile $ProfileName in $script:VAULT_NAME..." - Set-Secret -Name $ProfileName -Secret $ProfileData -Vault $script:VAULT_NAME - Write-Debug "Profile $ProfileName created successfully in $script:VAULT_NAME." - Write-Debug 'Clearing existing profiles selection...' - Clear-AtlassianPowerKitProfile - $LOADED_PROFILE = Set-AtlassianPowerKitProfile -SelectedProfileName $ProfileName - return $LOADED_PROFILE + return $PROFILE_LOADED } -# Function to set the Atlassian Cloud API headers function Set-AtlassianAPIHeaders { # check if there is a profile loaded - if (!$env:AtlassianPowerKit_PROFILE_NAME) { - Write-Debug 'No profile loaded. Please load a profile first.' - return $false + if (!$(Test-VaultProfileLoaded)) { + Write-Debug "$($MyInvocation.InvocationName) failed. Profile not loaded. Exiting..." + throw "$($MyInvocation.InvocationName) failed. Profile not loaded. Exiting..." } else { - Write-Debug "Profile $ProfileName loaded. Setting API headers and AtlassianAPIEndpoint..." $HEADERS = @{ Authorization = "Basic $($env:AtlassianPowerKit_AtlassianAPIAuthString)" Accept = 'application/json' } # Add atlassian headers to the profile data - $env:AtlassianPowerKit_AtlassianAPIHeaders = $HEADERS | ConvertTo-Json - Write-Debug "Atlassian headers set for $($env:AtlassianPowerKit_PROFILE_NAME)." - Write-Debug "Headers are: $($env:AtlassianPowerKit_AtlassianAPIHeaders)" - Test-AtlassianPowerKitProfile - Write-Debug "Atlassian headers set and tested successfully for $($env:AtlassianPowerKit_PROFILE_NAME)." + $API_HEADERS = $HEADERS | ConvertTo-Json -Compress } + Return $API_HEADERS } -function Set-AtlassianPowerKitProfile { +function Set-AtlassianPowerKitProfileFromVault { param ( [Parameter(Mandatory = $true)] [string]$SelectedProfileName ) - if ($SelectedProfileName -eq $env:AtlassianPowerKit_PROFILE_NAME) { - Write-Debug "$SelectedProfileName is already loaded." - return $env:AtlassianPowerKit_PROFILE_NAME + $SKIP_LOAD = Test-VaultProfileLoaded + if ($SKIP_LOAD) { + Get-Item -Path "env:$ENVAR_PREFIX*" | Write-Debug + Write-Debug "$($MyInvocation.InvocationName) profile already loaded. Skipping vault load..." } else { - Write-Debug "$SelectedProfileName not loaded, loading..." + Write-Debug "Profile $SelectedProfileName not loaded. Loading from vault..." # Load all profiles from the secret vault - if (!$(Get-SecretVault -Name $script:VAULT_NAME -ErrorAction SilentlyContinue)) { + if (!$(Get-SecretVault -Name $VAULT_NAME -ErrorAction SilentlyContinue)) { Register-AtlassianPowerKitVault } # Check if the profile exists $PROFILE_LIST = Get-AtlassianPowerKitProfileList if (!$PROFILE_LIST.Contains($SelectedProfileName)) { - Write-Debug "Profile $SelectedProfileName does not exists in the vault - we have: $PROFILE_LIST, creating... $SelectedProfileName" - New-AtlassianPowerKitProfile -ProfileName $SelectedProfileName - return $false + Write-Debug "Profiles found in vault: $($PROFILE_LIST | ConvertTo-Json -Depth 100) ..." + Write-Debug 'Profiles found in vault: ' + $PROFILE_LIST | ConvertTo-Json -Depth 100 | Write-Debug + Write-Error "$($MyInvocation.InvocationName) failed. Profile $SelectedProfileName not found in the vault. Exiting..." + Throw "$($MyInvocation.InvocationName) failed. Profile $SelectedProfileName not found in the vault. Exiting..." } else { Write-Debug "Profile $SelectedProfileName exists in the vault, loading..." try { # if vault is locked, unlock it - Unlock-Vault -VaultName $script:VAULT_NAME - $PROFILE_DATA = (Get-Secret -Name $SelectedProfileName -Vault $script:VAULT_NAME -AsPlainText) - #Create environment variables for each item in the profile data - $PROFILE_DATA.GetEnumerator() | ForEach-Object { - Write-Debug "Setting environment variable: $($_.Key) = $($_.Value)" - # Create environment variable concatenated with AtlassianPowerKit_ prefix - $SetEnvar = '$env:AtlassianPowerKit_' + $_.Key + " = `"$($_.Value)`"" - Invoke-Expression -Command $SetEnvar | Out-Null - Write-Debug "Environment variable set: $SetEnvar" + if (unlock-vault -VaultName $VAULT_NAME) { + $PROFILE_DATA = (Get-Secret -Name $SelectedProfileName -Vault $VAULT_NAME -AsPlainText) + #Create environment variables for each item in the profile data + Write-Debug "Successfully retrieved profile data for $SelectedProfileName : " + $PROFILE_DATA | ConvertTo-Json -Depth 100 | Write-Debug + $PROFILE_DATA.GetEnumerator() | ForEach-Object { + #Write-Debug "Setting environment variable: $($_.Key) = $($_.Value)" + # Create environment variable concatenated with AtlassianPowerKit_ + $VAR_NAME = $ENVAR_PREFIX + $_.Key + $VAR_VALUE = $_.Value + Write-Debug "Setting environment variable: env:$VAR_NAME = $VAR_VALUE" + $SetEnvar = '$env:' + $VAR_NAME + ' = "' + $VAR_VALUE + '"' + Invoke-Expression -Command $SetEnvar + Get-Item -Path "env:$VAR_NAME" | Write-Debug + } } - } + } catch { Write-Debug "Failed to load profile $SelectedProfileName. Please check the vault key file." throw "Failed to load profile $SelectedProfileName. Please check the vault key file." } - Set-AtlassianAPIHeaders - #Set-OpsgenieAPIHeaders - $env:AtlassianPowerKit_CloudID = $(Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/_edge/tenant_info").cloudId - Write-Debug "Profile $SelectedProfileName loaded successfully." } - $LOADED_PROFILE = Get-CurrentAtlassianPowerKitProfile - return $LOADED_PROFILE } - return $false + $ENVVAR_ARRAY = Get-Item -Path "env:$ENVAR_PREFIX*" + Return $ENVVAR_ARRAY } - # Function to test if AtlassianPowerKit profile authenticates successfully function Test-AtlassianPowerKitProfile { Write-Debug 'Testing Atlassian Cloud PowerKit Profile...' - #Write-Debug "API Headers: $($script:AtlassianAPIHeaders | Format-List * | Out-String)' - Write-Debug "API Endpoint: $($env:AtlassianPowerKit_AtlassianAPIEndpoint) ..." - Write-Debug "API Headers: $($env:AtlassianPowerKit_AtlassianAPIHeaders) ..." + ##Write-Debug "API Headers: $($script:AtlassianAPIHeaders | Format-List * | Out-String)' + #Write-Debug "API Endpoint: $($env:AtlassianPowerKit_AtlassianAPIEndpoint) ..." + #Write-Debug "API Headers: $($env:AtlassianPowerKit_AtlassianAPIHeaders) ..." $HEADERS = ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders $TEST_ENDPOINT = 'https://' + $env:AtlassianPowerKit_AtlassianAPIEndpoint + '/rest/api/2/myself' try { - Write-Debug "Running: Invoke-RestMethod -Uri https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/2/myself -Headers $($env:AtlassianPowerKit_AtlassianAPIHeaders | ConvertFrom-Json -AsHashtable) -Method Get" - Invoke-RestMethod -Method Get -Uri $TEST_ENDPOINT -Headers $HEADERS | Write-Debug + #Write-Debug "Running: Invoke-RestMethod -Uri https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/2/myself -Headers $($env:AtlassianPowerKit_AtlassianAPIHeaders | ConvertFrom-Json -AsHashtable) -Method Get" + $REST_RESPONSE = Invoke-RestMethod -Method Get -Uri $TEST_ENDPOINT -Headers $HEADERS -StatusCodeVariable REST_STATUS #Write-Debug "Results: $($REST_RESULTS | ConvertTo-Json -Depth 10) ..." - Write-Debug 'Donennnne' - } - catch { - Write-Debug "Error: $_ ..." - throw 'Atlassian Cloud API Auth test failed.' - } - Write-Debug "Atlassian Cloud Auth test returned: $($REST_RESULTS.displayName) --- OK!" -} - -function Unlock-Vault { - param ( - [Parameter(Mandatory = $true)] - [string]$VaultName - ) - try { - if ((Get-SecretVault | Where-Object IsDefault).Name -ne $script:VAULT_NAME) { - Write-Debug "$script:VAULT_NAME is not the default vault. Setting as default..." - Set-SecretVaultDefault -Name $script:VAULT_NAME - } - # Attempt to get a non-existent secret. If the vault is locked, this will throw an error. - $VAULT_KEY = Get-VaultKey - Unlock-SecretStore -Password $VAULT_KEY - } - catch { - # If an error is thrown, the vault is locked. - Write-Debug "Unlock-Vault failed: $_ ..." - throw 'Unlock-Vault failed Exiting' + Write-Debug "$($MyInvocation.InvocationName) returned status code: $($REST_STATUS)" } - # If no error is thrown, the vault is unlocked. - Write-Debug 'Vault is unlocked.' -} - -# Function to update the vault with the new profile data -function Update-AtlassianPowerKitVault { - param ( - [Parameter(Mandatory = $true)] - [string]$ProfileName, - [Parameter(Mandatory = $true)] - [hashtable]$ProfileData - ) - Write-Debug "Writing profile data to vault for $ProfileName..." - Unlock-Vault -VaultName $script:VAULT_NAME - try { - Set-Secret -Name $ProfileName -Secret $ProfileData -Vault $script:VAULT_NAME - } catch { - Write-Debug "Update of vault failed for $ProfileName." - throw "Update of vault failed for $ProfileName." + Write-Debug "$($MyInvocation.InvocationName) failed: with $_" + Write-Debug 'Rest response: ' + $REST_RESPONSE | ConvertTo-Json -Depth 10 | Write-Debug + throw "$($MyInvocation.InvocationName) failed. Exiting..." } - Write-Debug "Vault entruy for $ProfileName updated successfully." + Return $true } function Remove-AtlasianPowerKitProfile { @@ -574,8 +523,55 @@ function Remove-AtlasianPowerKitProfile { ) Write-Debug "Removing profile $ProfileName..." if ($ProfileName -eq $env:AtlassianPowerKit_PROFILE_NAME) { - Clear-AtlassianPowerKitProfile + Clear-AtlassianPowerKitProfile | Write-Debug } - Remove-Secret -Name $ProfileName -Vault $script:VAULT_NAME + Remove-Secret -Name $ProfileName -Vault $VAULT_NAME | Write-Debug Write-Debug "Profile $ProfileName removed." +} +function Set-AtlassianPowerKitProfile { + param ( + [Parameter(Mandatory = $false)] + [string]$ProfileName = $false, + [Parameter(Mandatory = $false)] + [switch]$NoVault = $false + ) + if ($NoVault) { + Write-Debug 'NoVault switch enabled. Skipping vault actions and checking required environment variables are present' + foreach ($envVar in $REQUIRED_ENV_VARS) { + $envVarNameKey = $ENVAR_PREFIX + $envVar + $requiredEnvVar = $(Get-Item -Path "env:$envVarNameKey" -ErrorAction SilentlyContinue) + if (!$requiredEnvVar) { + Write-Error "Required environment variable $envVarNameKey not found. Exiting..." + throw "Required environment variable $envVarNameKey not found. Exiting..." + } + else { + Write-Debug "Required environment variable found: $($requiredEnvVar.Name) = $(($requiredEnvVar.Value).substring(0, [System.Math]::Min(20, $requiredEnvVar.Value.Length)))..." + # if the environment variable is AtlassianAPIEndpoint, use the prefix as the profile name (setting as environment variable: AtlassianPowerKit_PROFILE_NAME) + if ($requiredEnvVar.Name -eq 'AtlassianPowerKit_AtlassianAPIEndpoint') { + $ProfileName = $requiredEnvVar.Value -Split '.' | Select-Object -First 1 + Write-Debug "Setting environment variable: PROFILE_NAME = $ProfileName" + # Create environment variable concatenated with AtlassianPowerKit_ prefix + $SetEnvar = '$env:' + $ENVAR_PREFIX + 'PROFILE_NAME = `"' + $ProfileName + '"`' + Invoke-Expression -Command $SetEnvar | Out-Null + } + } + } + } + elseif ($ProfileName -ne $false) { + $VAULT_LOADED_ARRAY = Set-AtlassianPowerKitProfileFromVault -SelectedProfileName $ProfileName + $VAULT_LOADED_ARRAY | ForEach-Object { Write-Output "$($_.Name) = $($_.Value)" | Out-Null } + } + else { + Write-Error 'ProfileName is required. Exiting...' + throw 'ProfileName is required. Exiting...' + } + #Write-Debug 'Vault loaded array:' + #$VAULT_LOADED_ARRAY | ForEach-Object { Write-Debug "$($_.Name) = $($_.Value)" } + $env:AtlassianPowerKit_AtlassianAPIHeaders = Set-AtlassianAPIHeaders + #Write-Debug "API Headers set: $($env:AtlassianPowerKit_AtlassianAPIHeaders) calling https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/_edge/tenant_info to get CloudID..." + $env:AtlassianPowerKit_CloudID = $(Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/_edge/tenant_info").cloudId + #Write-Debug "CloudID set: $($env:AtlassianPowerKit_CloudID) ..." + $PROFILE_ENVARS = Get-Item -Path "env:$ENVAR_PREFIX*" + Write-Debug "ENVARS loaded, count: $($PROFILE_ENVARS.Count) ..." + Return $PROFILE_ENVARS } \ No newline at end of file diff --git a/AtlassianPowerKit.psm1 b/AtlassianPowerKit.psm1 index 22d95b1..75c3d5d 100644 --- a/AtlassianPowerKit.psm1 +++ b/AtlassianPowerKit.psm1 @@ -1,13 +1,10 @@ -$ErrorActionPreference = 'Stop'; $DebugPreference = 'Continue' <# .SYNOPSIS - Atlassian Cloud PowerKit module for interacting with Atlassian Cloud REST API. + Atlassian Cloud PowerKit module for interacting with Atlassian Cloud REST APIs and other handy functions relating to the OSM project. + .DESCRIPTION - Atlassian Cloud PowerKit module for interacting with Atlassian Cloud REST API. - - Dependencies: AtlassianPowerKit-Shared - - Functions: - - Use-AtlassianPowerKit: Interactive function to run any function in the module. - - Debug output is enabled by default. To disable, set $DisableDebug = $true before running functions. + See the [wiki](https://github.com/OrganisationServiceManagement/AtlassianPowerKit/wiki) for more information on how to use this module. + .EXAMPLE Use-AtlassianPowerKit This example lists all functions in the AtlassianPowerKit module. @@ -18,9 +15,10 @@ $ErrorActionPreference = 'Stop'; $DebugPreference = 'Continue' Get-DefinedPowerKitVariables This example lists all variables defined in the AtlassianPowerKit module. .LINK - GitHub: + GitHub: https://github.com/OrganisationServiceManagement/AtlassianPowerKit #> +$ErrorActionPreference = 'Stop'; $DebugPreference = 'Continue' function Get-RequisitePowerKitModules { $AtlassianPowerKitRequiredModules = @('PowerShellGet', 'Microsoft.PowerShell.SecretManagement', 'Microsoft.PowerShell.SecretStore') $AtlassianPowerKitRequiredModules | ForEach-Object { @@ -44,11 +42,11 @@ function Get-RequisitePowerKitModules { function Import-NestedModules { param ( [Parameter(Mandatory = $true)] - [string[]] $NESTED_MODULES + [string[]]$NESTED_MODULES ) $NESTED_MODULES | ForEach-Object { $MODULE_NAME = $_ - Write-Debug "Importing nested module: $MODULE_NAME" + #Write-Debug "Importing nested module: $MODULE_NAME" #Find-Module psd1 file in the subdirectory and import it $PSD1_FILE = Get-ChildItem -Path ".\$MODULE_NAME" -Filter "$MODULE_NAME.psd1" -Recurse -ErrorAction SilentlyContinue if (-not $PSD1_FILE) { @@ -60,7 +58,7 @@ function Import-NestedModules { throw "Multiple module files found for $MODULE_NAME. Exiting." } Import-Module $PSD1_FILE.FullName -Force - Write-Debug "Importing nested module: $PSD1_FILE, -- $($PSD1_FILE.BaseName)" + Write-Debug "Imported nested module: $PSD1_FILE, -- $($PSD1_FILE.BaseName)" #Write-Debug "Importing nested module: .\$($_.BaseName)\$($_.Name)" # Validate the module is imported if (-not (Get-Module -Name $MODULE_NAME)) { @@ -70,47 +68,51 @@ function Import-NestedModules { } return $NESTED_MODULES } - -function Test-OSMHomeDir { - # If the OSM_HOME environment variable is not set, set it to the current directory. - if (-not $env:OSM_HOME) { - Write-Debug "Setting OSM_HOME to $new_home" - $new_home = $(Get-Item $pwd).FullName | Split-Path -Parent - $env:OSM_HOME = $new_home - } - # Check the OSM_HOME environment variable directory exists - if (-not (Test-Path $env:OSM_HOME)) { - Write-Warning "OSM_HOME directory not found: $env:OSM_HOME" - Write-Warning "Changing OSM_HOME to $new_home" - $env:OSM_HOME = $new_home - } - $ValidatedOSMHome = (Get-Item $env:OSM_HOME).FullName - if (-not $env:OSM_INSTALL) { - # if linux, set the default OSM_INSTALL path to /opt/osm - if ($IsLinux) { - $env:OSM_INSTALL = '/opt/osm' +# Create OSM dirs +$OSM_DIRS = @('OSM_HOME', 'OSM_INSTALL') +function Confirm-OSMDirs { + $VALIDATED_DIRS = $OSM_DIRS | ForEach-Object { + # Check if $env: variable exists + $ENVAR_NAME = 'env:' + $_ + if (-not (Get-Item -Path $ENVAR_NAME -ErrorAction SilentlyContinue)) { + if ($IsLinux) { + $DIR_PATH = '/opt/osm' + } + else { + $DIR_PATH = $(Get-ItemProperty -Path .).FullName + } + $SetEnvar = '$' + $ENVAR_NAME + ' = "' + $DIR_PATH + '"' + Invoke-Expression -Command $SetEnvar | Write-Debug + #Write-Debug "Envar set: $SetEnvar" + } + # Get the path from the $env: variable and create the directory if it does not exist + $EXISING_ENVAR = Get-Item -Path $ENVAR_NAME + $EXPECTED_DIR = $EXISING_ENVAR.Value + if (-not (Test-Path $EXPECTED_DIR)) { + New-Item -ItemType Directory -Path $EXPECTED_DIR -Force | Write-Debug + #Write-Debug "Directory created: $EXPECTED_DIR" } else { - $env:OSM_INSTALL = $(Get-ItemProperty -Path ..\).FullName + #Write-Debug "Good news, $ENVAR_NAME already set and directory already exists: $EXPECTED_DIR" } - + $ENVAR_NAME } - return $ValidatedOSMHome + return $VALIDATED_DIRS } function Invoke-AtlassianPowerKitFunction { param ( [Parameter(Mandatory = $true)] - [string] $FunctionName, + [string]$FunctionName, [Parameter(Mandatory = $false)] - [hashtable] $FunctionParameters + [hashtable]$FunctionParameters ) $TEMP_DIR = "$env:OSM_HOME\$env:AtlassianPowerKit_PROFILE_NAME\.temp" if (-not (Test-Path $TEMP_DIR)) { - New-Item -ItemType Directory -Path $TEMP_DIR -Force | Out-Null + New-Item -ItemType Directory -Path $TEMP_DIR -Force | Write-Debug } $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() - $stopwatch.Start() | Out-Null + $stopwatch.Start() | Write-Debug try { if ($FunctionParameters) { @@ -146,13 +148,57 @@ function Invoke-AtlassianPowerKitFunction { function Show-AdminFunctions { param ( [Parameter(Mandatory = $false)] - [string[]] $AdminModules = @('AtlassianPowerKit-Shared', 'AtlassianPowerKit-UsersAndGroups') + [string[]]$AdminModules = @('AtlassianPowerKit-Shared', 'AtlassianPowerKit-UsersAndGroups') ) # Clear current screen Clear-Host Show-AtlassianPowerKitFunctions -NESTED_MODULES $AdminModules } +function Update-OSMPKFunctionsMarkDownDoc { + param ( + [Parameter(Mandatory = $true)] + [string[]]$NESTED_MODULES + ) + # Creates or updates a markdown document with all functions in the module, their descriptions, and parameters + $MARKDOWN_FILE = "$env:OSM_HOME\AtlassianPowerKit\AtlassianPowerKit-Functions.md" + if (-not (Test-Path $MARKDOWN_FILE)) { + New-Item -ItemType File -Path $MARKDOWN_FILE -Force | Write-Debug + } + else { + Clear-Content -Path $MARKDOWN_FILE | Write-Debug + } + $NESTED_MODULES | ForEach-Object { + $MODULE_NAME = $_ + Write-Output "# Module: $MODULE_NAME" | Out-File -FilePath $MARKDOWN_FILE -Append + Write-Debug "Updating markdown document for module: $MODULE_NAME" + $MODULE_FUNCTIONS = (Get-Module -Name $MODULE_NAME -All).ExportedFunctions.Keys + $MODULE_FUNCTIONS | ForEach-Object { + $FUNCTION_NAME = $_ + $FUNCTION_PARAMS = $FUNCTION_NAME.Parameters + Write-Output "## Function: $FUNCTION_NAME" | Out-File -FilePath $MARKDOWN_FILE -Append + Write-Output '### Params' | Out-File -FilePath $MARKDOWN_FILE -Append + foreach ($PARAM in $FUNCTION_PARAMS) { + $PARAM_DETAILS = $FUNCTION_PARAMS[$PARAM] + $PARAM_NAME = $PARAM + $PARAM_TYPE = $PARAM_DETAILS.ParameterType.Name + $PARAM_MANDATORY = $PARAM_DETAILS.IsMandatory + $PARAM_DEFAULT = $PARAM_DETAILS.DefaultValue + $PARAM_DETAILS = " - **$PARAM_NAME** ($PARAM_TYPE)" + if ($PARAM_MANDATORY) { + $PARAM_DETAILS += ' - Mandatory' + } + if ($PARAM_DEFAULT) { + $PARAM_DETAILS += " - Default: $PARAM_DEFAULT" + } + Write-Output $PARAM_DETAILS | Out-File -FilePath $MARKDOWN_FILE -Append + } + $FUNCTION_DETAILS | Out-File -FilePath $MARKDOWN_FILE -Append + } + } + return $MARKDOWN_FILE +} + # Function display console interface to run any function in the module function Show-AtlassianPowerKitFunctions { param ( @@ -227,88 +273,104 @@ function Show-AtlassianPowerKitFunctions { Write-Error "Function $selectedFunction does not exist in the function references." } } - # if selected function is Return, exit the function - if (!$SelectedFunctionName -or ($SelectedFunctionName -eq 0 -or $SelectedFunctionName -eq 'Return')) { - Write-Debug 'No function selected. Exiting' - return $null - } if ($SelectedFunctionName -eq 'A') { Show-AdminFunctions } + # if selected function is Return, exit the function + elseif (!$SelectedFunctionName -or ($SelectedFunctionName -eq 0 -or $SelectedFunctionName -eq 'Return')) { + #Write-Debug 'No function selected. Exiting' + return $null + } # Run the selected function timing the execution Write-Host "`n" Write-Host "Invoking AtlassingPowerKit Function: $SelectedFunctionName" -ForegroundColor Green return $SelectedFunctionName } +function New-AtlassianPowerKitProfile { + param ( + [Parameter(Mandatory = $false)] + [string]$PROFILE_NAME = $null + ) + if (!$PROFILE_NAME) { + $PROFILE_NAME = Read-Host -Prompt 'Enter a name for the new profile' + } + $PROFILE_NAME = $PROFILE_NAME.Trim().ToLower() + $API_ENDPOINT = Read-Host -Prompt 'Enter the Atlassian API endpoint (e.g. https://your-domain.atlassian.net)' + $API_CREDPAIR = Get-Credential -Message 'Enter your Atlassian API credentials (email and API token)' + $REGISTERED_PROFILE = Register-AtlassianPowerKitProfileInVault -ProfileName $PROFILE_NAME -AtlassianAPIEndpoint $API_ENDPOINT -AtlassianAPICredentialPair $API_CREDPAIR + $ENVAR_ARRAY = Import-AtlassianPowerKitProfile -selectedProfile $REGISTERED_PROFILE + return $ENVAR_ARRAY +} # Function to list availble profiles with number references for interactive selection or 'N' to create a new profile -function Show-AtlassianPowerKitProfileList { - #Get-AtlassianPowerKitProfileList - $PROFILE_LIST = Get-AtlassianPowerKitProfileList - $profileIndex = 0 - if (!$PROFILE_LIST) { - Write-Host 'Please create a new profile.' - $REGISTERED_PROFILE = New-AtlassianPowerKitProfile - return $REGISTERED_PROFILE - #Write-Debug "Profile List: $(Get-AtlassianPowerKitProfileList)" - #Show-AtlassianPowerKitProfileList - } - else { - #Write-Debug "Profile list: $env:AtlassianPowerKit_PROFILE_LIST_STRING" - Write-Debug "Profile list string $PROFILE_LIST" - $PROFILE_LIST.split() | ForEach-Object { - Write-Host "[$profileIndex] $_" - $profileIndex++ - } - } - Write-Host '[N] Create a new profile' - Write-Host '[D] Delete a profile' - Write-Host '[A] Admin (danger) functions' - Write-Host '[Q / Return] Quit' - Write-Host '++++++++++++++++++++++++++++++++++++++++++++++++++++++++++' -ForegroundColor DarkGray - try { - # read input from the user and just break with no error if the input is not a number, 'N', 'R' or 'Q' - $selectedProfile = Read-Host 'Select a profile number or action' - } - catch { - return $null - } - if ((!$selectedProfile) -or ($selectedProfile -eq 'Q')) { - return $null - } - elseif ($selectedProfile -eq 'N') { - New-AtlassianPowerKitProfile - } - elseif ($selectedProfile -eq 'A') { - Show-AdminFunctions +function Import-AtlassianPowerKitProfile { + param ( + [Parameter(Mandatory = $false)] + [switch]$NoVault = $false, + [Parameter(Mandatory = $false)] + [string]$selectedProfile = $false + ) + if ($NoVault) { + #Write-Debug "$($MyInvocation.InvocationName) -NoVault flag set, attempting to load profile from environment variables" + $ENVAR_ARRAY = Set-AtlassianPowerKitProfile -NoVault } - elseif ($selectedProfile -eq 'D') { - Remove-AtlasianPowerKitProfile + elseif ($selectedProfile -ne $false) { + #Write-Debug "$($MyInvocation.InvocationName) -ProfileName profided, attempting to load profile: $selectedProfile from the vault" + $ENVAR_ARRAY = Set-AtlassianPowerKitProfile -ProfileName $selectedProfile + if (!$ENVAR_ARRAY -or $ENVAR_ARRAY.Count -lt 3) { + Write-Host "Could not load profile: $selectedProfile from the vault. Requesting values to add it to vault." + $ENVAR_ARRAY = New-AtlassianPowerKitProfile -PROFILE_NAME $selectedProfile + } } else { - $selectedProfile = [int]$selectedProfile - $SELECTED_PROFILE_NAME = $PROFILE_LIST[$selectedProfile] - # Write-Debug "Selected profile index: $selectedProfile" - # Write-Debug "Selected profile name: $($PROFILE_LIST[$selectedProfile])" - #$LOADED_PROFILENAME = Set-AtlassianPowerKitProfile -SelectedProfileName $($PROFILE_LIST[$selectedProfile]) - return $SELECTED_PROFILE_NAME + #Write-Debug "$($MyInvocation.InvocationName) -NoVault flag not set and no profile selected, checking for existing profiles in the vault" + $VAULT_PROFILES = Get-AtlassianPowerKitProfileList + if ($VAULT_PROFILES) { + if ($VAULT_PROFILES.Count -eq 1) { + $selectedProfile = $VAULT_PROFILES[0] + Write-Debug "Only one profile found in the vault, selecting $selectedProfile" + $ENVAR_ARRAY = Set-AtlassianPowerKitProfile -ProfileName $selectedProfile + } + else { + Write-Output 'Multiple profiles found in the vault but no profile provided, please use the -APK_Profile parameter to specify the desired profile' + foreach ($APK_PROFILENAME in $VAULT_PROFILES) { + Write-Output " AtlassianPowerkit -APK_PROFILENAME $APK_PROFILENAME" + } + Throw 'Ambiguous profile state' + } + } + else { + Write-Output 'No profiles found in the vault, please create a new profile.' + $ENVAR_ARRAY = New-AtlassianPowerKitProfile + } } + return $ENVAR_ARRAY } function AtlassianPowerKit { param ( [Parameter(Mandatory = $false)] - [string] $Profile, + [string]$APK_Profile, + [Parameter(Mandatory = $false)] + [switch]$ArchiveProfileDirs, + [Parameter(Mandatory = $false)] + [switch]$ResetVault, [Parameter(Mandatory = $false)] - [switch] $ArchiveProfileDirs, + [string]$FunctionName, [Parameter(Mandatory = $false)] - [switch] $ResetVault, + [hashtable]$FunctionParameters, [Parameter(Mandatory = $false)] - [string] $FunctionName, + [switch]$ClearProfile, [Parameter(Mandatory = $false)] - [hashtable] $FunctionParameters, + [switch]$ListProfiles, [Parameter(Mandatory = $false)] - [switch] $ClearProfile + [switch]$NoVault = $false, + [Parameter(Mandatory = $false)] + [string]$RemoveVaultProfile = $false, + [Parameter(Mandatory = $false)] + [switch]$NewVaultProfile = $false, + [Parameter(Mandatory = $false)] + [switch]$DocFunctions = $false ) if (!$env:AtlassianPowerKit_RequisiteModules) { $env:AtlassianPowerKit_RequisiteModules = Get-RequisitePowerKitModules @@ -318,64 +380,99 @@ function AtlassianPowerKit { try { #Push-Location -Path $PSScriptRoot -ErrorAction Continue Write-Debug "Starting AtlassianPowerKit, running from $((Get-Item -Path $PSScriptRoot).FullName)" - Write-Debug "OSM_HOME: $(Test-OSMHomeDir)" + #Write-Debug 'OSM Directories: ' + foreach ($OSM_DIR in Confirm-OSMDirs) { + Get-Item -Path "$OSM_DIR" | Write-Debug + } # If current directory is not the script root, push the script root to the stack if ($ResetVault) { - Clear-AtlassianPowerKitVault | Out-Null + Write-Debug '-ResetVault flagged Clearing the AtlassianPowerKit vault, ignoring any other parameters' + Clear-AtlassianPowerKitVault | Write-Debug + return $true + } + elseif ($ListProfiles) { + Write-Debug '$ListProfiles flaged, Listing AtlassianPowerKit profiles, ignoring any other parameters' + $PROFILE_LIST = Get-AtlassianPowerKitProfileList + return $PROFILE_LIST + } + elseif ($ArchiveProfileDirs) { + Write-Debug '-ArchiveProfileDirs flagged, Clearing the AtlassianPowerKit profile directories, ignoring any other parameters' + Clear-AtlassianPowerKitProfileDirs | Write-Debug return $true } - if ($ArchiveProfileDirs) { - Clear-AtlassianPowerKitProfileDirs | Out-Null + elseif ($ClearProfile) { + Write-Debug '-ClearProfile flagged, Clearing the AtlassianPowerKit profile, ignoring any other parameters' + Clear-AtlassianPowerKitProfile | Write-Debug return $true } - if ($ClearProfile) { - Clear-AtlassianPowerKitProfile | Out-Null + elseif ($RemoveVaultProfile -ne $false) { + Write-Debug '-RemoveVaultProfile flagged, Removing the AtlassianPowerKit profile from the vault, ignoring any other parameters' + Remove-AtlassianPowerKitProfile -ProfileName $RemoveVaultProfile | Write-Debug return $true } - # If no profile name is provided, list the available profiles - $ProfileName = $null - if ($Profile) { - $ProfileName = $Profile.Trim().ToLower() + elseif ($DocFunctions) { + Write-Debug '-DocFunctions flagged, Creating a markdown document of all AtlassianPowerKit functions, ignoring any other parameters' + Update-OSMPKFunctionsMarkDownDoc -NESTED_MODULES $NESTED_MODULES + return $true + } + elseif ($NewVaultProfile) { + Write-Debug '-NewVaultProfile flagged, Creating a new AtlassianPowerKit profile in the vault, ignoring any other parameters' + New-AtlassianPowerKitProfile | Write-Debug + return $true } - if (!$ProfileName) { - $ProfileName = Show-AtlassianPowerKitProfileList + elseif ($NoVault) { + Write-Debug '-NoVault flagged, attempting to load profile from environment variables' + $PROFILE_ARRAY = Import-AtlassianPowerKitProfile -NoVault + } + elseif ($APK_Profile) { + Write-Debug "Profile provided: $APK_Profile" + $ProfileName = $APK_Profile.Trim().ToLower() + $PROFILE_ARRAY = Import-AtlassianPowerKitProfile -selectedProfile $ProfileName + } + else { + Write-Debug 'No profile provided, checking if vault has only 1 profile' + $PROFILE_ARRAY = Import-AtlassianPowerKitProfile + } + Write-Debug "Profile set to: $env:AtlassianPowerKit_PROFILE_NAME" + $PROFILE_ARRAY | ForEach-Object { + Write-Output " $_" | Out-Null } - $CURRENT_PROFILE = Set-AtlassianPowerKitProfile -SelectedProfileName $ProfileName - Write-Debug "Profile set to: $CURRENT_PROFILE" if (!$FunctionName) { $FunctionName = Show-AtlassianPowerKitFunctions -NESTED_MODULES $NESTED_MODULES } - # If function parameters are provided, splat them to the function - Write-Debug "AtlassianPowerKit Main - Running function: $FunctionName, with profile: $CURRENT_PROFILE" + #Write-Debug "Function selected: $FunctionName" if ($FunctionParameters) { - Write-Debug ' Parameters provided to the function via hashtable:' + Write-Debug '-FunctionParameters provided !' + if ($FunctionParameters.GetType() -ne [hashtable]) { + Write-Debug '-FunctionParameters must be a hashtable, e.g.:' + Write-Debug ' @{ key1 = "value1"; key2 = "value2" }' + throw 'Function parameters must be a hashtable. Exiting.' + } # Iterate through the hashtable and display the key value pairs as "-key value" $FunctionParameters.GetEnumerator() | ForEach-Object { Write-Debug " -$($_.Key) $_.Value" } - $RETURN_JSON = $(Invoke-AtlassianPowerKitFunction -FunctionName $FunctionName -FunctionParameters $FunctionParameters) + $RET_VAL = Invoke-AtlassianPowerKitFunction -FunctionName $FunctionName -FunctionParameters $FunctionParameters Write-Debug "AtlassianPowerKit Main: Received JSON of size: $($RETURN_JSON.Length) characters" } elseif ($FunctionName) { - Write-Debug "AtlassianPowerKit Main: No parameters provided to the function, attempting to run the function without parameters: $FunctionName" - $RETURN_JSON = $(Invoke-AtlassianPowerKitFunction -FunctionName $FunctionName) - Write-Debug "AtlassianPowerKit Main: Received JSON of size: $($RETURN_JSON.Length) characters" + Write-Debug "$($MyInvocation.InvocationName) attempting to run function: $FunctionName without parameters - most functions will handle this by requesting user input" + $RET_VAL = Invoke-AtlassianPowerKitFunction -FunctionName $FunctionName + } } catch { # Write call stack and sub-function error messages to the debug output - Write-Debug '++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ AtlassianPowerKit Main: ' + Write-Debug "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ $((Get-Item -Path $PSScriptRoot).FullName) $($MyInvocation.InvocationName) FAILED: " # Write full call stack to the debug output and error message to the console Get-PSCallStack - Write-Debug '++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ AtlassianPowerKit Main: ' + Write-Debug "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ $((Get-Item -Path $PSScriptRoot).FullName) $($MyInvocation.InvocationName)" Write-Error $_.Exception.Message } - # finally { - # #Clear-AtlassianPowerKitProfile - # #Pop-Location - # #Remove-Item 'env:AtlassianPowerKit_*' -ErrorAction Continue - # #Write-Debug 'Gracefully exited AtlassianPowerKit' - # } - $RETURN_JSON | ConvertFrom-Json -Depth 100 | ConvertTo-Json -Depth 100 -Compress | Write-Debug - $RETURN_JSON + if (!$RET_VAL) { + Write-Output 'Nothing to return, have a nice day.' + } + else { + return $RET_VAL + } } \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 24881a9..cf8b65c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,21 +2,21 @@ FROM mcr.microsoft.com/powershell:latest # Set the working directory -WORKDIR /opt/osm/AtlassianPowerKit -ADD . . +WORKDIR /mnt/osm # Install Git and required PowerShell modules -RUN apt-get update && \ - apt-get install -y git && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* && \ - mkdir -p /mnt/osm && \ - chmod 755 -R ./* && \ +#RUN apt-get update && \ +# apt-get install -y git && \ +# apt-get clean && \ +# rm -rf /var/lib/apt/lists/* && \ +# mkdir -p /mnt/osm && \ +# chmod 755 -R ./* +RUN pwd && \ + chmod 755 -R /mnt +RUN pwsh -Command "Set-PSRepository -Name PSGallery -InstallationPolicy Trusted" && \ pwsh -Command "Install-Module -Name PowerShellGet -Force" && \ pwsh -Command "Install-Module -Name Microsoft.PowerShell.SecretManagement,Microsoft.PowerShell.SecretStore -Force" -# Set the OSM_HOME environment variable -ENV OSM_HOME=/mnt/osm +# Set the entry point +ENTRYPOINT ["pwsh"] -# Use CMD instead of ENTRYPOINT for overridable command -CMD ["pwsh", "./Run.ps1"] diff --git a/README.md b/README.md index 1dd0ba2..50f31de 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,22 @@ # AtlassianPowerKit - Various functions in PowerShell to interact with Atlassian Cloud APIs +- Supports multiple profiles for different Atlassian Cloud accounts +- Docker image available for cross-platform support (Windows, macOS, Linux): + - [markz0r/atlassian-powerkit](https://hub.docker.com/r/markz0r/atlassian-powerkit) -## Quick Start +## Usage ```powershell git clone https://github.com/OrganisationServiceManagement/AtlassianPowerKit.git cd .\AtlassianPowerKit; Import-Module "AtlassianPowerKit.psd1" -AtlassianPowerKit ``` -## Usage - ```powershell # Text UI AtlassianPowerKit # Direct invocation (after profile configured) -AtlassianPowerKitFunction -FunctionName "Get-JiraIssue" -FunctionParameters @{"Key"="TEST-1"} -Profile "zoak" +AtlassianPowerKit -FunctionName "Get-JiraIssue" -FunctionParameters @{"Key"="TEST-1"} -Profile "zoak" ``` ```docker @@ -29,11 +29,16 @@ mkdir ./osm_home docker run -it --rm -v ${PWD}/osm_home:/mnt/osm -v "$HOME/.local/share/powershell/secretmanagement/ " ``` -```powershell +## Documentation -## Prerequisites +- _[AtlassianPowerKit Wiki](../../wiki)_ -- Windows PowerShell 7.0 or later +## Dependencies + +- PowerShell 7.0 or later (Core is supported on Windows, macOS, and Linux) +- Alternatively, you can use the Docker image to run the module: + - https://hub.docker.com/r/markz0r/atlassian-powerkit + - `docker run --rm -v ${PWD}\osm_home:/mnt/osm -v "$Env:LOCALAPPDATA\Microsoft\PowerShell\secretmanagement\:/root/.secretmanagement/" -it markz0r/atlassian-powerkit:latest` ## Contributing @@ -46,4 +51,7 @@ See [LICENSE](LICENSE.md) file. ## Disclaimer This module is provided as-is without any warranty or support. Use it at your own risk. + +``` + ``` diff --git a/Run.ps1 b/Run.ps1 index e1cee57..a007acd 100644 --- a/Run.ps1 +++ b/Run.ps1 @@ -1,15 +1,14 @@ # Run.ps1 -# Set the environment variable if needed -$env:OSM_HOME = '/mnt/osm' -$env:OSM_INSTALL = '/opt/osm' - # Import necessary modules Import-Module -Name Microsoft.PowerShell.SecretManagement, Microsoft.PowerShell.SecretStore -Force Set-Location "$env:OSM_INSTALL/AtlassianPowerKit" Import-Module "$env:OSM_INSTALL/AtlassianPowerKit/AtlassianPowerKit.psd1" -Force $env:SECRETSTORE_PATH = $env:OSM_HOME +# Load Environment Variables from the host + + # Check if arguments were passed to the script if ($args.Count -gt 0) { # Run AtlassianPowerKit with the provided arguments diff --git a/osm_home/tmp b/osm_home/tmp deleted file mode 100644 index 1c2f433..0000000 --- a/osm_home/tmp +++ /dev/null @@ -1 +0,0 @@ -tmp \ No newline at end of file From 4904a4891607d49a153fcced1b9e51c27d854611 Mon Sep 17 00:00:00 2001 From: Mark Culhane Date: Sun, 26 Jan 2025 05:52:11 +1100 Subject: [PATCH 07/11] Added htmltoadf as a submodule --- .gitmodules | 3 +++ submodules/htmltoadf | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 submodules/htmltoadf diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..764cd8f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "submodules/htmltoadf"] + path = submodules/htmltoadf + url = https://github.com/wouterken/htmltoadf.git diff --git a/submodules/htmltoadf b/submodules/htmltoadf new file mode 160000 index 0000000..247f248 --- /dev/null +++ b/submodules/htmltoadf @@ -0,0 +1 @@ +Subproject commit 247f2481ec71cee3930d963245a190c1527c8dfc From cebae29bbbc84e4df0f9d36503cdb7307cd021c4 Mon Sep 17 00:00:00 2001 From: Mark Culhane Date: Thu, 13 Mar 2025 14:00:19 +1100 Subject: [PATCH 08/11] sync --- .../AtlassianPowerKit-Admin.psd1 | 3 +- .../AtlassianPowerKit-Admin.psm1 | 98 +-- .../AtlassianPowerKit-Confluence.psd1 | 2 +- .../AtlassianPowerKit-Confluence.psm1 | 130 +++- .../AtlassianPowerKit-GRCosm.psm1 | 123 ++-- AtlassianPowerKit-GRCosm/generate-pdf.js | 80 +++ .../AtlassianPowerKit-Jira.psd1 | 6 + .../AtlassianPowerKit-Jira.psm1 | 586 +++++++++++------- AtlassianPowerKit.psm1 | 78 +-- 9 files changed, 751 insertions(+), 355 deletions(-) create mode 100644 AtlassianPowerKit-GRCosm/generate-pdf.js diff --git a/AtlassianPowerKit-Admin/AtlassianPowerKit-Admin.psd1 b/AtlassianPowerKit-Admin/AtlassianPowerKit-Admin.psd1 index e30b59e..50e56ea 100644 --- a/AtlassianPowerKit-Admin/AtlassianPowerKit-Admin.psd1 +++ b/AtlassianPowerKit-Admin/AtlassianPowerKit-Admin.psd1 @@ -79,7 +79,8 @@ 'Get-JiraProjectIssueTypes', 'Get-JiraCloudIssueTypeSchema' 'Get-JiraCloudIssueTypeMetadata', - 'Get-JiraOSMFilterList' + 'Get-JiraOSMFilterList', + 'Export-ProjectProformaFormTemplates' ) # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. diff --git a/AtlassianPowerKit-Admin/AtlassianPowerKit-Admin.psm1 b/AtlassianPowerKit-Admin/AtlassianPowerKit-Admin.psm1 index efc9973..739c67b 100644 --- a/AtlassianPowerKit-Admin/AtlassianPowerKit-Admin.psm1 +++ b/AtlassianPowerKit-Admin/AtlassianPowerKit-Admin.psm1 @@ -52,8 +52,7 @@ function New-JiraIssueType { Write-Warn "Multiple issue types found with the name: $JiraIssueTypeName. Returning the first issue type." $ExistingJiraIssueType = $ExistingJiraIssueType[0] } - } - else { + } else { # Create a JSON object for the new issue type using the $JiraIssueType fields: name, description, hierarchyLevel, avatarId (removing the other fields) $NewJiraIssueType = @{ name = $JiraIssueTypeName @@ -76,6 +75,15 @@ function New-JiraIssueType { Return $ExistingJiraIssueType | ConvertTo-Json -Depth 100 -Compress } +function Set-OrgAdminUser { + param ( + [Parameter(Mandatory = $true)] + [string]$ORG_ADMIN_USER + ) + $ATLASSIAN_ADMIN_API = "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/user?username=$ORG_ADMIN_USER" + +} + # Function to load issue types from a JSON file function Import-JiraIssueTypes { @@ -101,13 +109,13 @@ function Test-ExistingConfigJSON { $CONFIG_FILE = Get-ChildItem -Path $CONFIG_FILE_PATHPATTERN | Where-Object { $_.LastWriteTime -gt (Get-Date).AddHours(-12) } if ($CONFIG_FILE) { $CONFIG_FILE - } - else { + } else { $null } } # Returns JSON path that can be loaded with Get-Content $(Import-JSONConfigExport) | ConvertFrom-Json -AsHashtable -NoEnumerate function Import-JSONConfigExport { + $FULL_CONFIG_OUTPUT_JSONFILE = $null # Advise user of the age of the existing JSON export and ask if they want to use it defaulting to 'Yes' while (! $FULL_CONFIG_OUTPUT_JSONFILE) { @@ -119,8 +127,7 @@ function Import-JSONConfigExport { Write-Debug "Fresh, existing JSON export: $($LATEST_EXISTING_JSON_EXPORT.FullName)" $FULL_CONFIG_OUTPUT_JSONFILE = $LATEST_EXISTING_JSON_EXPORT.FullName } - } - else { + } else { Write-Debug "$($MyInvocation.MyCommand.Name): Creating new FULL DEPLOYMENT CONFIG json file using: Get-OSMDeploymentConfigsJIRA -PROFILE_NAME $PROFILE_NAME" $RAW_CONFIG_JSON = Get-OSMDeploymentConfigsJIRA -PROFILE_NAME $PROFILE_NAME | ConvertFrom-Json -Depth 100 $FULL_CONFIG_OUTPUT_JSONFILE = "$OUTPUT_PATH\FULL-$PROFILE_NAME-$(Get-Date -Format 'yyyyMMdd-HHmm').json" @@ -159,8 +166,7 @@ function Get-OSMConfigAsMarkdown { $PROJECT_ISSUE_TYPES | ForEach-Object { if ($_ -ne $null -and $null -ne $_.Name -and $null -ne $_.self) { Write-Output "- [$($_.Name)]($($_.self)) - [Show All Instances](https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/jira/servicedesk/projects/$PROJECT_KEY/issues/?jql=project%20%3D%20$PROJECT_KEY%20AND%20issuetype%20%3D%20%22$($_.name.replace(' ','%20'))%22)" - } - else { + } else { Write-Output '- Invalid or missing issue type' } } @@ -169,8 +175,7 @@ function Get-OSMConfigAsMarkdown { $PROJECT_REQUEST_TYPES | ForEach-Object { if ($_ -ne $null -and $null -ne $_.Name -and $null -ne $_.self) { Write-Output "- [$($_.Name)]($($_.self)) - [Show All Instances](https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint).atlassian.net/jira/servicedesk/projects/$PROJECT_KEY/issues/?jql=project%20%3D%20$PROJECT_KEY%20AND%20issuetype%20%3D%20%22$($_.name.replace(' ','%20'))%22)" - } - else { + } else { Write-Output '- Invalid or missing request type' } } @@ -193,8 +198,39 @@ function Get-OSMConfigAsMarkdown { Return $JSON_RETURN | ConvertTo-Json -Depth 100 -Compress } +function Export-ProjectProformaFormTemplates { + param ( + [Parameter(Mandatory = $true)] + [string]$PROJECT_KEY, + [Parameter(Mandatory = $false)] + [string]$OUTPUT_PATH = "$($env:OSM_HOME)\$($env:AtlassianPowerKit_PROFILE_NAME)\JIRA" + ) + Import-Module "$env:OSM_INSTALL\AtlassianPowerKit\AtlassianPowerKit-Jira\AtlassianPowerKit-Jira.psd1" -Force | Out-Null + $TIME_STAMP = Get-Date -Format 'yyyyMMdd-HHmm' + $PROFILE_NAME = $env:AtlassianPowerKit_PROFILE_NAME + $FORMLIST_FILE = "$OUTPUT_PATH\$PROFILE_NAME-$PROJECT_KEY-ProformaFormList-$TIME_STAMP.json" + # Get List of Forms for the project + $FORMS_ALL = Get-FormsForJiraProject -PROJECT_KEY $PROJECT_KEY + $FORM_LIST = $FORMS_ALL | ConvertFrom-Json | Where-Object { $_.name -notcontains 'z_Archive' } + $FORM_LIST | ConvertTo-Json -Depth 100 | Out-File -FilePath $FORMLIST_FILE -Force | Out-Null + $FORM_LIST | ForEach-Object { + $FORM_ID = $_.id + $FORM_NAME = $_.name + Write-Debug "======================= Processing Form: $FORM_NAME" + $FORM_ENDPOINT = "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/proforma/1.0/form/$FORM_ID/schema" + $REST_RESULTS = Invoke-RestMethod -Uri $FORM_ENDPOINT -Method Get -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) + $REST_RESULTS | ConvertTo-Json -Depth 100 | Write-Debug + exit + $FORM_TEMPLATE | ConvertTo-Json -Depth 100 | Out-File -FilePath "$OUTPUT_PATH\$PROFILE_NAME-$PROJECT_KEY-$FORM_NAME-FormTemplate-$(Get-Date -Format 'yyyyMMdd-HHmm').json" -Force + } + + $PROFORMA_API_ENDPOINT = "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/proforma/1.0" + Return $OUTPUT_FILE + +} + # Function Get Deployment -Function Get-OSMDeploymentConfigsJIRA { +function Get-OSMDeploymentConfigsJIRA { param ( [Parameter(Mandatory = $false)] [string]$PROFILE_NAME = $env:AtlassianPowerKit_PROFILE_NAME, @@ -207,8 +243,7 @@ Function Get-OSMDeploymentConfigsJIRA { $PROFILE_PROJECT_LIST = Test-ExistingConfigJSON -CONFIG_FILE_PATHPATTERN "$($env:OSM_HOME)\$PROFILE_NAME\$PROFILE_NAME-ProjectList-*.json" if ($PROFILE_PROJECT_LIST) { $PROJECT_LIST = Get-Content $PROFILE_PROJECT_LIST.FullName | ConvertFrom-Json -AsHashtable -NoEnumerate - } - else { + } else { $PROJECT_LIST = AtlassianPowerKit -Profile $PROFILE_NAME -FunctionName 'Get-JiraProjectList' | ConvertFrom-Json -AsHashtable -NoEnumerate } #$PROJECT_LIST | ForEach-Object { Write-Host "Project: $($_.name) - $($_.key)" } @@ -222,16 +257,14 @@ Function Get-OSMDeploymentConfigsJIRA { $PROFILE_PROJECT_PROPERTIES = Test-ExistingConfigJSON -CONFIG_FILE_PATHPATTERN "$($env:OSM_HOME)\$PROFILE_NAME\$PROFILE_NAME-$PROJECT_KEY-ProjectProperties-*.json" if ($PROFILE_PROJECT_PROPERTIES) { $PROFILE_PROJECT_PROPERTIES = Get-Content $PROFILE_PROJECT_PROPERTIES.FullName | ConvertFrom-Json -AsHashtable -NoEnumerate - } - else { + } else { $PROFILE_PROJECT_PROPERTIES = AtlassianPowerKit -Profile $PROFILE_NAME -FunctionName 'Get-JiraProjectProperties' -FunctionParameters @{ PROJECT_KEY = $PROJECT_KEY } | ConvertFrom-Json -AsHashtable -NoEnumerate } # PROJECT_ISSUE_TYPE_SCHEMA $PROJECT_ISSUE_TYPE_SCHEMA = Test-ExistingConfigJSON -CONFIG_FILE_PATHPATTERN "$($env:OSM_HOME)\$PROFILE_NAME\$PROFILE_NAME-$PROJECT_KEY-IssueTypeSchema-*.json" if ($PROJECT_ISSUE_TYPE_SCHEMA) { $PROJECT_ISSUE_TYPE_SCHEMA = Get-Content $PROJECT_ISSUE_TYPE_SCHEMA.FullName | ConvertFrom-Json -AsHashtable -NoEnumerate - } - else { + } else { $PROJECT_ISSUE_TYPE_SCHEMA = AtlassianPowerKit -Profile $PROFILE_NAME -FunctionName 'Get-JiraCloudIssueTypeSchema' -FunctionParameters @{ PROJECT_KEY = $PROJECT_KEY } | ConvertFrom-Json -AsHashtable -NoEnumerate } # @@ -239,15 +272,13 @@ Function Get-OSMDeploymentConfigsJIRA { $PROJECT_ISSUE_TYPES = Test-ExistingConfigJSON -CONFIG_FILE_PATHPATTERN "$($env:OSM_HOME)\$PROFILE_NAME\$PROFILE_NAME-$PROJECT_KEY-ProjectIssueTypes-*.json" if ($PROJECT_ISSUE_TYPES) { $PROJECT_ISSUE_TYPES = Get-Content $PROJECT_ISSUE_TYPES.FullName | ConvertFrom-Json -AsHashtable -NoEnumerate - } - else { + } else { $PROJECT_ISSUE_TYPES = AtlassianPowerKit -Profile $PROFILE_NAME -FunctionName 'Get-JiraProjectIssueTypes' -FunctionParameters @{ PROJECT_KEY_OR_ID = $PROJECT_KEY } | ConvertFrom-Json -AsHashtable -NoEnumerate } $PROJECT_REQUEST_TYPES = Test-ExistingConfigJSON -CONFIG_FILE_PATHPATTERN "$($env:OSM_HOME)\$PROFILE_NAME\$PROFILE_NAME-$PROJECT_KEY-RequestTypeSchema-*.json" if ($PROJECT_REQUEST_TYPES) { $PROJECT_REQUEST_TYPES = Get-Content $PROJECT_REQUEST_TYPES.FullName | ConvertFrom-Json -AsHashtable -NoEnumerate - } - else { + } else { $PROJECT_REQUEST_TYPES = AtlassianPowerKit -Profile $PROFILE_NAME -FunctionName 'Get-JiraServiceDeskRequestTypes' -FunctionParameters @{ PROJECT_KEY = $PROJECT_KEY } | ConvertFrom-Json -AsHashtable -NoEnumerate } @@ -255,8 +286,7 @@ Function Get-OSMDeploymentConfigsJIRA { $PROJECT_FORMS = Test-ExistingConfigJSON -CONFIG_FILE_PATHPATTERN "$($env:OSM_HOME)\$PROFILE_NAME\$PROFILE_NAME-$PROJECT_KEY-Forms-*.json" if ($PROJECT_FORMS) { $PROJECT_FORMS = Get-Content $PROJECT_FORMS.FullName | ConvertFrom-Json -AsHashtable -NoEnumerate - } - else { + } else { $PROJECT_FORMS = AtlassianPowerKit -Profile $PROFILE_NAME -FunctionName 'Get-FormsForJiraProject' -FunctionParameters @{ PROJECT_KEY = $PROJECT_KEY } | ConvertFrom-Json -AsHashtable -NoEnumerate } @@ -265,8 +295,7 @@ Function Get-OSMDeploymentConfigsJIRA { $PROJECT_WORKFLOWS_SCHEMES = Test-ExistingConfigJSON -CONFIG_FILE_PATHPATTERN "$($env:OSM_HOME)\$PROFILE_NAME\$PROFILE_NAME-$PROJECT_KEY-ProjectWorkflowSchemes-*.json" if ($PROJECT_WORKFLOWS_SCHEMES) { $PROJECT_WORKFLOWS_SCHEMES = Get-Content $PROJECT_WORKFLOWS_SCHEMES.FullName | ConvertFrom-Json -AsHashtable -NoEnumerate - } - else { + } else { $PROJECT_WORKFLOWS_SCHEMES = AtlassianPowerKit -Profile $PROFILE_NAME -FunctionName 'Get-JiraProjectWorkflowSchemes' -FunctionParameters @{ PROJECT_KEY = $PROJECT_KEY } | ConvertFrom-Json -AsHashtable -NoEnumerate } @@ -295,8 +324,7 @@ function Get-JiraProjectIssueTypes { # If the AtlassianPowerKit-J if ($PROJECT_KEY_OR_ID -match '^\d+$') { $PROJECT_ID = $PROJECT_KEY_OR_ID - } - else { + } else { # Get the most recent auda-ProjectList-*.json in the $OUTPUT_PATH or run Get-JiraProjectList and check again for the file $PROJECT_LIST_FILE = Get-ChildItem -Path $OUTPUT_PATH -Filter "$env:AtlassianPowerKit_PROFILE_NAME-ProjectList-*.json" | Sort-Object -Property LastWriteTime -Descending | Select-Object -First 1 While (-not $PROJECT_LIST_FILE) { @@ -348,15 +376,13 @@ function Get-JiraCloudIssueTypeSchema { Write-Debug "Project ID passed: $PROJECT_KEY_OR_ID" $PROJECT_ID = $PROJECT_KEY_OR_ID $PROJECT_KEY = (Get-JiraProjectByKey -PROJECT_KEY $PROJECT_KEY_OR_ID | ConvertFrom-Json -AsHashtable -NoEnumerate).key - } - else { + } else { Write-Debug "Project Key passed: $PROJECT_KEY_OR_ID ... getting project ID..." $PROJECT_OBJECT = Get-JiraProjectByKey -PROJECT_KEY $PROJECT_KEY_OR_ID | ConvertFrom-Json -AsHashtable -NoEnumerate #ConvertTo-Json $PROJECT_OBJECT -Depth 50 | Write-Debug if ($PROJECT_OBJECT.id) { $PROJECT_ID = $PROJECT_OBJECT.id - } - else { + } else { Write-Error "Project ID not found for project key: $PROJECT_KEY_OR_ID" } } @@ -375,17 +401,15 @@ function Get-FilterJQL { # While response code is 429, wait and try again try { $REST_RESPONSE = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/filter/$($FILTER_ID)" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get -ContentType 'application/json' - } - catch { + } catch { # Catch 429 errors and wait for the retry-after time if ($_.Exception.Response.StatusCode -eq 429) { Write-Warn "429 error, waiting for $RETRY_AFTER seconds..." Start-Sleep -Seconds $RETRY_AFTER $REST_RESPONSE = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/filter/$($FILTER_ID)" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get -ContentType 'application/json' - } - else { - Write-Debug "$($MyInvocation.MyCommand.Name): Error getting filter JQL: $($_.Exception.Message)" - Write-Error "Error getting filter JQL: $($_.Exception.Message)" + } else { + Write-Debug "$($MyInvocation.MyCommand.Name): Error getting filter JQL: $($_.Exception.Message)' + Write-Error 'Error getting filter JQL: $($_.Exception.Message)" } } Return $REST_RESPONSE.jql diff --git a/AtlassianPowerKit-Confluence/AtlassianPowerKit-Confluence.psd1 b/AtlassianPowerKit-Confluence/AtlassianPowerKit-Confluence.psd1 index 0eac7fd..74784be 100644 --- a/AtlassianPowerKit-Confluence/AtlassianPowerKit-Confluence.psd1 +++ b/AtlassianPowerKit-Confluence/AtlassianPowerKit-Confluence.psd1 @@ -80,7 +80,7 @@ 'Get-ConfluencePageByID', 'Get-ConfluencePageByTitle', 'Remove-AttachmentsFromConfPage', - 'Set-ConfluencePageContent', + 'Set-AttachmentForConfluencePage', 'Set-ConfluenceYearMonthStructure', 'Set-ConfluenceSpacePropertyByID' ) diff --git a/AtlassianPowerKit-Confluence/AtlassianPowerKit-Confluence.psm1 b/AtlassianPowerKit-Confluence/AtlassianPowerKit-Confluence.psm1 index 5a83c35..4d00045 100644 --- a/AtlassianPowerKit-Confluence/AtlassianPowerKit-Confluence.psm1 +++ b/AtlassianPowerKit-Confluence/AtlassianPowerKit-Confluence.psm1 @@ -1,4 +1,31 @@ $ErrorActionPreference = 'Stop'; $DebugPreference = 'Continue' + +function Export-ConfluencePageToMarkDown { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [string]$CONFLUENCE_SPACE_KEY, + [Parameter(Mandatory = $true)] + [int64]$CONFLUENCE_PAGE_ID + ) + Write-Debug 'Exporting Confluence page to markdown...not done yet' + ## Check if node module @atlaskit/adf-utils is installed + #$NODE_MODULE = 'atlaskit/adf-utils' + #$NODE_MODULE_PATH = "$($env:OSM_HOME)\node_modules\$NODE_MODULE" + #if (-not (Test-Path $NODE_MODULE_PATH)) { + # #Check if node is installed + # $NODE_PATH = Get-Command -Name 'node' -ErrorAction SilentlyContinue + # if (-not $NODE_PATH) { + # Write-Error 'Node.js is not installed. Please install Node.js and run `npm install @atlaskit/adf-utils` to install the required node module.' + # } + # else { + # Push-Location $env:OSM_HOME\AtlassianPowerKit\AtlassianPowerKit-Confluence + # npm install $NODE_MODULE + # Pop-Location + # } + #} + +} function Export-ConfluencePage { param ( [Parameter(Mandatory = $true)] @@ -6,30 +33,50 @@ function Export-ConfluencePage { [Parameter(Mandatory = $true)] [int64]$CONFLUENCE_PAGE_ID, [Parameter(Mandatory = $false)] - [string]$CONFLUENCE_PAGE_FORMAT = 'storage' + [string]$CONFLUENCE_PAGE_FORMAT = 'atlas_doc_format' ) + Write-Debug "Recommended format is 'atlas_doc_format', see: https://developer.atlassian.com/cloud/jira/platform/apis/document/structure/" $CONFLUENCE_PAGE_ENDPOINT = "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/wiki/api/v2/pages/$($CONFLUENCE_PAGE_ID)?body-format=$($CONFLUENCE_PAGE_FORMAT)" Write-Debug "Exporting page format: $CONFLUENCE_PAGE_FORMAT for page ID: $CONFLUENCE_PAGE_ID ... URL: $CONFLUENCE_PAGE_ENDPOINT ..." try { $REST_RESULTS = Invoke-RestMethod -Uri $CONFLUENCE_PAGE_ENDPOINT -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get - Write-Debug $REST_RESULTS.getType() - Write-Debug (ConvertTo-Json $REST_RESULTS -Depth 10) + # Print properties of the REST_RESULTS object + #$REST_RESULTS | Get-Member -MemberType Properties -Force | ForEach-Object { + # Write-Debug "Property: $($_.Name), Value: $($REST_RESULTS.$($_.Name))" + #} } catch { Write-Error "Error exporting page format: $CONFLUENCE_PAGE_FORMAT for page ID: $CONFLUENCE_PAGE_ID" } $CONFLUENCE_PAGE_DIR = "$($env:OSM_HOME)\$($env:AtlassianPowerKit_PROFILE_NAME)\CONFLUENCE" - $CONFLUENCE_PAGE_CONTENT = $REST_RESULTS.body.$CONFLUENCE_PAGE_FORMAT + if ($CONFLUENCE_PAGE_FORMAT -eq 'atlas_doc_format') { + $CONFLUENCE_PAGE_CONTENT = $REST_RESULTS.body.atlas_doc_format.value + $FILE_EXTENSION = 'json' + } + elseif ($CONFLUENCE_PAGE_FORMAT -eq 'storage') { + $CONFLUENCE_PAGE_CONTENT = $REST_RESULTS.body.storage.value + $FILE_EXTENSION = 'xml' + } + else { + $CONFLUENCE_PAGE_CONTENT = $REST_RESULTS | ConvertTo-Json -Depth 100 + $FILE_EXTENSION = 'json' + } $CONFLUENCE_PAGE_TITLE = $REST_RESULTS.title $CONFLUENCE_PAGE_TITLE_FILENAME = $CONFLUENCE_PAGE_TITLE -replace ' ', '_' -replace ':', '-' -replace '[^a-zA-Z0-9_-]', '' $CURRENT_DATE_TIME = Get-Date -Format 'yyyyMMdd-HHmmss' - $OUTFILE = "$CONFLUENCE_PAGE_DIR\$($CONFLUENCE_PAGE_TITLE_FILENAME)_$CURRENT_DATE_TIME.$($EXPORT_EXTENSION_MAP[$CONFLUENCE_PAGE_FORMAT])" + $OUTFILE = "$CONFLUENCE_PAGE_DIR\$($CONFLUENCE_PAGE_TITLE_FILENAME)-$CURRENT_DATE_TIME.$FILE_EXTENSION" if (-not (Test-Path $CONFLUENCE_PAGE_DIR)) { New-Item -ItemType Directory -Path $CONFLUENCE_PAGE_DIR -Force | Out-Null } Write-Debug 'Confluence Page Content:' - $CONFLUENCE_PAGE_CONTENT.Value | Set-Content -Path $OUTFILE -Encoding UTF8 -Force - return $OUTFILE + $CONFLUENCE_PAGE_CONTENT | Set-Content -Path $OUTFILE -Encoding UTF8 -Force + $RETURN_OBJ = @{ + CONFLUENCE_PAGE_TITLE = $CONFLUENCE_PAGE_TITLE + CONFLUENCE_PAGE_ID = $CONFLUENCE_PAGE_ID + CONFLUENCE_PAGE_FORMAT = $CONFLUENCE_PAGE_FORMAT + FILE_NAME = $OUTFILE + } + return $RETURN_OBJ | ConvertTo-Json } function Export-ConfluencePageAllChildren { @@ -407,6 +454,69 @@ function Remove-AttachmentsFromConfPage { } } +function Set-AttachmentForConfluencePage { + param ( + [Parameter(Mandatory = $true)] + [int64]$CONFLUENCE_PAGE_ID, + [Parameter(Mandatory = $true)] + [string]$ATTACHMENT_FILE_PATH + ) + + # Validate the file path + if (-not (Test-Path $ATTACHMENT_FILE_PATH)) { + Write-Error "Attachment file does not exist: $ATTACHMENT_FILE_PATH" + return + } + + # API endpoint + $CONFLUENCE_PAGE_V1_ATTACHMENTS_ENDPOINT = "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/wiki/rest/api/content/$CONFLUENCE_PAGE_ID/child/attachment" + + # Request headers + $REQUEST_HEADERS = @{ + 'Authorization' = "Basic $env:AtlassianPowerKit_AtlassianAPIAuthString" + 'X-Atlassian-Token' = 'no-check' + } + + # File preparation + $FileName = [System.IO.Path]::GetFileName($ATTACHMENT_FILE_PATH) + $Boundary = [System.Guid]::NewGuid().ToString() + $FileContent = [System.IO.File]::ReadAllBytes($ATTACHMENT_FILE_PATH) + + # Construct multipart form-data + $Body = @( + "--$Boundary" + "Content-Disposition: form-data; name=`"file`"; filename=`"$FileName`"" + 'Content-Type: application/pdf' + '' + ([System.Text.Encoding]::UTF8.GetString($FileContent)) + "--$Boundary--" + ) -join "`r`n" + + try { + # POST the attachment + $REST_RESULTS = Invoke-RestMethod -Uri $CONFLUENCE_PAGE_V1_ATTACHMENTS_ENDPOINT ` + -Headers $REQUEST_HEADERS ` + -Method Post ` + -ContentType "multipart/form-data; boundary=$Boundary" ` + -Body ([System.Text.Encoding]::UTF8.GetBytes($Body)) + + # Output results + Write-Debug 'Attachment uploaded successfully.' + Write-Debug (ConvertTo-Json $REST_RESULTS -Depth 10) + return $REST_RESULTS | ConvertTo-Json + } + catch { + # Handle exceptions + Write-Error "Failed to upload attachment: $($_.Exception.Message)" + if ($_.Exception.Response) { + Write-Error "Response Status Code: $($_.Exception.Response.StatusCode)" + Write-Error "Response Status Description: $($_.Exception.Response.StatusDescription)" + } + } +} + + + # Function to set confluence space properties by space ID function Set-ConfluenceSpacePropertyByID { param ( @@ -449,10 +559,10 @@ function Set-ConfluenceYearMonthStructure { throw "Page does not exist: $CONFLUENCE_PAGE_ID" } $CONFLUENCE_PAGE_TITLE = $CONFLUENCE_PAGE.title - if ($CONFLUENCE_PAGE_TITLE -notmatch '^\d{4} (.*)') { + if ($CONFLUENCE_PAGE_TITLE -notmatch '^\d { 4 } (.*)') { throw "Confluence page title does not match 'YYYY (.*)' format. This function is intended to be used on pages with titles in the format 'YYYY (.*)'" } - $MATCH = $CONFLUENCE_PAGE_TITLE -match '(\d{4}) - (.*)' + $MATCH = $CONFLUENCE_PAGE_TITLE -match '(\d { 4 }) - (.*)' $CONFLUENCE_PAGE_YEAR = $MATCH[1] $CONFLUENCE_STRUCTURE_NAME = $MATCH[2].Trim() @@ -482,7 +592,7 @@ function Set-ConfluenceYearMonthStructure { $CHILD_PAGES = Get-ConfluenceChildPages -CONFLUENCE_SPACE_KEY $CONFLUENCE_SPACE_KEY -PARENT_ID $CONFLUENCE_PAGE_ID $CHILD_PAGES.results | ForEach-Object { $CHILD_PAGE_TITLE = $_.title - $CHILD_PAGE_TITLE_MATCH = $CHILD_PAGE_TITLE -match '(\d{4})(\d{2})(\d+.*)' + $CHILD_PAGE_TITLE_MATCH = $CHILD_PAGE_TITLE -match '(\d { 4 })(\d { 2 })(\d+.*)' if ($CHILD_PAGE_TITLE_MATCH) { $CHILD_PAGE_YEAR = $Matches[1] $CHILD_PAGE_MONTH = $Matches[2] diff --git a/AtlassianPowerKit-GRCosm/AtlassianPowerKit-GRCosm.psm1 b/AtlassianPowerKit-GRCosm/AtlassianPowerKit-GRCosm.psm1 index 9585512..456904a 100644 --- a/AtlassianPowerKit-GRCosm/AtlassianPowerKit-GRCosm.psm1 +++ b/AtlassianPowerKit-GRCosm/AtlassianPowerKit-GRCosm.psm1 @@ -70,55 +70,89 @@ function Update-GRCosmConfRegister { [Parameter(Mandatory = $true)] [string]$CONFLUENCE_SPACE_KEY, [Parameter(Mandatory = $true)] - [string]$CONF_PAGE_ID, + [string]$CONFLUENCE_PAGE_ID, [Parameter(Mandatory = $true)] - [string]$FILTER_ID, - [Parameter(Mandatory = $true)] - [string]$REGISTER_STORAGE_TEMPLATE_PATH, - [Parameter(Mandatory = $false)] - [string]$TEMPLATE_PLACEHOLDER_MAP + [string]$FILTER_ID ) - if (-not $TEMPLATE_PLACEHOLDER_MAP) { - $TEMPLATE_PLACEHOLDER_MAP = @{ - 'GRCOSM_REGISTER_TABLE_DATA' = 'Get-JiraFilterResultsAsConfluenceTable -FILTER_ID $FILTER_ID' - } - } - # Check template file exists - if (-not (Test-Path $REGISTER_STORAGE_TEMPLATE_PATH)) { - Write-Error "Update-GRCosmConfRegister: Template file not found: $REGISTER_STORAGE_TEMPLATE_PATH" + $FILTER_INFO = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/filter/$($FILTER_ID)" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get + + $WEB_UI_HEADERS = @{ + Authorization = "Basic $env:AtlassianPowerKit_AtlassianAPIAuthString" + Accept = 'text/html' } - # Backup the Confluence page storage format - $BACKUP_FILE = Export-ConfluencePageStorageFormat -CONFLUENCE_SPACE_KEY $CONFLUENCE_SPACE_KEY -CONFLUENCE_PAGE_ID $CONF_PAGE_ID - Write-Debug "Backup file: $BACKUP_FILE" + $TIME_STAMP = Get-Date -Format 'yyyyMMdd_HHmmss' + $FILENAME = $FILTER_INFO.name.Replace(' ', '_').Replace(':', '-').Replace('/', '_').Replace('\', '_').Replace('[^a-zA-Z0-9]', '') + $TIME_STAMP + #FiLENAME is just GRC.*, exclude any prefix + $FILENAME = $FILENAME -replace '.*GRC', 'GRC' + $OUTPUT_FILE = "$($env:OSM_HOME)\$($env:AtlassianPowerKit_PROFILE_NAME)\$FILENAME" + $PUPPETEER_PATH = "$($env:OSM_HOME)\AtlassianPowerKit\AtlassianPowerKit-GRCosm" + #Push-Location $PUPPETEER_PATH - # Split $BACKUP_FILE on _ and drop the last element - $BACKUP_FILE_BASE = $($BACKUP_FILE -split '_2')[0] + $NodePath = Get-Command -Name 'node' | Select-Object -ExpandProperty Source + Write-Debug "NodePath: $NodePath" + $ScriptPath = "$PUPPETEER_PATH\generate-pdf.js" + $Arguments = @( + $ScriptPath, # First argument is the script path + "--url=https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/sr/jira.issueviews:searchrequest-printable/$FILTER_ID/SearchRequest-$FILTER_ID.html", + "--auth=Basic $env:AtlassianPowerKit_AtlassianAPIAuthString", + "--output=$OUTPUT_FILE", + '--format=A2', + '--landscape', + '--scale=0.75', + '--background' + ) - # Get JIRA filter data - Fields are determined by the JIRA filter - # Write-Debug '############################################################################################' - Write-Debug 'Update-GRCosmConfRegister: Getting JIRA filter results as Confluence table...' - $CONF_REGISTER_TABLE_DATA = Get-JiraFilterResultsAsConfluenceTable -FILTER_ID $FILTER_ID - Write-Debug 'Update-GRCosmConfRegister: Jira filter results as Confluence table returned' - # Write-Debug "Type: $($CONF_REGISTER_TABLE_DATA.GetType())" - # Write-Debug "Length: $($CONF_REGISTER_TABLE_DATA.Length)" - # Write-Debug "Content: `n $($CONF_REGISTER_TABLE_DATA)" - Write-Debug 'Update-GRCosmConfRegister: Getting Confluence template data...' - # ([string]::join("",$CONTENT.Split("`n").Trim())) - $UPDATED_PAGE_STORAGE_DATA = Get-Content $REGISTER_STORAGE_TEMPLATE_PATH -Raw - Write-Debug 'Update-GRCosmConfRegister: Confluence template data returned' - # Write-Debug "Type: $($UPDATED_PAGE_STORAGE_DATA.GetType())" - # Write-Debug "Length: $($UPDATED_PAGE_STORAGE_DATA.Length)" - # Write-Debug "Content: `n $($UPDATED_PAGE_STORAGE_DATA)" - Write-Debug 'Update-GRCosmConfRegister: Replacing GRCOSM_REGISTER_TABLE_DATA PLA with JIRA filter results...' - $UPDATED_PAGE_STORAGE_DATA = $UPDATED_PAGE_STORAGE_DATA -replace 'GRCOSM_REGISTER_TABLE_DATA', $CONF_REGISTER_TABLE_DATA - Write-Debug 'Update-GRCosmConfRegister: GRCOSM_REGISTER_TABLE_DATA replaced' - Write-Debug "Type: $($UPDATED_PAGE_STORAGE_DATA.GetType())" - Write-Debug '############################ STORAGE FORMAT TO SEND ################################################' - $UPDATED_PAGE_STORAGE_DATA | Out-File "$BACKUP_FILE_BASE-LATEST.xml" -Encoding UTF8 -Force - Write-Debug "############################ STORAGE FORMAT TO SEND ################################################ `n `n `n" - Write-Debug "$BACKUP_FILE_BASE-LATEST.xml" - Set-ConfluencePageContent -CONFLUENCE_SPACE_KEY $CONFLUENCE_SPACE_KEY -CONFLUENCE_PAGE_ID $CONF_PAGE_ID -CONFLUENCE_PAGE_STORAGE_FILE "$BACKUP_FILE_BASE-LATEST.xml" + # Start the Node.js process and wait for it to complete + $Process = Start-Process -FilePath $NodePath ` + -WorkingDirectory $PUPPETEER_PATH ` + -ArgumentList $Arguments ` + -NoNewWindow ` + -PassThru ` + -Wait + # Check the exit code + if ($Process.ExitCode -ne 0) { + Write-Error "Node.js script failed with exit code $($Process.ExitCode)" + } + else { + Write-Host 'Node.js script completed successfully.' + } + Import-Module "$($env:OSM_HOME)\AtlassianPowerKit\AtlassianPowerKit-Confluence\AtlassianPowerKit-Confluence.psd1" -Force | Out-Null + Write-Debug "Removing attachments from Confluence page: $CONFLUENCE_PAGE_ID" + Remove-AttachmentsFromConfPage -CONFLUENCE_PAGE_ID $CONFLUENCE_PAGE_ID | Out-Null + Write-Debug "Setting attachment for Confluence page: $CONFLUENCE_PAGE_ID to $OUTPUT_FILE.pdf" + Set-AttachmentForConfluencePage -CONFLUENCE_PAGE_ID $CONFLUENCE_PAGE_ID -ATTACHMENT_FILE_PATH "$OUTPUT_FILE.pdf" | Out-Null + Write-Debug "Attached file: $OUTPUT_FILE.pdf to Confluence page: $CONFLUENCE_PAGE_ID" + $CURRENT_PAGE_STORAGE_FORMAT_FILE = Export-ConfluencePageStorageFormat -CONFLUENCE_SPACE_KEY $CONFLUENCE_SPACE_KEY -CONFLUENCE_PAGE_ID $CONFLUENCE_PAGE_ID + $CURRENT_PAGE_STORAGE_FORMAT = Get-Content $CURRENT_PAGE_STORAGE_FORMAT_FILE + # Update ri:filename="GRCosm-_Asset_Register20250129_005159.pdf" to the uploaded file name + $UPDATED_PAGE_STORAGE_FORMAT = $CURRENT_PAGE_STORAGE_FORMAT -replace 'ri:filename=".*?"', "ri:filename=""$FILENAME.pdf""" + Write-Debug "Updated storage format for Confluence page: $CONFLUENCE_PAGE_ID, getting current page info..." + $CURRENT_PAGE_INFO = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/wiki/api/v2/pages/$CONFLUENCE_PAGE_ID" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get + # Step 4: Prepare the updated content + $UPDATEDBODY = @{ + id = $CONFLUENCE_PAGE_ID + status = 'current' + title = $CURRENT_PAGE_INFO.title + body = @{ + storage = @{ + representation = 'storage' + value = $UPDATED_PAGE_STORAGE_FORMAT + } + } + version = @{ + number = $CURRENT_PAGE_INFO.version.number + 1 + } + } | ConvertTo-Json -Depth 40 -Compress + #$UPDATEDBODY = $UPDATEDBODY -replace '\\"', '"' + Write-Debug "Updated body: $UPDATEDBODY" + + # Step 5: Update the Confluence page + Write-Debug "Pushing updated Confluence page: $CONFLUENCE_PAGE_ID" + $UPDATE_RESPONSE = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/wiki/api/v2/pages/$CONFLUENCE_PAGE_ID" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -ContentType 'application/json' -Method Put -Body $UPDATEDBODY + Write-Debug "Update response: $UPDATE_RESPONSE" + Write-Debug "Confluence page updated: $CONFLUENCE_PAGE_ID" + Return $UPDATE_RESPONSE | ConvertTo-Json } function Get-OSMPlaceholdersJira { @@ -209,7 +243,8 @@ function Get-OSMPlaceholdersConfluence { $placeholder | ForEach-Object { # Write output in red Write-Output "#### PLACEHOLDER FOUND!!! See: $($FILE.FullName): $_" - $PLACEHOLDERS += , ($($FILE.NAME), $_) } + $PLACEHOLDERS += , ($($FILE.NAME), $_) + } } else { Write-Debug "No placeholders found in file: $($FILE.FullName)" diff --git a/AtlassianPowerKit-GRCosm/generate-pdf.js b/AtlassianPowerKit-GRCosm/generate-pdf.js new file mode 100644 index 0000000..ad412c8 --- /dev/null +++ b/AtlassianPowerKit-GRCosm/generate-pdf.js @@ -0,0 +1,80 @@ +const puppeteer = require("puppeteer"); +const yargs = require("yargs"); +const fs = require("fs"); + +(async () => { + const argv = yargs + .option("url", { + alias: "u", + description: "The URL of the page to print", + type: "string", + demandOption: true, + }) + .option("auth", { + alias: "a", + description: + 'Base64-encoded Authorization header (e.g., "Basic username:api_token")', + type: "string", + demandOption: true, + }) + .option("output", { + alias: "o", + description: "Output file path prefix (e.g., 'output' for output.pdf)", + type: "string", + default: "output", + }) + .help() + .alias("help", "h").argv; + + const { url, auth, output } = argv; + + const browser = await puppeteer.launch({ headless: false }); // Use headless: true for production + const page = await browser.newPage(); + + try { + // Step 1: Set Authorization Header + await page.setExtraHTTPHeaders({ + Authorization: auth, + "X-Atlassian-Token": "no-check", + }); + + // Debug: Log failed requests + page.on("requestfailed", (request) => { + console.error( + `Request failed: ${request.url()} - ${request.failure().errorText}` + ); + }); + + // Step 2: Navigate to the page + console.log(`Navigating to URL: ${url}`); + await page.goto(url, { waitUntil: "networkidle2" }); + + // Debug: Log final URL + console.log(`Final URL: ${page.url()}`); + + // Debug: Capture screenshot + console.log("Capturing screenshot..."); + await page.screenshot({ path: "debug-screenshot.png", fullPage: true }); + + // Step 3: Generate the PDF + console.log("Generating PDF..."); + const pdfPath = `${output}.pdf`; + await page.pdf({ + path: pdfPath, + format: "A2", + landscape: true, + printBackground: true, + margin: { + top: "10mm", + right: "10mm", + bottom: "10mm", + left: "10mm", + }, + }); + console.log(`PDF saved as '${pdfPath}'`); + } catch (err) { + console.error("An error occurred:", err); + } finally { + await browser.close(); + } +})(); diff --git a/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psd1 b/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psd1 index ec19b4a..b6574dc 100644 --- a/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psd1 +++ b/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psd1 @@ -82,6 +82,7 @@ 'Get-JiraFields', 'Get-JiraFilterResultsAsConfluenceTable', 'Get-JiraOSMFilterList', + 'Get-JiraFilterResults', 'Get-JiraCloudIssueTypeSchema', 'Get-JiraIssue', 'Get-JiraIssueChangeNullsFromJQL', @@ -96,14 +97,19 @@ 'Get-JSMServices', 'Get-JSONFieldsWithData', 'Get-FormsForJiraProject', + 'Get-JIRAFieldContextList', + 'Get-JIRAFieldContextOptionList', + 'Reset-FormsFromJQLQueryResults', 'Remove-FormsFromJQLQueryResults', 'Remove-RemoteIssueLink', 'Set-IssueLinkTypeByJQL', 'Set-JiraIssueField', 'Set-JiraIssueFieldForJQLQueryResults', 'Set-JiraProjectProperty', + 'Set-JIRARegisterRefProperty', 'Set-AttachedFormsExternalJQLQuery', 'Set-AttachedFormsExternal', + 'Set-JIRAFieldContextOptions', 'Set-OSMRelationFieldBulkSQL', 'Set-OSMRelationFieldIssueKey' ) diff --git a/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psm1 b/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psm1 index 97cd297..2b289b1 100644 --- a/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psm1 +++ b/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psm1 @@ -68,25 +68,25 @@ GitHub: https://github.com/markz0r/AtlassianPowerKit $ErrorActionPreference = 'Stop'; $DebugPreference = 'Continue' # Directory of this file $RETRY_AFTER = 10 -function Convert-JiraIssueToTableRow { - param ( - [Parameter(Mandatory = $true)] - [array]$RAW_ROW - ) - $TABLE_ROW = '' - $RAW_ROW | ForEach-Object { - $ROW_VAL = $_ - if ($ROW_VAL) { - $TABLE_ROW += "

$ROW_VAL

" - } - else { - $TABLE_ROW += '

N/A

' - } - } - $TABLE_ROW += '' - $TABLE_ROW - return $TABLE_ROW -} +#function Convert-JiraIssueToTableRow { +# param ( +# [Parameter(Mandatory = $true)] +# [array]$RAW_ROW +# ) +# $TABLE_ROW = '' +# $RAW_ROW | ForEach-Object { +# $ROW_VAL = $_ +# if ($ROW_VAL) { +# $TABLE_ROW += "

$ROW_VAL

" +# } +# else { +# $TABLE_ROW += '

N/A

' +# } +# } +# $TABLE_ROW += '' +# $TABLE_ROW +# return $TABLE_ROW +#} function ConvertTo-JSONMarkdownList { @@ -114,8 +114,8 @@ function ConvertTo-JSONMarkdownList { function Export-RestorableJiraBackupJQL { param ( - [Parameter(Mandatory = $true)] - [string]$JQL_STRING + [Parameter(Mandatory = $false)] + [string]$JQL_STRING = 'project in (GRCOSM)' ) $OUTPUT_DIR = "$($env:OSM_HOME)\$($env:AtlassianPowerKit_PROFILE_NAME)\JIRA\Exported-Backup-$(Get-Date -Format 'yyyyMMdd-HHmmss')" if (-not (Test-Path $OUTPUT_DIR)) { @@ -124,9 +124,28 @@ function Export-RestorableJiraBackupJQL { Write-Debug "$($MyInvocation.MyCommand.Name) Getting JIRA issues to for JQL: $JQL_STRING ..." $JIRA_ISSUES = Get-JiraCloudJQLQueryResult -JQL_STRING $JQL_STRING -ReturnJSONOnly Write-Debug "$($MyInvocation.MyCommand.Name) - JQL Results Received from Get-JiraCloudJQLQueryResult..." - #$JIRA_ISSUES | ConvertTo-Json -Depth 100 | Out-File -FilePath "$OUTPUT_DIR\Full.json" -Force + $JIRA_ISSUES | ForEach-Object { + $ISSUE = $_ + $ISSUE_KEY = $ISSUE.key + Write-Debug "Exporting issue: $ISSUE_KEY to $OUTPUT_DIR\$ISSUE_KEY ..." + if (-not (Test-Path "$OUTPUT_DIR\$ISSUE_KEY")) { + New-Item -ItemType Directory -Path "$OUTPUT_DIR\$ISSUE_KEY" -Force | Out-Null + } + $ISSUE | ConvertTo-Json -Depth 100 | Out-File -FilePath "$OUTPUT_DIR\$ISSUE_KEY\$ISSUE_KEY.json" -Force + if ($ISSUE.fields.attachment) { + $ATTACHMENTS = $ISSUE.fields.attachment + $ATTACHMENTS | ForEach-Object { + $ATTACHMENT = $_ + $ATTACHMENT_ID = $ATTACHMENT.id + $ATTACHMENT_FILENAME = $ATTACHMENT.filename + Write-Debug "Exporting attachment: $OUTPUT_DIR\$ISSUE_KEY\$ATTACHMENT_FILENAME ..." + Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/attachment/content/$ATTACHMENT_ID" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get -ContentType $Attachment.mimeType -OutFile "$OUTPUT_DIR\$ISSUE_KEY\$ATTACHMENT_FILENAME" + Write-Debug "Exporting attachment: $OUTPUT_DIR\$ISSUE_KEY\$ATTACHMENT_FILENAME ... Done" + } + } + } #Write-Debug "Raw JSON file exported to: $OUTPUT_DIR\Full.json" - $JIRA_ISSUES_JSON = $JIRA_ISSUES | ConvertFrom-Json -Depth 1024 -NoEnumerate + $JIRA_ISSUES_JSON = $JIRA_ISSUES | ConvertFrom-Json -Depth 1024 Write-Debug "Exporting $($JIRA_ISSUES_JSON.Count) JIRA issues to: $OUTPUT_DIR ..." $JIRA_ISSUES_JSON | ForEach-Object { $ISSUE = $_ @@ -181,8 +200,7 @@ function Import-JIRAIssueFromJSONBackup { if ($FIELD_MAP_JSON) { Write-Debug "Field map provided: $FIELD_MAP_JSON" - } - else { + } else { Write-Debug 'Using manual mapping...' $POST_ISSUE = @{ fields = @{ @@ -259,8 +277,7 @@ function Import-JIRAIssueFromJSONBackup { $POST_ISSUE | ConvertTo-Json -Depth 100 -EscapeHandling Default | Write-Debug try { $POST_REST_RESPONSE = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/issue" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Post -ContentType 'application/json' -Body $($POST_ISSUE | ConvertTo-Json -EscapeHandling Default -Depth 100) - } - catch { + } catch { Write-Debug "Error importing issue: $ISSUE_KEY to $DEST_PROJECT_KEY as $DEST_ISSUE_TYPE" # Write full errordetails to terminal, ensuring $ErrorDetails returned as json is fully written to terminal and not truncated Write-Debug ($_.ErrorDetails.ErrorMessages | ConvertFrom-Json -Depth 100 -NoEnumerate | ConvertTo-Json -Depth 100) @@ -289,6 +306,41 @@ function Import-JIRAIssueFromJSONBackup { Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/issue/$NEW_ISSUE_KEY/comment" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Post -ContentType 'application/json' -Body $POST_COMMENT_JSON } +function Get-JiraFilterResults { + param ( + [Parameter(Mandatory = $true)] + [string]$FILTER_ID, + [Parameter(Mandatory = $false)] + [switch]$ReturnJSONOnly + ) + $FILTER_INFO = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/filter/$($FILTER_ID)" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get -ContentType 'application/json' + + $FILTER_COLUMNS = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/filter/$($FILTER_ID)/columns" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get -ContentType 'application/json' + Write-Debug "Filter Columns: $($FILTER_COLUMNS | ConvertTo-Json -Depth 10)" + $COLUMN_VALS = $FILTER_COLUMNS | ForEach-Object { $_.Value } + $JSON_ISSUE_SEARCH_RESPONSE = Get-JiraCloudJQLQueryResult -JQL_STRING $FILTER_INFO.jql -RETURN_FIELDS $COLUMN_VALS | ConvertFrom-Json + # Write-Debug "JSON_ISSUE_SEARCH_RESPONSE: $($JSON_ISSUE_SEARCH_RESPONSE | ConvertTo-Json -Depth 10)" + # For each Value in $FILTER_COLUMNS find in $JSON_ISSUE_SEARCH_RESPONSE.output_file and replace with $FILTER_COLUMNS.label + # Do a simple find and replace of the contents of the output file, dont need to convert to json + # Read the file contents once + $FILE_CONTENTS = Get-Content -Path $JSON_ISSUE_SEARCH_RESPONSE.output_file -Raw + + # Perform all replacements + $FILTER_COLUMNS | ForEach-Object { + $COLUMN = $_ + Write-Debug "Replacing $($COLUMN.value) with $($COLUMN.label) in $($JSON_ISSUE_SEARCH_RESPONSE.output_file)" + $FILE_CONTENTS = $FILE_CONTENTS -replace [regex]::Escape($COLUMN.value), $COLUMN.label + } + + # Write the updated contents back to the file once + Set-Content -Path $($JSON_ISSUE_SEARCH_RESPONSE.output_file) -Value $FILE_CONTENTS -Force + if ($ReturnJSONOnly) { + return $FILE_CONTENTS + } else { + return $JSON_ISSUE_SEARCH_RESPONSE.output_file + } +} + function Get-JiraFilterResultsAsConfluenceTable { param ( [Parameter(Mandatory = $true)] @@ -300,46 +352,63 @@ function Get-JiraFilterResultsAsConfluenceTable { $COLUMN_VALS = $FILTER_COLUMNS | ForEach-Object { $_.Value } $TABLE_HEADERS = '' - $CONFLUENCE_STORAGE_RAW_FOOTER = "

" + #$CONFLUENCE_STORAGE_RAW_FOOTER = "

" # Write-Debug "Filter Columns [$( $($FILTER_COLUMNS).count)]" - #Write-Debug "Filter Columns: $($FILTER_COLUMNS | ConvertTo-Json -Depth 10)" + Write-Debug "Filter Columns: $($FILTER_COLUMNS | ConvertTo-Json -Depth 10)" $FILTER_COLUMNS | ForEach-Object { $TABLE_HEADERS += "

$($_.label)

" } $TABLE_HEADERS += '' + Write-Debug "TABLE_HEADERS: $TABLE_HEADERS" Write-Debug '########################################################## Get-JiraFilterResultsAsConfluenceTable calling Get-JiraCloudJQLQueryResult' - $JSON_PART_FILES = Get-JiraCloudJQLQueryResult -JQL_STRING $FILTER_INFO.jql -RETURN_FIELDS $COLUMN_VALS + $JSON_ISSUE_SEARCH_RESPONSE = Get-JiraCloudJQLQueryResult -JQL_STRING $FILTER_INFO.jql -RETURN_FIELDS $COLUMN_VALS Write-Debug '########################################################## Get-JiraFilterResultsAsConfluenceTable returned from Get-JiraCloudJQLQueryResult - Done' - Write-Debug '########################################################## Get-JiraFilterResultsAsConfluenceTable - JSON_PART_FILES: ParseJIRAIssueJSONForConfluence ' - $HASH_ARRAYLIST = $JSON_PART_FILES | ForEach-Object { - Write-Debug "Processing JSON_PART_FILE: $_" - ParseJIRAIssueJSONForConfluence -JSON_PART_FILE $_ - } + Write-Debug '########################################################## Get-JiraFilterResultsAsConfluenceTable - Read JSON File' + Write-Debug "JSON_ISSUE_SEARCH_RESPONSE: $JSON_ISSUE_SEARCH_RESPONSE" + $JSON_ISSUE_ARRAY_FILE = $($JSON_ISSUE_SEARCH_RESPONSE | ConvertFrom-Json).output_file + $HASH_ARRAYLIST = Get-Content -Path $JSON_ISSUE_ARRAY_FILE | ConvertFrom-Json Write-Debug "HASH_ARRAYLIST: $($HASH_ARRAYLIST.GetType())" + Write-Debug "HASH_ARRAYLIST Count: $($HASH_ARRAYLIST.Count)" Write-Debug '########################################################## Get-JiraFilterResultsAsConfluenceTable - HASH_ARRAYLIST: ' + $TABLE_HEADER_NAMES = $FILTER_COLUMNS | ForEach-Object { $_.value } + Write-Debug "TABLE_HEADER_NAMES: $($TABLE_HEADER_NAMES)" + $HASH_ARRAYLIST | ForEach-Object { + $ROW_HASH = $_ + Write-Debug '####################' + Write-Debug "ISSUE: $($ROW_HASH.Key)" + Write-Debug 'FIELDS: ' + $($ROW_HASH.fields) | ConvertTo-Json -Depth 10 | Write-Debug + } + Write-Debug '##########################################################' + Write-Debug 'THIS FUNCTION IS ABANDONED - use Export-PrintableJiraFilterResults instead' + exit + #$flattenedIssues = $HASH_ARRAYLIST | ForEach-Object { + ## [PSCustomObject]@{ + - $TABLE_ROWS = @($HASH_ARRAYLIST | ForEach-Object { - $ROW_HASH = $_ - Write-Debug '####################' - - Write-Debug "ISSUE: $($ROW_HASH.Key)" - #Write-Debug 'FIELDS: ' - #$($ROW_HASH.fields) | ConvertTo-Json -Depth 10 | Write-Debug - $ORDERED_FIELD_VALUES = @() - foreach ($FILTER_COLUMN in $FILTER_COLUMNS) { - $FIELD_NAME = $FILTER_COLUMN.value - $FIELD_VALUE = $ROW_HASH.Fields[$FIELD_NAME] - - # Add the field value to the ordered list - $ORDERED_FIELD_VALUES += $FIELD_VALUE - } - Write-Debug 'Get-JiraFilterResultsAsConfluenceTable: Starting Convert-JiraIssueToTableRow...with ORDERED_FIELD_VALUES: ' - Convert-JiraIssueToTableRow -RAW_ROW $ORDERED_FIELD_VALUES - } - ) + #$TABLE_ROWS = @($HASH_ARRAYLIST | ForEach-Object { + # $ROW_HASH = $_ + # Write-Debug '####################' + + # Write-Debug "ISSUE: $($ROW_HASH.Key)" + # #Write-Debug 'FIELDS: ' + # #$($ROW_HASH.fields) | ConvertTo-Json -Depth 10 | Write-Debug + # $ORDERED_FIELD_VALUES = @() + # Write-Debug 'Filter Columns: ' + # Write-Debug $FILTER_COLUMNS + # foreach ($FILTER_COLUMN in $FILTER_COLUMNS) { + # $FIELD_NAME = $FILTER_COLUMN.value + # $FIELD_VALUE = $ROW_HASH.Fields[$FIELD_NAME] + # # Add the field value to the ordered list + # $ORDERED_FIELD_VALUES += $FIELD_VALUE + # } + # Write-Debug 'Get-JiraFilterResultsAsConfluenceTable: Starting Convert-JiraIssueToTableRow...with ORDERED_FIELD_VALUES: ' + # Convert-JiraIssueToTableRow -RAW_ROW $ORDERED_FIELD_VALUES + # } + #) - $CONFLUENCE_STORAGE_RAW = $TABLE_HEADERS + $TABLE_ROWS + $CONFLUENCE_STORAGE_RAW_FOOTER - return $CONFLUENCE_STORAGE_RAW + #$CONFLUENCE_STORAGE_RAW = $TABLE_HEADERS + $TABLE_ROWS + $CONFLUENCE_STORAGE_RAW_FOOTER + #return $CONFLUENCE_STORAGE_RAW } function Get-JiraIssueChangeNullsFromJQL { @@ -357,8 +426,7 @@ function Get-JiraIssueChangeNullsFromJQL { $NULL_CHANGE_ITEMS = $REST_RESULTS.issues | ForEach-Object { Get-JiraIssueChangeNulls -Key $_.key } - } - else { + } else { Write-Debug "Field name or ID provided: $FIELD_NAME_OR_ID_OR_NULL" $NULL_CHANGE_ITEMS = $REST_RESULTS.issues | ForEach-Object { Get-JiraIssueChangeNulls -Key $_.key -SELECTOR "$FIELD_NAME_OR_ID_OR_NULL" @@ -376,20 +444,17 @@ function Get-JiraIssueChangeNullsFromJQL { if ($_.fieldtype -eq 'custom') { if ($_.fieldId -ne 'customfield_10163') { $New_Value = $_.fromString - } - else { + } else { $New_Value = $_.from } $New_Value = $New_Value -replace '[\[\]\s]', '' $New_Value = $New_Value.Split(',') - } - else { + } else { $New_Value = , @($_.from) } if ($_.fieldId -eq 'customfield_10163') { $TARGET_FIELD = 'customfield_10370' - } - else { + } else { $TARGET_FIELD = $_.fieldId } @@ -448,8 +513,7 @@ function Get-JSONFieldsWithData { Write-Debug "Processing field: $FIELD" if ((!$FIELD.Value) -or ($FIELD.Key -in $EXCLUDED_FIELDS)) { Write-Debug "Field without data: $FIELD)" - } - else { + } else { Write-Debug '######' Write-Debug "Field with data: $($FIELD | ConvertTo-Json -Depth 10)" $FIELD_INFO = $JIRA_FIELD_ARRAY | Where-Object { $_.id -eq $FIELD.Key } @@ -468,8 +532,7 @@ function Get-JSONFieldsWithData { Write-Debug "Processing file: $($FILE_PATH)" if (-not (Test-Path $FILE_PATH)) { Write-Error "File not found: $($FILE_PATH)" - } - else { + } else { $RAW_JSON_STRING = Get-Content -Path $FILE_PATH -Raw Write-Debug "Raw JSON String: $($RAW_JSON_STRING.GetType())" $JSON_OBJECT_ARRAY = $RAW_JSON_STRING | ConvertFrom-Json -Depth 40 @@ -514,8 +577,7 @@ function Test-JiraIssueExists { # Invoke-RestMethod and capture the response to $ISSUE_KEY, even if it is an error try { $ISSUE_RESPONSE = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/issue/$($KeyOrID)?fields=null" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get -ContentType 'application/json' - } - catch { + } catch { Write-Debug ($_ | Select-Object -Property * -ExcludeProperty psobject | Out-String) $ISSUE_RESPONSE = ($_ | Select-Object -Property * -ExcludeProperty psobject | Out-String) } @@ -523,8 +585,7 @@ function Test-JiraIssueExists { if ($ISSUE_RESPONSE.id) { Write-Debug "Jira issue $KeyOrID exists." return $true - } - else { + } else { Write-Debug "Jira issue $KeyOrID does not exist." return $false } @@ -552,8 +613,7 @@ function Get-JiraIssueLinks { ) try { $ISSUE_LINKS = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/2/issue/$($IssueKey)?fields=issuelinks" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get -ContentType 'application/json' - } - catch { + } catch { Write-Debug ($_ | Select-Object -Property * -ExcludeProperty psobject | Out-String) Write-Error "Error updating field: $($_.Exception.Message)" } @@ -564,41 +624,42 @@ function Get-JiraIssueLinks { function Clear-EmptyFields { param ( [Parameter(Mandatory = $true)] - [psobject]$Object + [string]$JSONString ) - Write-Warning 'THIS IS CURRENTLY BROKENNNN' - - if ($Object -is [System.Management.Automation.PSCustomObject]) { - # Process hashtable or custom object - $Object.psobject.properties | ForEach-Object { - if ($null -eq $_.Value -or $_.Value -eq '' -or ($_.Value -is [System.Collections.ICollection] -and $_.Value.Count -eq 0)) { - $Object.psobject.properties.Remove($_.Name) | Out-Null - } - else { - # Recursively clean nested objects - $_.Value = Clear-EmptyFields -Object $_.Value | Out-Null - } - } - } - elseif ($Object -is [System.Collections.IDictionary]) { - # Process dictionary - $keys = @($Object.Keys) - foreach ($key in $keys) { - if ($null -eq $Object[$key] -or $Object[$key] -eq '' -or ($Object[$key] -is [System.Collections.ICollection] -and $Object[$key].Count -eq 0)) { - $Object.Remove($key) | Out-Null - } - else { - # Recursively clean nested objects - $Object[$key] = Clear-EmptyFields -Object $Object[$key] - } + $JSON_OBJECT = $JSONString | ConvertFrom-Json -Depth 100 + $JSON_OBJECT | ForEach-Object { + $FIELD = $_ + if ($null -eq $FIELD) { + $FIELD = $null } } - elseif ($Object -is [System.Collections.IEnumerable] -and $Object -isnot [string]) { - # Process arrays/lists - $Object = $Object | ForEach-Object { Clear-EmptyFields -Object $_ } | Where-Object { $_ -ne $null } + return $JSON_OBJECT | ConvertTo-Json -Depth 100 -Compress +} +function Update-FieldNamesRaw { + param ( + [Parameter(Mandatory = $true)] + [string]$JSON_FILE_PATH + ) + + # Get Jira fields and create a mapping hashtable + $JIRA_FIELDS = Get-JiraFields + $JIRA_FIELD_MAPS = @{} + $JIRA_FIELDS | ForEach-Object { + $JIRA_FIELD_MAPS[$_.id] = $_.name } - return $Object + # Read the JSON file as a string + $jsonContent = Get-Content -Path $JSON_FILE_PATH -Raw + + # Perform raw find and replace for each field + foreach ($key in $JIRA_FIELD_MAPS.Keys) { + $jsonContent = $jsonContent -replace "`"$key`"", "`"$($JIRA_FIELD_MAPS[$key])`"" + } + $UPDATED_FILE = $JSON_FILE_PATH -replace '.json', '-updated.json' + + # Save the updated JSON content back to the file + $jsonContent | Out-File -FilePath $UPDATED_FILE -Force + return $UPDATED_FILE } # Function to return JQL query results as a PowerShell object that includes a loop to ensure all results are returned even if the @@ -635,8 +696,7 @@ function Get-JiraCloudJQLQueryResult { if ($DYN_LIMIT -eq 0) { Write-Debug 'No results found for the JQL query...' return - } - elseif ($DYN_LIMIT -gt $WARNING_LIMIT) { + } elseif ($DYN_LIMIT -gt $WARNING_LIMIT) { # Advise the user that the number of results exceeds $WARNING_LIMIT and ask if they want to continue Write-Warning "The number of results for the JQL query exceeds $WARNING_LIMIT. Do you want to continue? [Y/N]" $continue = Read-Host @@ -645,12 +705,14 @@ function Get-JiraCloudJQLQueryResult { return } } - $POST_BODY.expand = 'names' - $POST_BODY.maxResults = 1000 + $POST_BODY = @{ + jql = "$JQL_STRING" + expand = 'names' + maxResults = 250 + } if ($RETURN_FIELDS -and $null -ne $RETURN_FIELDS -and $RETURN_FIELDS.Count -gt 0) { $POST_BODY.fields = $RETURN_FIELDS - } - else { + } else { Write-Debug 'RETURN_FIELDS not provided, using default fields...' $POST_BODY.fields = @('*all', '-issuelinks', '-subtasks', '-worklog', '-changelog', '-comment') } @@ -678,8 +740,7 @@ function Get-JiraCloudJQLQueryResult { Write-Debug "Next page token found: $($REST_RESPONSE.nextPageToken), adding this page to the results..." $NEXT_PAGE = $REST_RESPONSE.nextPageToken $COMPLETE = $false - } - else { + } else { $NEXT_PAGE = $null $COMPLETE = $true } @@ -690,8 +751,7 @@ function Get-JiraCloudJQLQueryResult { Write-Debug "ISSUE_ARRAY Count: $($ISSUE_ARRAY.Count)" Write-Debug "$($MyInvocation.MyCommand.Name): End of loop, Collected Issue count: $($ISSUE_ARRAY.Count)...Completed?: $COMPLETE" } while (! $COMPLETE) - } - catch { + } catch { Write-Debug 'Error getting jql results with Request details:' Write-Debug '##############################################' Write-Debug "-Uri https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/search/jql -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Post -Body $($POST_BODY | ConvertTo-Json -Depth 10) " @@ -710,56 +770,41 @@ function Get-JiraCloudJQLQueryResult { #Write-Debug "FIELDS ARRAY TYPE IS: $($FIELDS_ARRAY.GetType())' #Write-Debug 'FIELD COUNT FOR ISSUE: $($FIELDS_ARRAY.Count)' Write-Debug "Cleaning fields for issue: $($ISSUE.key)" - Write-Debug "FIELDS_ARRAY: $($FIELDS_ARRAY.GetType())" - Write-Debug "FIELDS_ARRAY Count: $($FIELDS_ARRAY.Count)" - $CLEAN_FIELD_ARRAY = Clear-EmptyFields -Object $FIELDS_ARRAY + #Write-Debug "FIELDS_ARRAY: $($FIELDS_ARRAY.GetType())" + #Write-Debug "FIELDS_ARRAY Count: $($FIELDS_ARRAY.Count)" # Replace the fields array with the cleaned fields array in the issue object #Write-Debug "Updating Issue.fields using CLEAN_FILED_ARRAY: $($CLEAN_FIELD_ARRAY.GetType())" - $ISSUE.fields = $CLEAN_FIELD_ARRAY + $ISSUE.fields = $(Clear-EmptyFields -JSONString ($FIELDS_ARRAY | ConvertTo-Json -Depth 100)) | ConvertFrom-Json return $ISSUE } # Replace the combined issues array with the cleaned issues array $ISSUE_ARRAY = $CLEAN_ISSUES } - if ($MapFieldNames) { - # Get the field mappings from Jira - $JIRA_FIELDS = Get-JiraFields - - # Create a hashtable to map field IDs to field names - $JIRA_FIELD_MAPS = @{} - $JIRA_FIELDS | ForEach-Object { - $JIRA_FIELD_MAPS[$_.id] = $_.name | Out-Null - #Write-Debug "JIRA_FIELD_MAPS: $($_.id) - $($_.name)" - } - - # Map the field names in the combined issues - $ISSUE_ARRAY | ForEach-Object { - $_.fields.PSObject.Properties | ForEach-Object { - if ($JIRA_FIELD_MAPS.ContainsKey($_.Name)) { - $newName = $JIRA_FIELD_MAPS[$_.Name] - $_ | Add-Member -MemberType NoteProperty -Name $newName -Value $_.Value -Force | Out-Null - $_.PSObject.Properties.Remove($_.Name) | Out-Null - } - } - } - } if ($ReturnJSONOnly) { + if ($MapFieldNames) { + Write-Warning 'MapFieldNames not supported with ReturnJSONOnly, ignoring...' + } Write-Debug 'Returning JSON only...' #Write-Debug "########## $($MyInvocation.MyCommand.Name) completed, returning JSON..:" #$ISSUE_ARRAY | ConvertTo-Json -Depth 100 | Write-Debug #Write-Debug "########## $($MyInvocation.MyCommand.Name) completed, returning JSON ^^^" - return $($ISSUE_ARRAY | ConvertTo-Json -Depth 100) - } - else { + return $($ISSUE_ARRAY | ConvertTo-Json -Depth 100 -Compress) + + } else { $ISSUE_ARRAY | ConvertTo-Json -Depth 100 -Compress | Out-File -FilePath $OUTPUT_FILE Write-Debug "JIRA COMBINED Query results written to: $OUTPUT_FILE" #Write-Debug '########## Get-JiraCloudJQLQueryResult completed, OUTPUT_FILE_LIST: ' #$OUTPUT_FILE_LIST | Write-Debug # Combine raw, compressed JSON files into a single JSON file that is valid JSON + if ($MapFieldNames) { + # Get the field mappings from Jira + Write-Debug "JIRA COMBINED Query results written to: $OUTPUT_FILE" + $FINAL_FILE = Update-FieldNamesRaw -JSON_FILE_PATH $OUTPUT_FILE + } $RESULT_JSON = @{ result = 'success' - issue_count = $ISSUE_ARRAY.Count - output_file = $OUTPUT_FILE + issue_count = $VALIDATE_QUERY.count + output_file = $FINAL_FILE output_dir = $OUTPUT_DIR } return $RESULT_JSON | ConvertTo-Json @@ -821,8 +866,7 @@ function Set-JiraIssueField { #Write-Debug "Trying: Invoke-RestMethod -Uri $REQUEST_URL -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Put -Body $FIELD_PAYLOAD -ContentType 'application/json'" try { $UPDATE_ISSUE_RESPONSE = Invoke-RestMethod -Uri $REQUEST_URL -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Put -Body $($FIELD_PAYLOAD | ConvertTo-Json -Depth 30) -ContentType 'application/json' - } - catch { + } catch { $_ | Select-Object -Property * -ExcludeProperty psobject | Out-String | Write-Debug Write-Error "Error updating field: $($_.Exception.Message)" } @@ -854,8 +898,7 @@ function Set-JiraIssueFieldForJQLQueryResults { Write-Debug "Updating fields for issue: $($_.key - $ISSUE_SUMMARY)" if (! $DryRun) { Set-JiraIssueField -ISSUE_KEY $ISSUE_KEY -Field_Ref $FIELD_REF -New_Value $NEW_VALUE -FIELD_TYPE $FIELD_TYPE - } - else { + } else { Write-Debug "Dry Run: Set-JiraIssueField -ISSUE_KEY $ISSUE_KEY -Field_Ref $FIELD_REF -New_Value $NEW_VALUE" } } @@ -895,8 +938,7 @@ function Set-OSMRelationFieldIssueKey { if ($_.inwardIssue) { $LINK_TYPE_NAME = $_.type.inward $LINKED_ISSUE = $_.inwardIssue.key - } - else { + } else { $LINK_TYPE_NAME = $_.type.outward $LINKED_ISSUE = $_.outwardIssue.key } @@ -904,8 +946,7 @@ function Set-OSMRelationFieldIssueKey { Write-Debug "Link Type: $LINK_TYPE_NAME, Linked Issue: $LINKED_ISSUE" if ($LINKED_ISSUES_HASHTABLE.ContainsKey($LINK_TYPE_NAME)) { $LINKED_ISSUES_HASHTABLE[$LINK_TYPE_NAME] += @("https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/browse/$LINKED_ISSUE") - } - else { + } else { $LINKED_ISSUES_HASHTABLE[$LINK_TYPE_NAME] = @("https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/browse/$LINKED_ISSUE") } } @@ -945,8 +986,7 @@ function Get-JiraIssueChangeNulls { $NULL_CHANGE_ITEMS += $MAMMA.items | Where-Object { ($MAMMA.created -gt (Get-Date).AddMonths($CHECK_MONTHS)) -and ((-not $_.toString) -and ( -not $_.to)) -and (-not $_.field.StartsWith('BCMS')) -and (-not $EXCLUDED_FIELDS.Contains($_.field)) } - } - else { + } else { $NULL_CHANGE_ITEMS += $MAMMA.items | Where-Object { (($SELECTOR -eq $($_.fieldId)) -and ($INCLDUED_VALUES.Contains($_.toString))) #Write-Debug "Selector: $SELECTOR" @@ -1072,13 +1112,11 @@ function Get-JiraProjectWorkflowSchemes { ConvertTo-Json $PROJECT_OBJECT -Depth 50 | Write-Debug if ($PROJECT_OBJECT.id) { $PROJECT_ID = $PROJECT_OBJECT.id - } - else { + } else { Write-Error "Project ID not found for project key: $PROJECT_KEY" } $URL = "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/workflowscheme/project?projectId=$PROJECT_ID" - } - else { + } else { Write-Debug 'No project key passed, getting all project workflow schemes...disables' # $OUTPUT_FILE = "$OUTPUT_DIR\$env:AtlassianPowerKit_PROFILE_NAME-ALL-JIRAProjectWorkflowSchemes-$(Get-Date -Format 'yyyyMMdd-HHmmss').json" # $URL = "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/workflowscheme" @@ -1115,8 +1153,7 @@ function Get-JiraActiveWorkflows { if ($AMIBIGUOUS_FIELDS) { $AMIBIGUOUS_FIELDS = $AMIBIGUOUS_FIELDS -join ', ' $WORKFLOW | Add-Member -MemberType NoteProperty -Name 'AmbiguousDup' -Value $AMIBIGUOUS_FIELDS - } - else { + } else { $WORKFLOW | Add-Member -MemberType NoteProperty -Name 'AmbiguousDup' -Value 'No' } $OUTPUT_FILE = "$env:OSM_HOME\$env:AtlassianPowerKit_PROFILE_NAME\JIRA\$env:AtlassianPowerKit_PROFILE_NAME-JIRAWorkflows-$(Get-Date -Format 'yyyyMMdd-HHmmss').csv" @@ -1138,8 +1175,7 @@ function Get-JiraFieldDups { $JIRA_FIELDS | ForEach-Object { if ($JIRA_FIELD_NAMES -contains $_.name) { $DUPLICATE_FIELD_NAMES += $_.name - } - else { + } else { $JIRA_FIELD_NAMES += $_.name } } @@ -1166,6 +1202,7 @@ function Get-JiraFields { $CSV_DATA | Out-File -FilePath $OUTPUT_FILE #$REST_RESULTS | ConvertTo-Json -Depth 10 | Out-File -FilePath $OUTPUT_FILE # Write results to a CSV file + Write-Debug "Jira Fields written to: $OUTPUT_FILE" } return $REST_RESULTS @@ -1179,8 +1216,7 @@ function Get-DuplicateJiraFieldNames { $JIRA_FIELDS | ForEach-Object { if ($JIRA_FIELD_NAMES -contains $_.name) { $DUPLICATE_FIELD_NAMES += $_.name - } - else { + } else { $JIRA_FIELD_NAMES += $_.name } } @@ -1195,8 +1231,7 @@ function Get-JSMServices { $REST_RESULTS = Invoke-RestMethod -Uri $JSM_SERVICES_ENDPOINT -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get -ContentType 'application/json' Write-Debug $REST_RESULTS.getType() Write-Debug (ConvertTo-Json $REST_RESULTS -Depth 10) - } - catch { + } catch { Write-Debug ($_ | Select-Object -Property * -ExcludeProperty psobject | Out-String) # This functions name is $MyInvocation.MyCommand.Name $ERROR_MESSAGE = "Error from $($MyInvocation.MyCommand.Name) - $($_.Exception.Message)" @@ -1215,8 +1250,7 @@ function Get-JSMService { $REST_RESULTS = Invoke-RestMethod -Uri $JSM_SERVICES_ENDPOINT -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get -ContentType 'application/json' Write-Debug $REST_RESULTS.getType() Write-Debug (ConvertTo-Json $REST_RESULTS -Depth 10) - } - catch { + } catch { Write-Debug ($_ | Select-Object -Property * -ExcludeProperty psobject | Out-String) Write-Error "Error updating field: $($_.Exception.Message)" } @@ -1299,8 +1333,7 @@ function Set-JiraProjectProperty { $content = Get-Content $JSON_FILE # validate the JSON content $content | ConvertFrom-Json | Out-Null - } - catch { + } catch { Write-Debug "File not found or invalid JSON: $JSON_FILE" $content | ConvertFrom-Json | Out-Null return @@ -1311,10 +1344,36 @@ function Set-JiraProjectProperty { Write-Debug (ConvertTo-Json $REST_RESULTS -Depth 10) Write-Debug '###############################################' Write-Debug "Querying the property to confirm the value was set... $PROPERTY_KEY in $PROJECT_KEY via $($env:AtlassianPowerKit_AtlassianAPIEndpoint)" - Get-JiraProjectProperty -JiraCloudProjectKey $PROJECT_KEY -PROPERTY_KEY $PROPERTY_KEY + Get-JiraProjectProperty -PROJECT_KEY $PROJECT_KEY -PROPERTY_KEY $PROPERTY_KEY Write-Debug '###############################################' } +function Set-JIRARegisterRefProperty { + param ( + [Parameter(Mandatory = $true)] + [string]$PROJECT_KEY, + [Parameter(Mandatory = $false)] + [string]$FILTER_ID # For JSON files just use Set-JiraProjectProperty -PROJECT_KEY $PROJECT_KEY -PROPERTY_KEY $PROPERTY_KEY -JSON_FILE $OUTPUT_FILE + ) + # Get the Register values + $REGISTER_JSON = Get-JiraCloudJQLQueryResult -JQL_STRING "filter=$FILTER_ID" -RETURN_FIELDS @('summary') -ReturnJSONOnly | ConvertFrom-Json + # Build a JSON property object with the with id: issue.key and value: issue.fields.summary + #Write-Debug "Register JSON: $REGISTER_JSON" + $REGISTER_ARRAY = $REGISTER_JSON | ForEach-Object { + Write-Debug "Register Object: $_" + $REGISTER_OBJECT = @{ + 'id' = $_.key + 'name' = $_.fields.summary + } + $REGISTER_OBJECT + } + Write-Debug "Register Array: $($REGISTER_ARRAY | ConvertTo-Json)" + $OUTPUT_FILE = "$env:OSM_HOME\$env:AtlassianPowerKit_PROFILE_NAME\JIRA\$env:AtlassianPowerKit_PROFILE_NAME-$PROJECT_KEY-$PROPERTY_KEY-Register-$(Get-Date -Format 'yyyyMMdd-HHmmss').json" + $REGISTER_ARRAY | ConvertTo-Json -Compress | Out-File -FilePath $OUTPUT_FILE + Set-JiraProjectProperty -PROJECT_KEY $PROJECT_KEY -PROPERTY_KEY $PROPERTY_KEY -JSON_FILE $OUTPUT_FILE + #Set-JiraProjectProperty -PROJECT_KEY $PROJECT_KEY -PROPERTY_KEY $PROPERTY_KEY -JSON_FILE (ConvertTo-Json $REGISTER_ARRAY -Depth 10) +} + # Funtion to delete a project property (JIRA entity) function Clear-JiraProjectProperty { param ( @@ -1332,6 +1391,116 @@ function Clear-JiraProjectProperty { Write-Debug '###############################################' } +function Get-JIRAFieldContextList { + param ( + [Parameter(Mandatory = $true)] + [string]$FIELD_ID + ) + $REST_RESULTS = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)//rest/api/3/field/$FIELD_ID/context" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get -ContentType 'application/json' + #Write-Debug $REST_RESULTS.getType() + #Write-Debug (ConvertTo-Json $REST_RESULTS -Depth 10) + return $REST_RESULTS +} + +function Get-JIRAFieldContextOptionList { + param ( + [Parameter(Mandatory = $true)] + [string]$FIELD_ID, + [Parameter(Mandatory = $true)] + [string]$CONTEXT_ID + + ) + $REQUEST_RESULTS = $() + $REST_RESULTS = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/field/$FIELD_ID/context/$CONTEXT_ID/option" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get -ContentType 'application/json' + $REQUEST_RESULTS += $REST_RESULTS.values + while (!$REST_RESULTS.isLast) { + $nextPageStart = $REST_RESULTS.startAt + $REST_RESULTS.maxResults + $APPEND_RESULTS = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/field/$FIELD_ID/context/$CONTEXT_ID/option?startAt=$nextPageStart" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get -ContentType 'application/json' + $REST_RESULTS.values += $APPEND_RESULTS.values + $REST_RESULTS.startAt = $APPEND_RESULTS.startAt + $REST_RESULTS.isLast = $APPEND_RESULTS.isLast + $REQUEST_RESULTS += $APPEND_RESULTS.values + } + $REQUEST_RESULTS | ConvertTo-Json -Depth 50 | Write-Debug + Write-Debug '^^^^^ Options found: ' + #ENSURE THE $REQUEST_RESULTS is flattened + return $REQUEST_RESULTS | ConvertTo-Json -Depth 50 -Compress +} + +function Set-JIRAFieldContextOptions { + param ( + [Parameter(Mandatory = $true)] + [string]$FIELD_ID, + [Parameter(Mandatory = $true)] + [string]$CONTEXT_ID, + [Parameter(Mandatory = $true)] + [string]$SOURCE_PROPERTIES_URL + ) + try { + $SOURCE_PROPERTIES = Invoke-RestMethod -Uri $SOURCE_PROPERTIES_URL -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get -ContentType 'application/json' + #Write-Debug $SOURCE_PROPERTIES.getType() + #Write-Debug (ConvertTo-Json $SOURCE_PROPERTIES -Depth 10) + } catch { + Write-Debug ($_ | Select-Object -Property * -ExcludeProperty psobject | Out-String) + Write-Error "Error updating field: $($_.Exception.Message)" + } + + # Get the current options for the field context + $EXISTING_OPTIONS = Get-JIRAFieldContextOptionList -FIELD_ID $FIELD_ID -CONTEXT_ID $CONTEXT_ID | ConvertFrom-Json -Depth 10 + # ConvertFrom-Json -Depth 40 + #Write-Debug "Existing Options: $($EXISTING_OPTIONS.values.count)" + $RET_ARRAY = @() # Initialize an empty array to store results + + foreach ($HOLDME in $SOURCE_PROPERTIES.value) { + # Check if the option already exists + $EXISTING_OPTION = $EXISTING_OPTIONS | Where-Object { $_.value -eq $HOLDME.name } + + if ($EXISTING_OPTION) { + Write-Debug "Option found: $($HOLDME.name) - Updating it..." + $EXISTING_OPTION | ConvertTo-Json -Depth 10 | Write-Debug + + # Construct the JSON payload for updating + $JSON_PAYLOAD = @{ + 'id' = $EXISTING_OPTION.id + 'value' = $HOLDME.name + 'disabled' = $false + } | ConvertTo-Json -Depth 10 + + $METHOD = 'Put' + } else { + Write-Debug "Option not found: $($HOLDME.name) - Adding it..." + + # Construct the JSON payload for adding + $JSON_PAYLOAD = @{ + 'value' = $HOLDME.name + 'disabled' = $false + } | ConvertTo-Json -Depth 10 + + $METHOD = 'Post' + } + + # Construct the full payload + $FULL_PAYLOAD = "{`"options`": [$JSON_PAYLOAD]}" + + # Make the API request + $REST_RESULTS = Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/field/$FIELD_ID/context/$CONTEXT_ID/option" ` + -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) ` + -Method $METHOD ` + -Body $FULL_PAYLOAD ` + -ContentType 'application/json' ` + -ErrorAction Continue + + # Store the result + $RET_ARRAY += $REST_RESULTS + } + + Write-Debug "Options processed for field context: $FIELD_ID - $CONTEXT_ID - ALL DONE!" + + # Return the results as JSON + return $RET_ARRAY | ConvertTo-Json -Depth 10 -Compress + +} + # Function to list all users for a JSM cloud project function Remove-RemoteIssueLink { param ( @@ -1351,8 +1520,7 @@ function Remove-RemoteIssueLink { Write-Debug "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/issue/$($_.key)/remotelink?globalId=$GLOBAL_LINK_ID_ENCODED" Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/rest/api/3/issue/$($_.key)/remotelink?globalId=$GLOBAL_LINK_ID_ENCODED" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Delete } - } - catch { + } catch { Write-Debug ($_ | Select-Object -Property * -ExcludeProperty psobject | Out-String) Write-Error "Error updating field: $($_.Exception.Message)" } @@ -1406,16 +1574,13 @@ function Set-IssueLinkTypeByJQL { if ($CONFIRM -ne 'Y') { Write-Warning 'Operation cancelled...' return - } - else { + } else { Write-Warning 'Proceeding !' } - } - else { + } else { Write-Warning "Force flag set, removing all links of type: $CURRNT_LINK_TYPE from the JQL query results: $JQL_STRING" } - } - elseif ((! $CURRNT_LINK_TYPE) -and $NEW_LINK_TYPE_OR_NONE -and $JQL_STRING) { + } elseif ((! $CURRNT_LINK_TYPE) -and $NEW_LINK_TYPE_OR_NONE -and $JQL_STRING) { #JUST CREATE A NEW LINK # Read from user the target issue key (asking for it) if (! $TARGET_ISSUE_KEY) { @@ -1429,12 +1594,10 @@ function Set-IssueLinkTypeByJQL { return } Write-Debug "Creating link type: $NEW_LINK_TYPE_OR_NONE from JQL query results: $JQL_STRING to $TARGET_ISSUE_KEY" - } - else { + } else { Write-Debug "Updating link type: $CURRNT_LINK_TYPE to $NEW_LINK_TYPE_OR_NONE from JQL query results: $JQL_STRING" } - } - else { + } else { Write-Debug 'Issue links for JQL query results can be created, updated or deleted' Write-Debug 'To create a link, required parameters are JQL_STRING, NEW_LINK_TYPE_OR_NONE, LINK_DIRECTION_FOR_JQL and TARGET_ISSUE_KEY' Write-Debug 'To update or remove a link, required parameters are JQL_STRING, CURRNT_LINK_TYPE, NEW_LINK_TYPE_OR_NONE' @@ -1484,8 +1647,7 @@ function Set-IssueLinkTypeByJQL { if (! $LINK_EXISTS) { Write-Debug "Creating new link [type = $NEW_LINK_TYPE_OR_NONE] from $INWARD_ISSUE_KEY to $OUTWARD_ISSUE_KEY" Invoke-RestMethod -Uri $ISSUELINK_ENDPOINT -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Post -Body ($PAYLOAD | ConvertTo-Json -Depth 10) -ContentType 'application/json' - } - else { + } else { Write-Debug 'Link already exists... skipping <---------------------------------------' } } @@ -1498,8 +1660,7 @@ function Set-IssueLinkTypeByJQL { Write-Debug "Removing link: $LINK_ID" try { Invoke-RestMethod -Uri "$ISSUELINK_ENDPOINT/$LINK_ID" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Delete -ContentType 'application/json' - } - catch { + } catch { Write-Debug ($_ | Select-Object -Property * -ExcludeProperty psobject | Out-String) Write-Error "Error updating field: $($_.Exception.Message)" } @@ -1515,8 +1676,7 @@ function Set-IssueLinkTypeByJQL { if ($LINK_DIRECTION_FOR_JQL -eq 'inward') { $INWARD_ISSUE_KEY = $THIS_ISSUE.key $OUTWARD_ISSUE_KEY = $TARGET_ISSUE_KEY - } - else { + } else { $INWARD_ISSUE_KEY = $TARGET_ISSUE_KEY $OUTWARD_ISSUE_KEY = $THIS_ISSUE.key } @@ -1531,13 +1691,11 @@ function Set-IssueLinkTypeByJQL { try { # First check if the new link type already exists New-JiraIssueLink -LINK_TYPE $NEW_LINK_TYPE_OR_NONE -INWARD_ISSUE_KEY $INWARD_ISSUE_KEY -OUTWARD_ISSUE_KEY $OUTWARD_ISSUE_KEY - } - catch { + } catch { Write-Debug ($_ | Select-Object -Property * -ExcludeProperty psobject | Out-String) Write-Error "Error updating field: $($_.Exception.Message)" } - } - else { + } else { Write-Debug "Issue Key: $($THIS_ISSUE.key) - Link Type Name: $($_.type.name), no new link type specified, just removing..." } # Write-Debug "New was created: $($NEW_LINK | ConvertTo-Json -Depth 10)" @@ -1546,8 +1704,7 @@ function Set-IssueLinkTypeByJQL { Write-Debug "Removing link: $($CURRNT_LINK_FULL.type.name) [$($CURRNT_LINK_FULL.id)] from $($CURRNT_LINK_FULL.inwardIssue.key) to $($CURRNT_LINK_FULL.outwardIssue.key)" try { Remove-JiraIssueLink -LINK_ID $($CURRNT_LINK_FULL.id) - } - catch { + } catch { Write-Debug ($_ | Select-Object -Property * -ExcludeProperty psobject | Out-String) Write-Error "Error updating field: $($_.Exception.Message)" } @@ -1568,7 +1725,7 @@ function Add-FormsFromJQLQueryResults { [switch]$InternalOnlyVisible = $false ) # /{issueIdOrKey}/form - $COMBINED_ISSUES_JSON = Get-JiraCloudJQLQueryResult -JQL_STRING $JQL_STRING -RETURN_FIELDS @('id', 'key') -ReturnJSONOnly -IncludeEmptyFields + $COMBINED_ISSUES_JSON = Get-JiraCloudJQLQueryResult -JQL_STRING $JQL_STRING -RETURN_FIELDS @('id', 'key') -ReturnJSONOnly $COMBINED_ISSUES = $COMBINED_ISSUES_JSON | ConvertFrom-Json Write-Debug "JQL Query results: $($COMBINED_ISSUES.Count)" $COMBINED_ISSUES | ForEach-Object { @@ -1585,8 +1742,7 @@ function Add-FormsFromJQLQueryResults { $PAYLOAD | ConvertTo-Json -Depth 10 | Write-Debug try { Invoke-RestMethod -Uri $ISSUE_FORM_ID_URL -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Post -Body ($PAYLOAD | ConvertTo-Json -Depth 10) -ContentType 'application/json' - } - catch { + } catch { Write-Debug "$($MyInvocation.InvocationName) - Failed to attach form ($FORM_ID) to issue: $($ISSUE.key)" Write-Debug ($_ | Select-Object -Property * -ExcludeProperty psobject | Out-String) Write-Error "Error updating field: $($_.Exception.Message)" @@ -1610,13 +1766,11 @@ function Set-AttachedFormsExternal { try { #https://api.atlassian.com/jira/forms/cloud/{cloudId}/issue/{issueIdOrKey}/form/{formId}/action/external' \ Invoke-RestMethod -Uri "$ISSUE_FORM_ATTACHMENTS_URL/$($ATTACHED_FORM.id)/action/external" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Put - } - catch { + } catch { Write-Debug ($_ | Select-Object -Property * -ExcludeProperty psobject | Out-String) Write-Error "Error updating field: $($_.Exception.Message)" } - } - else { + } else { Write-Debug "No form found for issue: $($ISSUE.key)" } } @@ -1627,7 +1781,7 @@ function Set-AttachedFormsExternalJQLQuery { [string]$JQL_STRING ) # /{issueIdOrKey}/form - $COMBINED_ISSUES_JSON = Get-JiraCloudJQLQueryResult -JQL_STRING $JQL_STRING -RETURN_FIELDS @('id', 'key') -ReturnJSONOnly -IncludeEmptyFields + $COMBINED_ISSUES_JSON = Get-JiraCloudJQLQueryResult -JQL_STRING $JQL_STRING -RETURN_FIELDS @('id', 'key') -ReturnJSONOnly $COMBINED_ISSUES = $COMBINED_ISSUES_JSON | ConvertFrom-Json Write-Debug "JQL Query results: $($COMBINED_ISSUES.Count)" $COMBINED_ISSUES | ForEach-Object { @@ -1674,6 +1828,19 @@ function Get-FormsForJiraIssue { $REST_RESULTS | ConvertTo-Json -Depth 10 | Write-Debug } +function Reset-FormsFromJQLQueryResults { + param ( + [Parameter(Mandatory = $true)] + [string]$JQL_STRING, + [Parameter(Mandatory = $true)] + [string]$FORM_ID + ) + Write-Debug "Expecting to reset form: $FORM_ID from JQL query results: $JQL_STRING" + # Get JQL query results + Remove-FormsFromJQLQueryResults -JQL_STRING $JQL_STRING +} + + # Function to remove forms from JQL query results function Remove-FormsFromJQLQueryResults { param ( @@ -1683,7 +1850,7 @@ function Remove-FormsFromJQLQueryResults { [switch]$DontReplace = $false ) # /{issueIdOrKey}/form - $COMBINED_ISSUES_JSON = Get-JiraCloudJQLQueryResult -JQL_STRING $JQL_STRING -RETURN_FIELDS @('id', 'key') -ReturnJSONOnly -IncludeEmptyFields + $COMBINED_ISSUES_JSON = Get-JiraCloudJQLQueryResult -JQL_STRING $JQL_STRING -RETURN_FIELDS @('id', 'key') -ReturnJSONOnly $COMBINED_ISSUES = $COMBINED_ISSUES_JSON | ConvertFrom-Json Write-Debug "JQL Query results: $($COMBINED_ISSUES.Count)" $COMBINED_ISSUES | ForEach-Object { @@ -1692,8 +1859,7 @@ function Remove-FormsFromJQLQueryResults { $ATTACHED_FORMS = Invoke-RestMethod -Uri "$ISSUE_FORM_ID_URL" -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get if ($null -eq $ATTACHED_FORMS -or $ATTACHED_FORMS -eq 0) { Write-Debug "No form found for issue: $($ISSUE.key)" - } - else { + } else { Write-Debug "Issue Key: $($ISSUE.key) - ATTACHED_FORMS to remove: " Write-Debug "ATTACHED FORMS COUNT: $($ATTACHED_FORMS.Count)" $ATTACHED_FORMS | ConvertTo-Json -Depth 10 | Write-Debug @@ -1713,12 +1879,10 @@ function Remove-FormsFromJQLQueryResults { Write-Debug "Re-attaching form ($FORM_TEMPLATE_ID) to issue: $($ISSUE.key)" Invoke-RestMethod -Uri $ISSUE_FORM_ID_URL -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Post -Body ($PAYLOAD | ConvertTo-Json -Depth 10) -ContentType 'application/json' Set-AttachedFormsExternal -ISSUE_KEY $($ISSUE.key) - } - else { + } else { Write-Debug "Not re-attaching form to issue: $($ISSUE.key)" } - } - catch { + } catch { Write-Debug ($_ | Select-Object -Property * -ExcludeProperty psobject | Out-String) Write-Error "Error updating field: $($_.Exception.Message)" } diff --git a/AtlassianPowerKit.psm1 b/AtlassianPowerKit.psm1 index 75c3d5d..6be5b33 100644 --- a/AtlassianPowerKit.psm1 +++ b/AtlassianPowerKit.psm1 @@ -29,8 +29,7 @@ function Get-RequisitePowerKitModules { Write-Debug "Module $_ not found. Installing..." Install-Module -Name $_ -Force -Scope CurrentUser | Write-Debug } - } - catch { + } catch { Write-Error "Module $_ not found and installation failed. Exiting." throw "Dependency module $_ unanable to install, try manual install, Exiting for now." } @@ -52,8 +51,7 @@ function Import-NestedModules { if (-not $PSD1_FILE) { Write-Error "Module $MODULE_NAME not found. Exiting." throw "Nested module $MODULE_NAME not found. Exiting." - } - elseif ($PSD1_FILE.Count -gt 1) { + } elseif ($PSD1_FILE.Count -gt 1) { Write-Error "Multiple module files found for $MODULE_NAME. Exiting." throw "Multiple module files found for $MODULE_NAME. Exiting." } @@ -77,8 +75,7 @@ function Confirm-OSMDirs { if (-not (Get-Item -Path $ENVAR_NAME -ErrorAction SilentlyContinue)) { if ($IsLinux) { $DIR_PATH = '/opt/osm' - } - else { + } else { $DIR_PATH = $(Get-ItemProperty -Path .).FullName } $SetEnvar = '$' + $ENVAR_NAME + ' = "' + $DIR_PATH + '"' @@ -91,8 +88,7 @@ function Confirm-OSMDirs { if (-not (Test-Path $EXPECTED_DIR)) { New-Item -ItemType Directory -Path $EXPECTED_DIR -Force | Write-Debug #Write-Debug "Directory created: $EXPECTED_DIR" - } - else { + } else { #Write-Debug "Good news, $ENVAR_NAME already set and directory already exists: $EXPECTED_DIR" } $ENVAR_NAME @@ -122,8 +118,7 @@ function Invoke-AtlassianPowerKitFunction { # Use Splatting (@) to pass parameters $RETURN_OBJECT = & $FunctionName @FunctionParameters - } - else { + } else { Write-Debug "Running function: $FunctionName without parameters" $RETURN_OBJECT = & $FunctionName } @@ -135,8 +130,7 @@ function Invoke-AtlassianPowerKitFunction { # Convert the returned object to JSON $RETURN_JSON = $RETURN_OBJECT Write-Debug "Returning JSON of size: $($RETURN_JSON.Length) characters" - } - catch { + } catch { Write-Debug "Error occurred while invoking function: $FunctionName" Write-Debug $_ $RETURN_JSON = "{'error': 'An error occurred while executing the function.', 'details': '$($_.Exception.Message)'}" @@ -164,8 +158,7 @@ function Update-OSMPKFunctionsMarkDownDoc { $MARKDOWN_FILE = "$env:OSM_HOME\AtlassianPowerKit\AtlassianPowerKit-Functions.md" if (-not (Test-Path $MARKDOWN_FILE)) { New-Item -ItemType File -Path $MARKDOWN_FILE -Force | Write-Debug - } - else { + } else { Clear-Content -Path $MARKDOWN_FILE | Write-Debug } $NESTED_MODULES | ForEach-Object { @@ -207,7 +200,8 @@ function Show-AtlassianPowerKitFunctions { ) $selectedFunction = $null # Remove AtlassianPowerKit-Shard and AtlassianPowerKit-UsersAndGroups from the nested modules - $NESTED_MODULES = $NESTED_MODULES | Where-Object { $_ -ne 'AtlassianPowerKit-UsersAndGroups' -and $_ -ne 'AtlassianPowerKit-Shared' } + #$NESTED_MODULES = $NESTED_MODULES | Where-Object { $_ -ne 'AtlassianPowerKit-UsersAndGroups' -and $_ -ne 'AtlassianPowerKit-Shared' } + $NESTED_MODULES = $NESTED_MODULES | Where-Object { $_ -ne 'AtlassianPowerKit-Shared' } # List nested modules and their exported functions to the console in a readable format, grouped by module $colors = @('Green', 'Cyan', 'Red', 'Magenta', 'Yellow', 'Blue', 'Gray') $colorIndex = 0 @@ -260,16 +254,14 @@ function Show-AtlassianPowerKitFunctions { if ($selectedFunction -match '^\d+$') { Write-Debug "Selected function by num: $selectedFunction" $SelectedFunctionName = ($functionReferences[[int]$selectedFunction]) - } - elseif ($selectedFunction -match '^(?i)[a-z]*-[a-z]*$') { + } elseif ($selectedFunction -match '^(?i)[a-z]*-[a-z]*$') { # Test if the function exists $selectedFunction = $selectedFunction Write-Debug "Selected function by name: $selectedFunction" #Write-Debug "Function references: $($functionReferences.GetType())" if ($functionReferences.Contains($selectedFunction)) { $SelectedFunctionName = $selectedFunction - } - else { + } else { Write-Error "Function $selectedFunction does not exist in the function references." } } @@ -313,16 +305,14 @@ function Import-AtlassianPowerKitProfile { if ($NoVault) { #Write-Debug "$($MyInvocation.InvocationName) -NoVault flag set, attempting to load profile from environment variables" $ENVAR_ARRAY = Set-AtlassianPowerKitProfile -NoVault - } - elseif ($selectedProfile -ne $false) { + } elseif ($selectedProfile -ne $false) { #Write-Debug "$($MyInvocation.InvocationName) -ProfileName profided, attempting to load profile: $selectedProfile from the vault" $ENVAR_ARRAY = Set-AtlassianPowerKitProfile -ProfileName $selectedProfile if (!$ENVAR_ARRAY -or $ENVAR_ARRAY.Count -lt 3) { Write-Host "Could not load profile: $selectedProfile from the vault. Requesting values to add it to vault." $ENVAR_ARRAY = New-AtlassianPowerKitProfile -PROFILE_NAME $selectedProfile } - } - else { + } else { #Write-Debug "$($MyInvocation.InvocationName) -NoVault flag not set and no profile selected, checking for existing profiles in the vault" $VAULT_PROFILES = Get-AtlassianPowerKitProfileList if ($VAULT_PROFILES) { @@ -330,16 +320,14 @@ function Import-AtlassianPowerKitProfile { $selectedProfile = $VAULT_PROFILES[0] Write-Debug "Only one profile found in the vault, selecting $selectedProfile" $ENVAR_ARRAY = Set-AtlassianPowerKitProfile -ProfileName $selectedProfile - } - else { + } else { Write-Output 'Multiple profiles found in the vault but no profile provided, please use the -APK_Profile parameter to specify the desired profile' foreach ($APK_PROFILENAME in $VAULT_PROFILES) { Write-Output " AtlassianPowerkit -APK_PROFILENAME $APK_PROFILENAME" } Throw 'Ambiguous profile state' } - } - else { + } else { Write-Output 'No profiles found in the vault, please create a new profile.' $ENVAR_ARRAY = New-AtlassianPowerKitProfile } @@ -389,47 +377,38 @@ function AtlassianPowerKit { Write-Debug '-ResetVault flagged Clearing the AtlassianPowerKit vault, ignoring any other parameters' Clear-AtlassianPowerKitVault | Write-Debug return $true - } - elseif ($ListProfiles) { + } elseif ($ListProfiles) { Write-Debug '$ListProfiles flaged, Listing AtlassianPowerKit profiles, ignoring any other parameters' $PROFILE_LIST = Get-AtlassianPowerKitProfileList return $PROFILE_LIST - } - elseif ($ArchiveProfileDirs) { + } elseif ($ArchiveProfileDirs) { Write-Debug '-ArchiveProfileDirs flagged, Clearing the AtlassianPowerKit profile directories, ignoring any other parameters' Clear-AtlassianPowerKitProfileDirs | Write-Debug return $true - } - elseif ($ClearProfile) { + } elseif ($ClearProfile) { Write-Debug '-ClearProfile flagged, Clearing the AtlassianPowerKit profile, ignoring any other parameters' Clear-AtlassianPowerKitProfile | Write-Debug return $true - } - elseif ($RemoveVaultProfile -ne $false) { + } elseif ($RemoveVaultProfile -ne $false) { Write-Debug '-RemoveVaultProfile flagged, Removing the AtlassianPowerKit profile from the vault, ignoring any other parameters' Remove-AtlassianPowerKitProfile -ProfileName $RemoveVaultProfile | Write-Debug return $true - } - elseif ($DocFunctions) { + } elseif ($DocFunctions) { Write-Debug '-DocFunctions flagged, Creating a markdown document of all AtlassianPowerKit functions, ignoring any other parameters' Update-OSMPKFunctionsMarkDownDoc -NESTED_MODULES $NESTED_MODULES return $true - } - elseif ($NewVaultProfile) { + } elseif ($NewVaultProfile) { Write-Debug '-NewVaultProfile flagged, Creating a new AtlassianPowerKit profile in the vault, ignoring any other parameters' New-AtlassianPowerKitProfile | Write-Debug return $true - } - elseif ($NoVault) { + } elseif ($NoVault) { Write-Debug '-NoVault flagged, attempting to load profile from environment variables' $PROFILE_ARRAY = Import-AtlassianPowerKitProfile -NoVault - } - elseif ($APK_Profile) { + } elseif ($APK_Profile) { Write-Debug "Profile provided: $APK_Profile" $ProfileName = $APK_Profile.Trim().ToLower() $PROFILE_ARRAY = Import-AtlassianPowerKitProfile -selectedProfile $ProfileName - } - else { + } else { Write-Debug 'No profile provided, checking if vault has only 1 profile' $PROFILE_ARRAY = Import-AtlassianPowerKitProfile } @@ -454,14 +433,12 @@ function AtlassianPowerKit { } $RET_VAL = Invoke-AtlassianPowerKitFunction -FunctionName $FunctionName -FunctionParameters $FunctionParameters Write-Debug "AtlassianPowerKit Main: Received JSON of size: $($RETURN_JSON.Length) characters" - } - elseif ($FunctionName) { + } elseif ($FunctionName) { Write-Debug "$($MyInvocation.InvocationName) attempting to run function: $FunctionName without parameters - most functions will handle this by requesting user input" $RET_VAL = Invoke-AtlassianPowerKitFunction -FunctionName $FunctionName } - } - catch { + } catch { # Write call stack and sub-function error messages to the debug output Write-Debug "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ $((Get-Item -Path $PSScriptRoot).FullName) $($MyInvocation.InvocationName) FAILED: " # Write full call stack to the debug output and error message to the console @@ -471,8 +448,7 @@ function AtlassianPowerKit { } if (!$RET_VAL) { Write-Output 'Nothing to return, have a nice day.' - } - else { + } else { return $RET_VAL } } \ No newline at end of file From 63876d469601f4474bc5a08fee6e7ef51dca2041 Mon Sep 17 00:00:00 2001 From: Mark Culhane Date: Fri, 11 Apr 2025 08:02:37 +1000 Subject: [PATCH 09/11] pre-docker final - tested --- .../AtlassianPowerKit-Confluence.psm1 | 55 +++++-------- .../AtlassianPowerKit-Jira.psm1 | 49 +++++++++++- .../AtlassianPowerKit-Shared.psm1 | 77 +++++++------------ .../AtlassianPowerKit-UsersAndGroups.psm1 | 53 ++++++------- AtlassianPowerKit.psm1 | 1 + 5 files changed, 116 insertions(+), 119 deletions(-) diff --git a/AtlassianPowerKit-Confluence/AtlassianPowerKit-Confluence.psm1 b/AtlassianPowerKit-Confluence/AtlassianPowerKit-Confluence.psm1 index 4d00045..86239f0 100644 --- a/AtlassianPowerKit-Confluence/AtlassianPowerKit-Confluence.psm1 +++ b/AtlassianPowerKit-Confluence/AtlassianPowerKit-Confluence.psm1 @@ -44,20 +44,17 @@ function Export-ConfluencePage { #$REST_RESULTS | Get-Member -MemberType Properties -Force | ForEach-Object { # Write-Debug "Property: $($_.Name), Value: $($REST_RESULTS.$($_.Name))" #} - } - catch { + } catch { Write-Error "Error exporting page format: $CONFLUENCE_PAGE_FORMAT for page ID: $CONFLUENCE_PAGE_ID" } $CONFLUENCE_PAGE_DIR = "$($env:OSM_HOME)\$($env:AtlassianPowerKit_PROFILE_NAME)\CONFLUENCE" if ($CONFLUENCE_PAGE_FORMAT -eq 'atlas_doc_format') { $CONFLUENCE_PAGE_CONTENT = $REST_RESULTS.body.atlas_doc_format.value $FILE_EXTENSION = 'json' - } - elseif ($CONFLUENCE_PAGE_FORMAT -eq 'storage') { + } elseif ($CONFLUENCE_PAGE_FORMAT -eq 'storage') { $CONFLUENCE_PAGE_CONTENT = $REST_RESULTS.body.storage.value $FILE_EXTENSION = 'xml' - } - else { + } else { $CONFLUENCE_PAGE_CONTENT = $REST_RESULTS | ConvertTo-Json -Depth 100 $FILE_EXTENSION = 'json' } @@ -85,8 +82,8 @@ function Export-ConfluencePageAllChildren { [string]$CONFLUENCE_SPACE_KEY, [Parameter(Mandatory = $true)] [int64]$CONFLUENCE_PAGE_ID, - [Parameter(Mandatory = $true)] - [string]$CONFLUENCE_PAGE_FORMAT, + [Parameter(Mandatory = $false)] + [string]$CONFLUENCE_PAGE_FORMAT = 'atlas_doc_format', [Parameter(Mandatory = $false)] [int]$DepthLimit = 10, [Parameter(Mandatory = $false)] @@ -146,8 +143,7 @@ function Export-ConfluencePageWord { $response = Invoke-WebRequest -Uri $CONFLUENCE_PAGE_ENDPOINT -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get # Save the content directly to the file [System.IO.File]::WriteAllBytes($FILE_NAME, $response.Content) - } - catch { + } catch { Write-Error "Exception.Message: $($_.Exception.Message)" } $FILE_STRING = "$($directoryPath.FullName)\$CONFLUENCE_PAGE_TITLE.doc" @@ -208,12 +204,10 @@ function Export-ConfluencePageWord { $templateDoc.SaveAs("$directoryPath\$CONFLUENCE_PAGE_TITLE-Templated.pdf", 17) $sourceDoc.Close() $templateDoc.Close() - } - catch { + } catch { Write-Debug 'AtlassianPowerKit-Confluence.psm1:Export-ConfluencePageWord - Errored!' Write-Error "Exception.Message: $($_.Exception.Message)" - } - finally { + } finally { $wordApp.Quit() $wordApp2.Quit() } @@ -259,8 +253,7 @@ function Export-ConfluencePageStorageFormat { #Write-Debug "Rest Result Fields, Recursive: $($REST_RESULTS | Get-Member -MemberType Properties -Force)" #Write-Debug (ConvertTo-Json $REST_RESULTS -Depth 10) Write-Debug '++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++' - } - catch { + } catch { $functionName = (Get-PSCallStack)[0].FunctionName Write-Debug "$functionName errored: $($_.Exception.Message)" Write-Error "$functionName errored: $($_.Exception.Message)" @@ -331,8 +324,7 @@ function Get-ConfluencePageByID { Write-Debug "Confluence Page Endpoint: $CONFLUENCE_PAGE_ENDPOINT" try { $REST_RESULTS = Invoke-RestMethod -Uri $CONFLUENCE_PAGE_ENDPOINT -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get - } - catch { + } catch { Write-Debug ($_ | Select-Object -Property * -ExcludeProperty psobject | Out-String) Write-Error "Get-ConfluencePageByID: $($_.Exception.Message)" } @@ -359,8 +351,7 @@ function Get-ConfluencePageByTitle { $REST_RESULTS = Invoke-RestMethod -Uri $CONFLUENCE_PAGE_ENDPOINT -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get #Write-Debug $REST_RESULTS.getType() Write-Debug (ConvertTo-Json $REST_RESULTS -Depth 10) - } - catch { + } catch { Write-Debug ($_ | Select-Object -Property * -ExcludeProperty psobject | Out-String) Write-Error "Error updating field: $($_.Exception.Message)" } @@ -373,8 +364,7 @@ function Get-ConfluenceSpaceList { $CONFLUENCE_SPACES_ENDPOINT = "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/wiki/api/v2/spaces" try { $REST_RESULTS = Invoke-RestMethod -Uri $CONFLUENCE_SPACES_ENDPOINT -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get -ContentType 'application/json' - } - catch { + } catch { Write-Debug 'StatusCode:' $_.Exception.Response.StatusCode.value__ Write-Debug 'StatusDescription:' $_.Exception.Response.StatusDescription } @@ -395,8 +385,7 @@ function Get-ConfluenceSpacePropertiesBySpaceID { $REST_RESULTS = Invoke-RestMethod -Uri $CONFLUENCE_SPACE_ENDPOINT -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get -ContentType 'application/json' Write-Debug $REST_RESULTS.getType() Write-Debug (ConvertTo-Json $REST_RESULTS -Depth 10) - } - catch { + } catch { Write-Debug 'StatusCode:' $_.Exception.Response.StatusCode.value__ Write-Debug 'StatusDescription:' $_.Exception.Response.StatusDescription } @@ -416,8 +405,7 @@ function Get-ConfluenceChildPages { $REST_RESULTS = Invoke-RestMethod -Uri $GET_CHILD_PAGE_ENDPOINT -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get Write-Debug $REST_RESULTS.getType() Write-Debug (ConvertTo-Json $REST_RESULTS -Depth 10) - } - catch { + } catch { Write-Debug 'StatusCode:' $_.Exception.Response.StatusCode.value__ Write-Debug 'StatusDescription:' $_.Exception.Response.StatusDescription } @@ -437,16 +425,14 @@ function Remove-AttachmentsFromConfPage { $REST_RESULTS = Invoke-RestMethod -Uri $CONFLUENCE_PAGE_ATTACHMENTS_ENDPOINT -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Get Write-Debug $REST_RESULTS.getType() Write-Debug (ConvertTo-Json $REST_RESULTS -Depth 10) - } - catch { + } catch { Write-Debug 'StatusCode:' $_.Exception.Response.StatusCode.value__ Write-Debug 'StatusDescription:' $_.Exception.Response.StatusDescription } $REST_RESULTS.results | ForEach-Object { if ($EXCLUDE_ATTACHMENT_NAMES -contains $_.title) { Write-Debug "Excluding attachment: $($_.title)" - } - else { + } else { $CONFLUENCE_PAGE_ATTACHMENT_DELETE_ENDPOINT = "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/wiki/api/v2/attachments/$($_.id)" Write-Debug "Deleting attachment: $($_.title)" Invoke-RestMethod -Uri $CONFLUENCE_PAGE_ATTACHMENT_DELETE_ENDPOINT -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Delete @@ -504,8 +490,7 @@ function Set-AttachmentForConfluencePage { Write-Debug 'Attachment uploaded successfully.' Write-Debug (ConvertTo-Json $REST_RESULTS -Depth 10) return $REST_RESULTS | ConvertTo-Json - } - catch { + } catch { # Handle exceptions Write-Error "Failed to upload attachment: $($_.Exception.Message)" if ($_.Exception.Response) { @@ -536,8 +521,7 @@ function Set-ConfluenceSpacePropertyByID { $REST_RESULTS = Invoke-RestMethod -Uri $CONFLUENCE_SPACE_ENDPOINT -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method Put -ContentType 'application/json' -Body $CONFLUENCE_SPACE_PROPERTIES Write-Debug $REST_RESULTS.getType() Write-Debug (ConvertTo-Json $REST_RESULTS -Depth 10) - } - catch { + } catch { Write-Debug 'StatusCode:' $_.Exception.Response.StatusCode.value__ Write-Debug 'StatusDescription:' $_.Exception.Response.StatusDescription } @@ -581,8 +565,7 @@ function Set-ConfluenceYearMonthStructure { $CONFLUENCE_PAGE_MONTH_PAGE = New-ConfluencePage -CONFLUENCE_SPACE_KEY $CONFLUENCE_SPACE_KEY -CONFLUENCE_PAGE_TITLE $CONFLUENCE_PAGE_MONTH_TITLE # Copy content from parent page Set-ConfluencePageContent -CONFLUENCE_SPACE_KEY $CONFLUENCE_SPACE_KEY -CONFLUENCE_PAGE_ID $CONFLUENCE_PAGE_MONTH_PAGE.id -CONFLUENCE_PAGE_STORAGE_FILE $TEMP_FILE.FullName - } - else { + } else { Write-Debug "Page already exists: $CONFLUENCE_PAGE_MONTH_TITLE" } } finally { diff --git a/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psm1 b/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psm1 index 2b289b1..0ab12ab 100644 --- a/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psm1 +++ b/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psm1 @@ -88,7 +88,6 @@ $RETRY_AFTER = 10 # return $TABLE_ROW #} - function ConvertTo-JSONMarkdownList { param ( [Parameter(Mandatory = $true)] @@ -873,6 +872,27 @@ function Set-JiraIssueField { return $UPDATE_ISSUE_RESPONSE } + +function Set-StatementOfApplicabilityRefs { + param ( + [Parameter(Mandatory = $true)] + [string]$JQL_STRING, + [Parameter(Mandatory = $true)] + [hashtable[]]$REF_MAP_HASHTABLE_ARRAY + ) + # enssure that the hashtable array has at least one element and all elements are hashtable with 4 keys: FIELD_ID, FIELD_NAME, LINK_TYPE, LINK_DIRECTION + + + + $ISSUES = Get-JiraCloudJQLQueryResult -JQL_STRING $JQL_STRING + $ISSUES.issues | ForEach-Object { + $ISSUE = $_ + $ISSUE_KEY = $ISSUE.key + Write-Debug "Updating fields for issue: $($_.key)" + Set-JiraIssueField -ISSUE_KEY $ISSUE_KEY -Field_Ref $FIELD_REF -New_Value $NEW_VALUE -FIELD_TYPE $FIELD_TYPE + } +} + # Function to set-jiraissuefield for a Jira issue field for all issues in JQL query results gibven the JQL query string, field name, and new value function Set-JiraIssueFieldForJQLQueryResults { param ( @@ -904,6 +924,32 @@ function Set-JiraIssueFieldForJQLQueryResults { } } +function Set-OSMPortalLinkForJQLQueryResults { + param ( + [Parameter(Mandatory = $false)] + [string]$JQL_STRING = 'project = GRCosm and issueType not in subTaskIssueTypes()', + [Parameter(Mandatory = $true)] + [string]$FIELD_REF, + [Parameter(Mandatory = $false)] + [string]$FIELD_TYPE = 'text', + [Parameter(Mandatory = $false)] + [string[]]$NEW_VALUE = @('

https://auda.atlassian.net/servicedesk/customer/portal/7/', '

'), + [Parameter(Mandatory = $false)] + [Switch]$DryRun = $false + ) + $ISSUES = Get-JiraCloudJQLQueryResult -JQL_STRING $JQL_STRING + $ISSUES.issues | ForEach-Object { + $ISSUE = $_ + $ISSUE_KEY = $ISSUE.key + Write-Debug "Updating fields for issue: $($_.key)" + if (! $DryRun) { + Set-JiraIssueField -ISSUE_KEY $ISSUE_KEY -Field_Ref $FIELD_REF -New_Value $NEW_VALUE -FIELD_TYPE $FIELD_TYPE + } else { + Write-Debug "Dry Run: Set-JiraIssueField -ISSUE_KEY $ISSUE_KEY -Field_Ref $FIELD_REF -New_Value $NEW_VALUE" + } + } +} + function Set-OSMRelationFieldBulkSQL { param ( [Parameter(Mandatory = $true)] @@ -1840,7 +1886,6 @@ function Reset-FormsFromJQLQueryResults { Remove-FormsFromJQLQueryResults -JQL_STRING $JQL_STRING } - # Function to remove forms from JQL query results function Remove-FormsFromJQLQueryResults { param ( diff --git a/AtlassianPowerKit-Shared/AtlassianPowerKit-Shared.psm1 b/AtlassianPowerKit-Shared/AtlassianPowerKit-Shared.psm1 index 9ad3da3..0d384a6 100644 --- a/AtlassianPowerKit-Shared/AtlassianPowerKit-Shared.psm1 +++ b/AtlassianPowerKit-Shared/AtlassianPowerKit-Shared.psm1 @@ -9,7 +9,7 @@ - Get-AtlassianAPIEndpoint - Get-OpsgenieAPIEndpoint - Clear-AtlassianPowerKitGlobalVariables - - To list all functions in this module, run: Get-Command -Module AtlassianPowerKit-Shared + - To list all functions in this module, run: `Get-Command -Module AtlassianPowerKit-Shared` - Debug output is enabled by default. To disable, set $DisableDebug = $true before running functions. .EXAMPLE @@ -64,8 +64,7 @@ function Clear-AtlassianPowerKitProfileDirs { if ($itemsToArchive.Count -eq 0) { Write-Debug "Profile directory $dir. FullName has nothing to archive. Skipping..." - } - else { + } else { # Archiving items Compress-Archive -Path $itemsToArchive.FullName -DestinationPath $ARCHIVE_PATH -Force Write-Debug "Archiving $($dir.BaseName) to $ARCHIVE_NAME in $($dir.FullName)...." @@ -120,20 +119,17 @@ function Get-PaginatedJSONResults { try { if ($METHOD -eq 'POST') { $PAGE_RESULTS = Invoke-RestMethod -Uri $URI -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method $METHOD -Body $ONE_POST_BODY -ContentType 'application/json' - } - else { + } else { $PAGE_RESULTS = Invoke-RestMethod -Uri $URI -Headers $(ConvertFrom-Json -AsHashtable $env:AtlassianPowerKit_AtlassianAPIHeaders) -Method $METHOD -ContentType 'application/json' #$PAGE_RESULTS | ConvertTo-Json -Depth 100 | Write-Debug } - } - catch { + } catch { # Catch 429 errors and wait for the retry-after time if ($_.Exception.Response.StatusCode -eq 429) { Write-Warn "429 error, waiting for $RETRY_AFTER seconds..." Start-Sleep -Seconds $RETRY_AFTER Get-PageResult -URI $URI -ONE_POST_BODY $ONE_POST_BODY - } - else { + } else { Write-Error "Error: $($_.Exception.Message)" throw 'Get-PageResult failed' } @@ -148,17 +144,14 @@ function Get-PaginatedJSONResults { $ONE_POST_BODY = $ONE_POST_BODY | ConvertFrom-Json $ONE_POST_BODY.nextPageToken = $PAGE_RESULTS.nextPageToken #$ONE_POST_BODY = $ONE_POST_BODY | ConvertTo-Json - } - else { + } else { $URI = $URI + "&nextPageToken=$($PAGE_RESULTS.nextPageToken)" } - } - elseif ($PAGE_RESULTS.nextPage) { + } elseif ($PAGE_RESULTS.nextPage) { Write-Debug "Next page: $($PAGE_RESULTS.nextPage)" if ($METHOD -eq 'POST') { Write-Error "$($MyInvocation.InvocationName) does not support POST method with nextPage. Exiting..." - } - else { + } else { $URI = $PAGE_RESULTS.nextPage } } @@ -171,8 +164,7 @@ function Get-PaginatedJSONResults { } if ($POST_BODY) { $RESULTS_ARRAY = Get-PageResult -URI $URI -METHOD $METHOD -ONE_POST_BODY $POST_BODY - } - else { + } else { $RESULTS_ARRAY = Get-PageResult -URI $URI -METHOD $METHOD } Write-Debug "$($MyInvocation.InvocationName) results:" @@ -187,8 +179,7 @@ function Get-AtlassianPowerKitProfileList { Register-AtlassianPowerKitVault Write-Debug "$($MyInvocation.InvocationName) vault registered successfully." $PROFILE_LIST = @() - } - else { + } else { #Write-Debug 'Vault already registered, getting profiles...' unlock-vault -VaultName $VAULT_NAME | Write-Debug $PROFILE_LIST = (Get-SecretInfo -Vault $VAULT_NAME -Name '*').Name @@ -251,8 +242,7 @@ function Unlock-Vault { throw 'Unlock-Vault failed. Exiting.' } Unlock-SecretStore -Password $VAULT_KEY | Write-Debug - } - catch { + } catch { # If an error is thrown, the vault is locked. Write-Debug "Unlock-Vault failed: $_ ..." throw 'Unlock-Vault failed Exiting' @@ -274,8 +264,7 @@ function Update-AtlassianPowerKitVault { Unlock-Vault -VaultName $VAULT_NAME | Write-Debug try { Set-Secret -Name $ProfileName -Secret $ProfileData -Vault $VAULT_NAME | Write-Debug - } - catch { + } catch { Write-Debug "Update of vault failed for $ProfileName." throw "Update of vault failed for $ProfileName." } @@ -312,8 +301,7 @@ function Register-AtlassianPowerKitVault { } if (Get-SecretVault -Name $VAULT_NAME -ErrorAction SilentlyContinue) { Write-Debug "Vault $VAULT_NAME already exists." - } - else { + } else { Write-Debug "Registering vault $VAULT_NAME..." $VAULT_KEY = Get-VaultKey $storeConfiguration = @{ @@ -337,8 +325,7 @@ function Register-AtlassianPowerKitVault { try { $VAULT_KEY = Get-VaultKey Unlock-Vault -VaultName $VAULT_NAME | Write-Debug - } - catch { + } catch { Write-Debug "Failed to unlock vault $VAULT_NAME. Please check the vault key file." Write-Debug "De-registering vault $VAULT_NAME... and resetting vault key file." Unregister-SecretVault -Name $VAULT_NAME | Write-Debug @@ -349,8 +336,7 @@ function Register-AtlassianPowerKitVault { if ($ATTEMPT -gt 5) { Write-Debug "$($MyInvocation.InvocationName) failed after $ATTEMPT attempts. Exiting..." throw "$($MyInvocation.InvocationName) failed!" - } - else { + } else { Register-AtlassianPowerKitVault -ATTEMPT ($ATTEMPT + 1) } Return $true @@ -368,8 +354,7 @@ function Register-AtlassianPowerKitProfileInVault { try { Register-AtlassianPowerKitVault | Write-Debug - } - catch { + } catch { Write-Debug "$($MyInvocation.InvocationName) failed to register vault. Exiting" throw "$($MyInvocation.InvocationName) failed to register vault. Exiting" } @@ -379,8 +364,7 @@ function Register-AtlassianPowerKitProfileInVault { if ($null -ne $VAULT_PROFILES_LIST -and $VAULT_PROFILES_LIST.Count -gt 0 -and $VAULT_PROFILES_LIST.Contains($ProfileName)) { Write-Debug "$($MyInvocation.InvocationName) Profile $ProfileName already exists in the vault. You must run AtlssianPowerKit -RemoveVaultProfile $ProfileName or AtlssianPowerKit -ResetVault to remove it first." throw "$($MyInvocation.InvocationName) Profile $ProfileName already exists in the vault. You must run AtlssianPowerKit -RemoveVaultProfile $ProfileName or AtlssianPowerKit -ResetVault to remove it first." - } - else { + } else { #Write-Debug "Profile $ProfileName does not exist. Creating..." Write-Debug "Preparing profile data for $ProfileName..." $CredPair = "$($AtlassianAPICredentialPair.UserName):$($AtlassianAPICredentialPair.GetNetworkCredential().password)" @@ -411,8 +395,7 @@ function Test-VaultProfileLoaded { Write-Debug "Profile is missing required environment variable: $envVarNameKey" $PROFILE_LOADED = $false break - } - else { + } else { #Write-Debug "Found required environment variable: $envVarNameKey already set." $ENV_STATE = $null } @@ -424,8 +407,7 @@ function Set-AtlassianAPIHeaders { if (!$(Test-VaultProfileLoaded)) { Write-Debug "$($MyInvocation.InvocationName) failed. Profile not loaded. Exiting..." throw "$($MyInvocation.InvocationName) failed. Profile not loaded. Exiting..." - } - else { + } else { $HEADERS = @{ Authorization = "Basic $($env:AtlassianPowerKit_AtlassianAPIAuthString)" Accept = 'application/json' @@ -445,8 +427,7 @@ function Set-AtlassianPowerKitProfileFromVault { if ($SKIP_LOAD) { Get-Item -Path "env:$ENVAR_PREFIX*" | Write-Debug Write-Debug "$($MyInvocation.InvocationName) profile already loaded. Skipping vault load..." - } - else { + } else { Write-Debug "Profile $SelectedProfileName not loaded. Loading from vault..." # Load all profiles from the secret vault if (!$(Get-SecretVault -Name $VAULT_NAME -ErrorAction SilentlyContinue)) { @@ -460,8 +441,7 @@ function Set-AtlassianPowerKitProfileFromVault { $PROFILE_LIST | ConvertTo-Json -Depth 100 | Write-Debug Write-Error "$($MyInvocation.InvocationName) failed. Profile $SelectedProfileName not found in the vault. Exiting..." Throw "$($MyInvocation.InvocationName) failed. Profile $SelectedProfileName not found in the vault. Exiting..." - } - else { + } else { Write-Debug "Profile $SelectedProfileName exists in the vault, loading..." try { # if vault is locked, unlock it @@ -481,8 +461,7 @@ function Set-AtlassianPowerKitProfileFromVault { Get-Item -Path "env:$VAR_NAME" | Write-Debug } } - } - catch { + } catch { Write-Debug "Failed to load profile $SelectedProfileName. Please check the vault key file." throw "Failed to load profile $SelectedProfileName. Please check the vault key file." } @@ -506,8 +485,7 @@ function Test-AtlassianPowerKitProfile { $REST_RESPONSE = Invoke-RestMethod -Method Get -Uri $TEST_ENDPOINT -Headers $HEADERS -StatusCodeVariable REST_STATUS #Write-Debug "Results: $($REST_RESULTS | ConvertTo-Json -Depth 10) ..." Write-Debug "$($MyInvocation.InvocationName) returned status code: $($REST_STATUS)" - } - catch { + } catch { Write-Debug "$($MyInvocation.InvocationName) failed: with $_" Write-Debug 'Rest response: ' $REST_RESPONSE | ConvertTo-Json -Depth 10 | Write-Debug @@ -543,8 +521,7 @@ function Set-AtlassianPowerKitProfile { if (!$requiredEnvVar) { Write-Error "Required environment variable $envVarNameKey not found. Exiting..." throw "Required environment variable $envVarNameKey not found. Exiting..." - } - else { + } else { Write-Debug "Required environment variable found: $($requiredEnvVar.Name) = $(($requiredEnvVar.Value).substring(0, [System.Math]::Min(20, $requiredEnvVar.Value.Length)))..." # if the environment variable is AtlassianAPIEndpoint, use the prefix as the profile name (setting as environment variable: AtlassianPowerKit_PROFILE_NAME) if ($requiredEnvVar.Name -eq 'AtlassianPowerKit_AtlassianAPIEndpoint') { @@ -556,12 +533,10 @@ function Set-AtlassianPowerKitProfile { } } } - } - elseif ($ProfileName -ne $false) { + } elseif ($ProfileName -ne $false) { $VAULT_LOADED_ARRAY = Set-AtlassianPowerKitProfileFromVault -SelectedProfileName $ProfileName $VAULT_LOADED_ARRAY | ForEach-Object { Write-Output "$($_.Name) = $($_.Value)" | Out-Null } - } - else { + } else { Write-Error 'ProfileName is required. Exiting...' throw 'ProfileName is required. Exiting...' } diff --git a/AtlassianPowerKit-UsersAndGroups/AtlassianPowerKit-UsersAndGroups.psm1 b/AtlassianPowerKit-UsersAndGroups/AtlassianPowerKit-UsersAndGroups.psm1 index 21ef748..8816dc0 100644 --- a/AtlassianPowerKit-UsersAndGroups/AtlassianPowerKit-UsersAndGroups.psm1 +++ b/AtlassianPowerKit-UsersAndGroups/AtlassianPowerKit-UsersAndGroups.psm1 @@ -1,32 +1,32 @@ <# .SYNOPSIS - Atlassian Cloud PowerShell Module - Users and Groups - for handy functions to interact with Attlassian Cloud APIs. +Atlassian Cloud PowerShell Module - Users and Groups - for handy functions to interact with Attlassian Cloud APIs. .DESCRIPTION - Atlassian Cloud PowerShell Module - Users and Groups - - Dependencies: AtlassianPowerKit-Shared - - New-AtlassianAPIEndpoint - - Users and Groups Module Functions - - Get-AtlassianGroupMembers - - Get-AtlassianUser - - Show-JiraCloudJSMProjectRole - - To list all functions in this module, run: Get-Command -Module AtlassianPowerKit-UsersAndGroups - - Debug output is enabled by default. To disable, set $DisableDebug = $true before running functions. +Atlassian Cloud PowerShell Module - Users and Groups +- Dependencies: AtlassianPowerKit-Shared +- New-AtlassianAPIEndpoint +- Users and Groups Module Functions +- Get-AtlassianGroupMembers +- Get-AtlassianUser +- Show-JiraCloudJSMProjectRole +- To list all functions in this module, run: `Get-Command` -Module AtlassianPowerKit-UsersAndGroups +- Debug output is enabled by default. To disable, set $DisableDebug = $true before running functions. .EXAMPLE - Get-AtlassianGroupMembers -GROUP_NAME 'jira-administrators' +Get-AtlassianGroupMembers -GROUP_NAME 'jira-administrators' - This example gets all members of the 'jira-administrators' group. +This example gets all members of the 'jira-administrators' group. .EXAMPLE - Get-AtlassianUser -ACCOUNT_ID '5f7b7f7d7f7f7f7f7f7f7f7f7' +Get-AtlassianUser -ACCOUNT_ID '5f7b7f7d7f7f7f7f7f7f7f7f7' - This example gets the user details for the account ID '5f7b7f7d7f7f7f7f7f7f7f7f7'. +This example gets the user details for the account ID '5f7b7f7d7f7f7f7f7f7f7f7f7'. .EXAMPLE - Show-JiraCloudJSMProjectRole -JiraCloudJSMProjectKey 'OSM' +Show-JiraCloudJSMProjectRole -JiraCloudJSMProjectKey 'OSM' - This example gets all roles for the Jira Service Management (JSM) project with the key 'OSM'. +This example gets all roles for the Jira Service Management (JSM) project with the key 'OSM'. .LINK @@ -45,8 +45,7 @@ function Get-AtlassianGroupMembersBulk { $MEMBER_ENTRY_ARRAY = Get-AtlassianGroupMembers -GROUP_NAME $_.Key if ((!$MEMBER_ENTRY_ARRAY) -or $MEMBER_ENTRY_ARRAY.Count -eq 0) { Write-Output "No members found in group $($_.Key)" - } - else { + } else { Write-Debug "MEMBER_ENTRY_ARRAY TYPE = $($MEMBER_ENTRY_ARRAY.getType()), COUNT = $($MEMBER_ENTRY_ARRAY.Count)" $MEMBERS_LIST.add($_.Key, $MEMBER_ENTRY_ARRAY) } @@ -85,8 +84,7 @@ function Get-AtlassianGroups { try { $REST_RESULTS = Invoke-RestMethod -Uri $GROUPS_ENDPOINT -Headers $GROUP_ENDPOINT_HEADERS -Method Get -ContentType 'application/json' #Write-Debug $REST_RESULTS.getType() - } - catch { + } catch { Write-Debug 'StatusCode:' $_.Exception.Response.StatusCode.value__ Write-Debug 'StatusDescription:' $_.Exception.Response.StatusDescription } @@ -112,8 +110,7 @@ function Get-AtlassianGroupMembers { Write-Debug "Group Members Endpoint: $GROUP_MEMBERS_ENDPOINT" try { $REST_RESULTS = Invoke-RestMethod -Uri $GROUP_MEMBERS_ENDPOINT -Headers $HEADERS -Method Get -ContentType 'application/json' - } - catch { + } catch { # If rate limiting, sleep for 20 seconds then retry if ($_.Exception.Response.StatusCode.value__ -eq 429) { Write-Output 'Rate limited. Sleeping for 20 seconds then retrying.' @@ -129,8 +126,7 @@ function Get-AtlassianGroupMembers { # Build an array of hashtables with the values, handle null values and no members if ($REST_RESULTS.total -eq 0) { Write-Output "No members found in group $GROUP_NAME" - } - else { + } else { Write-Debug "REST_RESULTS TYPE = $($REST_RESULTS.getType())" Write-Debug "REST_RESULTS COUNT = $($REST_RESULTS.Count)" # Build an array of hashtables with the values handle null values @@ -155,8 +151,7 @@ function Get-AllAtlassianUsers { Write-Debug "Headers: $HEADERS" try { $REST_RESULTS = Invoke-RestMethod -Uri $USERS_ENDPOINT -Headers $HEADERS -Method Get -ContentType 'application/json' - } - catch { + } catch { Write-Debug 'StatusCode:' $_.Exception.Response.StatusCode.value__ Write-Debug 'StatusDescription:' $_.Exception.Response.StatusDescription } @@ -166,8 +161,7 @@ function Get-AllAtlassianUsers { # Build an array of hashtables with the values, handle null values and no members if ($REST_RESULTS.total -eq 0) { Write-Output 'No users found' - } - else { + } else { # Build an array of hashtables with the values handle null values $REST_RESULTS | ForEach-Object { $USERS_HASH_ARRAY += [PSCustomObject] @{ @@ -200,8 +194,7 @@ function Get-AtlassianUser { $REST_RESULTS = Invoke-RestMethod -Uri $USER_ENDPOINT -Headers $script:AtlassianAPIHeaders -Method Get -ContentType 'application/json' Write-Debug $REST_RESULTS.getType() Write-Debug (ConvertTo-Json $REST_RESULTS -Depth 10) - } - catch { + } catch { Write-Debug 'StatusCode:' $_.Exception.Response.StatusCode.value__ Write-Debug 'StatusDescription:' $_.Exception.Response.StatusDescription } diff --git a/AtlassianPowerKit.psm1 b/AtlassianPowerKit.psm1 index 6be5b33..e842be4 100644 --- a/AtlassianPowerKit.psm1 +++ b/AtlassianPowerKit.psm1 @@ -55,6 +55,7 @@ function Import-NestedModules { Write-Error "Multiple module files found for $MODULE_NAME. Exiting." throw "Multiple module files found for $MODULE_NAME. Exiting." } + Write-Debug "Importing nested module: $PSD1_FILE" Import-Module $PSD1_FILE.FullName -Force Write-Debug "Imported nested module: $PSD1_FILE, -- $($PSD1_FILE.BaseName)" #Write-Debug "Importing nested module: .\$($_.BaseName)\$($_.Name)" From b5dc2a32a122797ec068368b9b59c37f9519c8e1 Mon Sep 17 00:00:00 2001 From: markz0r Date: Tue, 20 May 2025 15:10:18 +1000 Subject: [PATCH 10/11] removing Microsoft vault dependency for 1password - untested --- .../AtlassianPowerKit-Jira.psm1 | 1 - .../AtlassianPowerKit-Shared.psm1 | 702 ++++++++++-------- AtlassianPowerKit.psm1 | 34 +- Run.ps1 | 171 ++--- 4 files changed, 507 insertions(+), 401 deletions(-) diff --git a/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psm1 b/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psm1 index 0ab12ab..60c57f1 100644 --- a/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psm1 +++ b/AtlassianPowerKit-Jira/AtlassianPowerKit-Jira.psm1 @@ -58,7 +58,6 @@ .EXAMPLE Get-Jira-CloudJQLQueryResults -JQL_STRING 'project is not EMPTY' -JSON_FILE_PATH 'All-Issues.json' - This example gets the Jira Cloud JQL query results for all issues in all projects. .LINK diff --git a/AtlassianPowerKit-Shared/AtlassianPowerKit-Shared.psm1 b/AtlassianPowerKit-Shared/AtlassianPowerKit-Shared.psm1 index 0d384a6..62cf510 100644 --- a/AtlassianPowerKit-Shared/AtlassianPowerKit-Shared.psm1 +++ b/AtlassianPowerKit-Shared/AtlassianPowerKit-Shared.psm1 @@ -33,9 +33,12 @@ GitHub: https://github.com/markz0r/AtlassianPowerKit #> # Vault path: $env:LOCALAPPDATA\Microsoft\PowerShell\secretmanagement\localstore\ +$script:OSMAtlassianProfilesVaultPath = if ($env:OSMAtlassianProfilesVaultPath) { + $env:OSMAtlassianProfilesVaultPath +} else { + 'op://employee/OSMAtlassianProfiles/notesPlain' +} $ErrorActionPreference = 'Stop'; $DebugPreference = 'Continue' -$VAULT_NAME = 'AtlassianPowerKitProfileVault' -$VAULT_KEY_PATH = "$($env:OSM_HOME)\vault_key.xml" $RETRY_AFTER = 60 $ENVAR_PREFIX = 'AtlassianPowerKit_' $REQUIRED_ENV_VARS = @('AtlassianAPIEndpoint', 'AtlassianAPIUserName', 'AtlassianAPIAuthString', 'PROFILE_NAME') @@ -79,20 +82,6 @@ function Clear-AtlassianPowerKitProfileDirs { Write-Debug 'Profile directories cleared.' } -function Clear-AtlassianPowerKitVault { - Unregister-SecretVault -Name $VAULT_NAME -ErrorAction Continue - Write-Debug "Vault $VAULT_NAME cleared." - $VAULT_KEY = Get-VaultKey - $storeConfiguration = @{ - Authentication = 'Password' - Password = $VAULT_KEY - PasswordTimeout = 3600 - Interaction = 'None' - } - Reset-SecretStore @storeConfiguration -Force - Clear-AtlassianPowerKitProfile -} - function Get-PaginatedJSONResults { param ( [Parameter(Mandatory = $true)] @@ -173,18 +162,26 @@ function Get-PaginatedJSONResults { } function Get-AtlassianPowerKitProfileList { - Write-Debug "$($MyInvocation.InvocationName) getting profile list from vault: $VAULT_NAME..." - if (!$(Get-SecretVault -Name $VAULT_NAME -ErrorAction SilentlyContinue)) { - Write-Debug "$($MyInvocation.InvocationName) vault not found, registering..." - Register-AtlassianPowerKitVault - Write-Debug "$($MyInvocation.InvocationName) vault registered successfully." - $PROFILE_LIST = @() + $vaultPath = if ($env:OSMAtlassianProfilesVaultPath) { + $env:OSMAtlassianProfilesVaultPath } else { - #Write-Debug 'Vault already registered, getting profiles...' - unlock-vault -VaultName $VAULT_NAME | Write-Debug - $PROFILE_LIST = (Get-SecretInfo -Vault $VAULT_NAME -Name '*').Name + 'op://employee/OSMAtlassianProfiles/notesPlain' + } + + Write-Debug "Getting Atlassian profiles from vault path: $vaultPath" + + try { + $profileJson = op read $vaultPath + $profileMap = $profileJson | ConvertFrom-Json -ErrorAction Stop + $profileList = $profileMap.PSObject.Properties.Name + } catch { + Write-Warning "āŒ Failed to read or parse profiles from 1Password: $_" + Write-Warning "Running 'New-AtlassianPowerKitProfile' to create a new profile." + return @() } - return $PROFILE_LIST + + Write-Debug "Found profiles: $($profileList -join ', ')" + return $profileList } function Get-LevenshteinDistance { @@ -214,264 +211,405 @@ function Get-LevenshteinDistance { return $d[$s.Length][$t.Length] } -# Function Update-ContentPlaceholderAll, takes a file path and a hashtable of placeholders and values, returning the content with the placeholders replaced (not updating the file) - -function Get-VaultKey { - if (-not (Test-Path $VAULT_KEY_PATH)) { - Write-Debug 'No vault key file found. Please register a vault first.' - return $false +# Function to set the Atlassian Cloud API headers +function Test-VaultProfileLoaded { + # Check if all of the $REQUIRED_ENV_VARS are set + $PROFILE_LOADED = $true + foreach ($envVar in $REQUIRED_ENV_VARS) { + $envVarNameKey = $ENVAR_PREFIX + $envVar + $ENV_STATE = Get-Item -Path "env:$envVarNameKey" -ErrorAction SilentlyContinue + if (!$ENV_STATE) { + Write-Debug "Profile is missing required environment variable: $envVarNameKey" + $PROFILE_LOADED = $false + break + } else { + #Write-Debug "Found required environment variable: $envVarNameKey already set." + $ENV_STATE = $null + } } - $VAULT_KEY = Import-Clixml -Path $VAULT_KEY_PATH - return $VAULT_KEY + return $PROFILE_LOADED } -function Unlock-Vault { +function Set-AtlassianAPIHeaders { + # check if there is a profile loaded + if (!$(Test-VaultProfileLoaded)) { + Write-Debug "$($MyInvocation.InvocationName) failed. Profile not loaded. Exiting..." + throw "$($MyInvocation.InvocationName) failed. Profile not loaded. Exiting..." + } else { + $HEADERS = @{ + Authorization = "Basic $($env:AtlassianPowerKit_AtlassianAPIAuthString)" + Accept = 'application/json' + } + # Add atlassian headers to the profile data + $API_HEADERS = $HEADERS | ConvertTo-Json -Compress + } + Return $API_HEADERS +} +function Set-AtlassianPowerKitProfile { param ( - [Parameter(Mandatory = $true)] - [string]$VaultName + [Parameter(Mandatory = $false)] + [string]$ProfileName ) - try { - if ((Get-SecretVault | Where-Object IsDefault).Name -ne $VAULT_NAME) { - Write-Debug "$VAULT_NAME is not the default vault. Setting as default..." - Set-SecretVaultDefault -Name $VAULT_NAME | Write-Debug + + $OSMprofile = $null + $runningInDocker = Test-Path -Path '/.dockerenv' -or $env:IN_DOCKER + + # --- OPTION 1: Docker env --- + if ($runningInDocker -and $env:OSMAtlassianProfiles) { + try { + $profileMap = $env:OSMAtlassianProfiles | ConvertFrom-Json -ErrorAction Stop + } catch { + throw 'āŒ Invalid JSON in OSMAtlassianProfiles environment variable.' + } + + if (-not $ProfileName) { + $keys = $profileMap.PSObject.Properties.Name + if ($keys.Count -eq 1) { + $ProfileName = $keys[0] + Write-Host "ā„¹ļø Defaulting to only available profile: '$ProfileName'" + } elseif ($keys.Count -gt 1) { + Write-Host 'Available profiles:' + $keys | ForEach-Object { Write-Host " - $_" } + $ProfileName = Read-Host 'Enter the profile name to load' + } else { + throw 'āŒ No profiles found in OSMAtlassianProfiles environment variable.' + } } - # Attempt to get a non-existent secret. If the vault is locked, this will throw an error. - $VAULT_KEY = Get-VaultKey - if (! $VAULT_KEY -or $VAULT_KEY -eq $false) { - Write-Debug 'Unlock-Vault failed. Exiting.' - throw 'Unlock-Vault failed. Exiting.' + + $OSMprofile = $profileMap.$ProfileName + if (-not $OSMprofile) { + throw "āŒ Profile '$ProfileName' not found in Docker OSMAtlassianProfiles." } - Unlock-SecretStore -Password $VAULT_KEY | Write-Debug - } catch { - # If an error is thrown, the vault is locked. - Write-Debug "Unlock-Vault failed: $_ ..." - throw 'Unlock-Vault failed Exiting' } - # If no error is thrown, the vault is unlocked. - Write-Debug 'Vault is unlocked.' - Return $true + + # --- OPTION 2: Host env --- + elseif ($env:OSMAtlassianProfiles) { + try { + $profileMap = $env:OSMAtlassianProfiles | ConvertFrom-Json -ErrorAction Stop + } catch { + throw 'āŒ Invalid JSON in OSMAtlassianProfiles environment variable.' + } + + if (-not $ProfileName) { + $keys = $profileMap.PSObject.Properties.Name + if ($keys.Count -eq 1) { + $ProfileName = $keys[0] + Write-Host "ā„¹ļø Defaulting to only available profile: '$ProfileName'" + } elseif ($keys.Count -gt 1) { + Write-Host 'Available profiles:' + $keys | ForEach-Object { Write-Host " - $_" } + $ProfileName = Read-Host 'Enter the profile name to load' + } else { + throw 'āŒ No profiles found in OSMAtlassianProfiles environment variable.' + } + } + + $OSMprofile = $profileMap.$ProfileName + if (-not $OSMprofile) { + throw "āŒ Profile '$ProfileName' not found in host OSMAtlassianProfiles env." + } + } + + # --- OPTION 3: Host with 1Password fallback --- + else { + try { + $vaultPath = if ($env:OSMAtlassianProfilesVaultPath) { + $env:OSMAtlassianProfilesVaultPath + } else { + 'op://employee/OSMAtlassianProfiles/notesPlain' + } + + $profileJson = op read $vaultPath + $profileMap = $profileJson | ConvertFrom-Json -ErrorAction Stop + + if (-not $ProfileName) { + $keys = $profileMap.PSObject.Properties.Name + if ($keys.Count -eq 1) { + $ProfileName = $keys[0] + Write-Host "ā„¹ļø Defaulting to only available profile: '$ProfileName'" + } elseif ($keys.Count -gt 1) { + Write-Host 'Available profiles:' + $keys | ForEach-Object { Write-Host " - $_" } + $ProfileName = Read-Host 'Enter the profile name to load' + } else { + throw 'āŒ No profiles found in 1Password store.' + } + } + + $OSMprofile = $profileMap.$ProfileName + if (-not $OSMprofile) { + throw "āŒ Profile '$ProfileName' not found in 1Password." + } + + } catch { + throw "āŒ Failed to read profile from 1Password: $_" + } + } + + # --- Apply profile values to ENV vars --- + $env:AtlassianPowerKit_PROFILE_NAME = $ProfileName + $env:AtlassianPowerKit_AtlassianAPIEndpoint = $OSMprofile.OSMAtlassianEndpoint + $env:AtlassianPowerKit_AtlassianAPIUserName = $OSMprofile.OSMAtlassianUsername + $env:AtlassianPowerKit_AtlassianAPIAuthString = $OSMprofile.OSMAtlassianAPIKey + $env:AtlassianPowerKit_AtlassianAPIHeaders = Set-AtlassianAPIHeaders + $env:AtlassianPowerKit_CloudID = $(Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/_edge/tenant_info").cloudId + + Write-Host "āœ… Loaded profile '$ProfileName'." + return Get-Item -Path "env:$ENVAR_PREFIX*" } -# Function to update the vault with the new profile data -function Update-AtlassianPowerKitVault { + +function Set-AtlassianPowerKitProfile { param ( [Parameter(Mandatory = $true)] - [string]$ProfileName, - [Parameter(Mandatory = $true)] - [hashtable]$ProfileData + [string]$ProfileName ) - Write-Debug "Writing profile data to vault for $ProfileName..." - Unlock-Vault -VaultName $VAULT_NAME | Write-Debug - try { - Set-Secret -Name $ProfileName -Secret $ProfileData -Vault $VAULT_NAME | Write-Debug - } catch { - Write-Debug "Update of vault failed for $ProfileName." - throw "Update of vault failed for $ProfileName." + + $OSMprofile = $null + $runningInDocker = Test-Path -Path '/.dockerenv' -or $env:IN_DOCKER + + # --- OPTION 1: Docker --- + if ($runningInDocker -and $env:OSMAtlassianProfiles) { + try { + $profileMap = $env:OSMAtlassianProfiles | ConvertFrom-Json -ErrorAction Stop + $OSMprofile = $profileMap.$ProfileName + } catch { + throw "Invalid JSON in OSMAtlassianProfiles env var: $_" + } + if (-not $profile) { + throw "Profile '$ProfileName' not found in Docker env OSMAtlassianProfiles." + } } - Write-Debug "Vault entruy for $ProfileName updated successfully." -} -function Register-AtlassianPowerKitVault { + # --- OPTION 2: Host with OSMAtlassianProfiles env --- + elseif ($env:OSMAtlassianProfiles) { + try { + $profileMap = $env:OSMAtlassianProfiles | ConvertFrom-Json -ErrorAction Stop + $OSMprofile = $profileMap.$ProfileName + } catch { + throw "Invalid JSON in OSMAtlassianProfiles env var: $_" + } + if (-not $OSMprofile) { + throw "Profile '$ProfileName' not found in host OSMAtlassianProfiles env." + } + } + + # --- OPTION 3: Host with 1Password fallback --- + else { + try { + Write-Host "šŸ” Loading Atlassian profile '$ProfileName' from 1Password..." + $opItemJson = op read "op://OSM/AtlassianProfile_$ProfileName/json" + $OSMprofile = $opItemJson | ConvertFrom-Json + } catch { + Write-Warning "āš ļø 1Password item for '$ProfileName' not found." + $confirm = Read-Host 'Do you want to create it now in 1Password? (Y/N)' + if ($confirm -ne 'Y') { throw 'Cannot proceed without valid profile.' } + + $endpoint = Read-Host 'Enter Atlassian Endpoint (e.g. example.atlassian.net)' + $username = Read-Host 'Enter Atlassian Username (email)' + $apiKey = Read-Host 'Enter Atlassian API Key (base64 encoded)' + + $newProfile = @{ + OSMAtlassianEndpoint = $endpoint + OSMAtlassianUsername = $username + OSMAtlassianAPIKey = $apiKey + } + + # Save to 1Password + $tempFile = [System.IO.Path]::GetTempFileName() + $newProfile | ConvertTo-Json -Depth 3 | Set-Content $tempFile + & op item create --category 'Secure Note' --title "AtlassianProfile_$ProfileName" -InputFile $tempFile | Out-Null + Remove-Item $tempFile + + Write-Host "āœ… Profile saved to 1Password as AtlassianProfile_$ProfileName. Please re-run the command." + return + } + } + + # --- Set ENVARS --- + $env:AtlassianPowerKit_PROFILE_NAME = $ProfileName + $env:AtlassianPowerKit_AtlassianAPIEndpoint = $profile.OSMAtlassianEndpoint + $env:AtlassianPowerKit_AtlassianAPIUserName = $profile.OSMAtlassianUsername + $env:AtlassianPowerKit_AtlassianAPIAuthString = $profile.OSMAtlassianAPIKey + $env:AtlassianPowerKit_AtlassianAPIHeaders = Set-AtlassianAPIHeaders + $env:AtlassianPowerKit_CloudID = $(Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/_edge/tenant_info").cloudId + + return Get-Item -Path "env:$ENVAR_PREFIX*" +} +function New-AtlassianPowerKitProfile { param ( [Parameter(Mandatory = $false)] - [Int]$ATTEMPT = 0 + [string]$opEntry = 'OSMAtlassianProfiles' + ) + + $profileName = Read-Host 'Enter a unique Profile Name' + $endpoint = Read-Host 'Enter Atlassian Endpoint (e.g. example.atlassian.net)' + $username = Read-Host 'Enter Atlassian Username (email)' + $apiKeySecure = Read-Host 'Enter Atlassian API Key' -AsSecureString + + # Convert SecureString to plain text + $apiKey = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto( + [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($apiKeySecure) ) - # Register the secret vault - # Cheking if the vault is already registered - while (-not (Test-Path $VAULT_KEY_PATH)) { - Write-Debug 'No vault key file found. Removing any existing vaults and re-creating...' - Unregister-SecretVault -Name $VAULT_NAME -ErrorAction SilentlyContinue - # Create a random secure key to use as the vault key as protected data - $VAULT_KEY = $null - while (-not $VAULT_KEY -or $VAULT_KEY.Length -lt 16) { - # Generate a random byte array - $randomBytes = New-Object byte[] 32 - [System.Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($randomBytes) - - # Convert the byte array to a secure string - $secureString = New-Object -TypeName System.Security.SecureString - foreach ($byte in $randomBytes) { - $secureString.AppendChar([char]$byte) + + # Build temporary env for testing + $env:AtlassianPowerKit_PROFILE_NAME = $profileName + $env:AtlassianPowerKit_AtlassianAPIEndpoint = $endpoint + $env:AtlassianPowerKit_AtlassianAPIUserName = $username + $env:AtlassianPowerKit_AtlassianAPIAuthString = $apiKey + $env:AtlassianPowerKit_AtlassianAPIHeaders = Set-AtlassianAPIHeaders + + try { + $cloudId = $(Invoke-RestMethod -Uri "https://$endpoint/_edge/tenant_info").cloudId + $env:AtlassianPowerKit_CloudID = $cloudId + Test-AtlassianPowerKitProfile | Out-Null + Write-Host "`nāœ… Profile connection test succeeded." + } catch { + Write-Warning "`nāŒ Connection test failed: $_" + return + } + + try { + $existingJson = op read "op://OSM/$opEntry/notesPlain" + $profileMap = $existingJson | ConvertFrom-Json -ErrorAction Stop + if ($profileMap.$profileName) { + $overwrite = Read-Host "Profile '$profileName' already exists. Overwrite? (Y/N)" + if ($overwrite -ne 'Y') { + Write-Host 'āŒ Aborted.' + return } - $VAULT_KEY = $secureString } - $VAULT_KEY | Export-Clixml -Path $VAULT_KEY_PATH - # Write the vault key to a temporary file - Write-Debug 'Vault key file created successfully.' + } catch { + Write-Host "šŸ” 1Password item '$opEntry' does not exist. Creating new profile store." + $profileMap = @{} } - if (Get-SecretVault -Name $VAULT_NAME -ErrorAction SilentlyContinue) { - Write-Debug "Vault $VAULT_NAME already exists." - } else { - Write-Debug "Registering vault $VAULT_NAME..." - $VAULT_KEY = Get-VaultKey - $storeConfiguration = @{ - Authentication = 'Password' - Password = $VAULT_KEY - PasswordTimeout = 3600 - Interaction = 'None' - } - Set-SecretVaultDefault -ClearDefault - Reset-SecretStore @storeConfiguration -Force - Register-SecretVault -Name $VAULT_NAME -ModuleName Microsoft.PowerShell.SecretStore -VaultParameters $storeConfiguration -DefaultVault -AllowClobber - Write-Debug "Vault $VAULT_NAME registered successfully." - Write-Debug "Checking if vault $VAULT_NAME is the default vault..." - if ((Get-SecretVault | Where-Object IsDefault).Name -ne $VAULT_NAME) { - Write-Debug "$VAULT_NAME is not the default vault. Setting as default..." - Set-SecretVaultDefault -Name $VAULT_NAME - } - Write-Debug "Vault $VAULT_NAME configured successfully." + + # Add/overwrite profile entry + $profileMap.$profileName = @{ + OSMAtlassianEndpoint = $endpoint + OSMAtlassianUsername = $username + OSMAtlassianAPIKey = $apiKey } - # Unlock the vault if it is locked + + # Write back to 1Password + $tempPath = [System.IO.Path]::GetTempFileName() + $profileMap | ConvertTo-Json -Depth 10 | Set-Content -Path $tempPath + try { - $VAULT_KEY = Get-VaultKey - Unlock-Vault -VaultName $VAULT_NAME | Write-Debug - } catch { - Write-Debug "Failed to unlock vault $VAULT_NAME. Please check the vault key file." - Write-Debug "De-registering vault $VAULT_NAME... and resetting vault key file." - Unregister-SecretVault -Name $VAULT_NAME | Write-Debug - Remove-Item -Path $VAULT_KEY_PATH -Force | Write-Debug - Write-Debug "Vault $VAULT_NAME de-registered and vault key file removed, starting from scratch..." - Write-Debug "$($MyInvocation.InvocationName) failed retrying, attempt: $ATTEMPT" - } - if ($ATTEMPT -gt 5) { - Write-Debug "$($MyInvocation.InvocationName) failed after $ATTEMPT attempts. Exiting..." - throw "$($MyInvocation.InvocationName) failed!" - } else { - Register-AtlassianPowerKitVault -ATTEMPT ($ATTEMPT + 1) + if ($existingJson) { + & op item edit "$opEntry" notesPlain="$(Get-Content $tempPath -Raw)" | Out-Null + Write-Host "`nāœ… Updated existing 1Password item '$opEntry'." + } else { + & op item create --category 'Secure Note' --title "$opEntry" notesPlain="$(Get-Content $tempPath -Raw)" | Out-Null + Write-Host "`nāœ… Created new 1Password item '$opEntry'." + } + } finally { + Remove-Item -Path $tempPath -Force } - Return $true -} +} -function Register-AtlassianPowerKitProfileInVault { - param( +function Edit-AtlassianPowerKitProfile { + param ( [Parameter(Mandatory = $false)] [string]$ProfileName, [Parameter(Mandatory = $false)] - [string]$AtlassianAPIEndpoint, - [Parameter(Mandatory = $false)] - [PSCredential]$AtlassianAPICredentialPair + [string]$opEntry = 'OSMAtlassianProfiles' ) + # Load profiles try { - Register-AtlassianPowerKitVault | Write-Debug + $rawJson = op read "op://employee/$opEntry/notesPlain" + $profileMap = $rawJson | ConvertFrom-Json -ErrorAction Stop } catch { - Write-Debug "$($MyInvocation.InvocationName) failed to register vault. Exiting" - throw "$($MyInvocation.InvocationName) failed to register vault. Exiting" - } - Write-Debug "$($MyInvocation.InvocationName) vault registered successfully." - $VAULT_PROFILES_LIST = Get-AtlassianPowerKitProfileList - # Check if the profile already exists in the secret vault - if ($null -ne $VAULT_PROFILES_LIST -and $VAULT_PROFILES_LIST.Count -gt 0 -and $VAULT_PROFILES_LIST.Contains($ProfileName)) { - Write-Debug "$($MyInvocation.InvocationName) Profile $ProfileName already exists in the vault. You must run AtlssianPowerKit -RemoveVaultProfile $ProfileName or AtlssianPowerKit -ResetVault to remove it first." - throw "$($MyInvocation.InvocationName) Profile $ProfileName already exists in the vault. You must run AtlssianPowerKit -RemoveVaultProfile $ProfileName or AtlssianPowerKit -ResetVault to remove it first." - } else { - #Write-Debug "Profile $ProfileName does not exist. Creating..." - Write-Debug "Preparing profile data for $ProfileName..." - $CredPair = "$($AtlassianAPICredentialPair.UserName):$($AtlassianAPICredentialPair.GetNetworkCredential().password)" - Write-Debug "CredPair: $CredPair" - $AtlassianAPIAuthToken = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($CredPair)) - $ProfileData = @{ - 'PROFILE_NAME' = $ProfileName - 'AtlassianAPIEndpoint' = $AtlassianAPIEndpoint - 'AtlassianAPIUserName' = $AtlassianAPICredential.UserName - 'AtlassianAPIAuthString' = $AtlassianAPIAuthToken - } - Write-Debug "$($MyInvocation.InvocationName) profile data prepared for $ProfileName, updating vault..." - Set-Secret -Name $ProfileName -Secret $ProfileData -Vault $VAULT_NAME | Write-Debug - Write-Debug "$($MyInvocation.InvocationName) profile data updated for $ProfileName." - Clear-AtlassianPowerKitProfile | Out-Null + throw "āŒ Failed to read or parse 1Password item '$opEntry'." } - return $PROFILE_NAME -} - -# Function to set the Atlassian Cloud API headers -function Test-VaultProfileLoaded { - # Check if all of the $REQUIRED_ENV_VARS are set - $PROFILE_LOADED = $true - foreach ($envVar in $REQUIRED_ENV_VARS) { - $envVarNameKey = $ENVAR_PREFIX + $envVar - $ENV_STATE = Get-Item -Path "env:$envVarNameKey" -ErrorAction SilentlyContinue - if (!$ENV_STATE) { - Write-Debug "Profile is missing required environment variable: $envVarNameKey" - $PROFILE_LOADED = $false - break + + # Default selection logic + if (-not $ProfileName) { + $keys = $profileMap.PSObject.Properties.Name + if ($keys.Count -eq 1) { + $ProfileName = $keys[0] + Write-Host "ā„¹ļø Defaulting to only available profile: '$ProfileName'" + } elseif ($keys.Count -gt 1) { + Write-Host 'Available profiles:' + $keys | ForEach-Object { Write-Host " - $_" } + $ProfileName = Read-Host 'Enter the profile name to edit' } else { - #Write-Debug "Found required environment variable: $envVarNameKey already set." - $ENV_STATE = $null + throw "āŒ No profiles found in $opEntry." } } - return $PROFILE_LOADED -} -function Set-AtlassianAPIHeaders { - # check if there is a profile loaded - if (!$(Test-VaultProfileLoaded)) { - Write-Debug "$($MyInvocation.InvocationName) failed. Profile not loaded. Exiting..." - throw "$($MyInvocation.InvocationName) failed. Profile not loaded. Exiting..." - } else { - $HEADERS = @{ - Authorization = "Basic $($env:AtlassianPowerKit_AtlassianAPIAuthString)" - Accept = 'application/json' + + if (-not $profileMap.ContainsKey($ProfileName)) { + throw "āŒ Profile '$ProfileName' not found." + } + + $OSMProfileData = $profileMap.$ProfileName + + Write-Host "`nEditing profile '$ProfileName'..." + Write-Host 'Select field to update:' + Write-Host '1) Endpoint' + Write-Host '2) Username' + Write-Host '3) API Key' + + $choice = Read-Host 'Enter 1, 2, or 3' + switch ($choice) { + '1' { + $newValue = Read-Host 'Enter new Atlassian Endpoint' + $OSMProfileData.OSMAtlassianEndpoint = $newValue + } + '2' { + $newValue = Read-Host 'Enter new Atlassian Username' + $OSMProfileData.OSMAtlassianUsername = $newValue + } + '3' { + $newSecure = Read-Host 'Enter new Atlassian API Key' -AsSecureString + $newPlain = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto( + [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($newSecure) + ) + $OSMProfileData.OSMAtlassianAPIKey = $newPlain + } + default { + Write-Host 'āŒ Invalid selection. Aborting.' + return } - # Add atlassian headers to the profile data - $API_HEADERS = $HEADERS | ConvertTo-Json -Compress } - Return $API_HEADERS -} -function Set-AtlassianPowerKitProfileFromVault { + # Save updated profile back + $profileMap.$ProfileName = $OSMProfileData + $tempPath = [System.IO.Path]::GetTempFileName() + $profileMap | ConvertTo-Json -Depth 10 | Set-Content -Path $tempPath + + try { + & op item edit "$opEntry" notesPlain="$(Get-Content $tempPath -Raw)" | Out-Null + Write-Host "`nāœ… Profile '$ProfileName' updated successfully." + } finally { + Remove-Item $tempPath -Force + } +} +function Export-AtlassianPowerKitProfilesForDocker { param ( - [Parameter(Mandatory = $true)] - [string]$SelectedProfileName + [Parameter(Mandatory = $false)] + [string]$opEntry = 'OSMAtlassianProfiles', + [Parameter(Mandatory = $false)] + [switch]$AsShellExport ) - $SKIP_LOAD = Test-VaultProfileLoaded - if ($SKIP_LOAD) { - Get-Item -Path "env:$ENVAR_PREFIX*" | Write-Debug - Write-Debug "$($MyInvocation.InvocationName) profile already loaded. Skipping vault load..." + + try { + $rawJson = op read "op://OSM/$opEntry/notesPlain" + $parsed = $rawJson | ConvertFrom-Json -ErrorAction Stop + } catch { + throw "Failed to read or parse 1Password item '$opEntry'." + } + + $compactJson = $parsed | ConvertTo-Json -Depth 10 -Compress + + if ($AsShellExport) { + Write-Output "export OSMAtlassianProfiles='$compactJson'" } else { - Write-Debug "Profile $SelectedProfileName not loaded. Loading from vault..." - # Load all profiles from the secret vault - if (!$(Get-SecretVault -Name $VAULT_NAME -ErrorAction SilentlyContinue)) { - Register-AtlassianPowerKitVault - } - # Check if the profile exists - $PROFILE_LIST = Get-AtlassianPowerKitProfileList - if (!$PROFILE_LIST.Contains($SelectedProfileName)) { - Write-Debug "Profiles found in vault: $($PROFILE_LIST | ConvertTo-Json -Depth 100) ..." - Write-Debug 'Profiles found in vault: ' - $PROFILE_LIST | ConvertTo-Json -Depth 100 | Write-Debug - Write-Error "$($MyInvocation.InvocationName) failed. Profile $SelectedProfileName not found in the vault. Exiting..." - Throw "$($MyInvocation.InvocationName) failed. Profile $SelectedProfileName not found in the vault. Exiting..." - } else { - Write-Debug "Profile $SelectedProfileName exists in the vault, loading..." - try { - # if vault is locked, unlock it - if (unlock-vault -VaultName $VAULT_NAME) { - $PROFILE_DATA = (Get-Secret -Name $SelectedProfileName -Vault $VAULT_NAME -AsPlainText) - #Create environment variables for each item in the profile data - Write-Debug "Successfully retrieved profile data for $SelectedProfileName : " - $PROFILE_DATA | ConvertTo-Json -Depth 100 | Write-Debug - $PROFILE_DATA.GetEnumerator() | ForEach-Object { - #Write-Debug "Setting environment variable: $($_.Key) = $($_.Value)" - # Create environment variable concatenated with AtlassianPowerKit_ - $VAR_NAME = $ENVAR_PREFIX + $_.Key - $VAR_VALUE = $_.Value - Write-Debug "Setting environment variable: env:$VAR_NAME = $VAR_VALUE" - $SetEnvar = '$env:' + $VAR_NAME + ' = "' + $VAR_VALUE + '"' - Invoke-Expression -Command $SetEnvar - Get-Item -Path "env:$VAR_NAME" | Write-Debug - } - } - } catch { - Write-Debug "Failed to load profile $SelectedProfileName. Please check the vault key file." - throw "Failed to load profile $SelectedProfileName. Please check the vault key file." - } - } + return $compactJson } - $ENVVAR_ARRAY = Get-Item -Path "env:$ENVAR_PREFIX*" - Return $ENVVAR_ARRAY } - # Function to test if AtlassianPowerKit profile authenticates successfully function Test-AtlassianPowerKitProfile { Write-Debug 'Testing Atlassian Cloud PowerKit Profile...' @@ -494,59 +632,43 @@ function Test-AtlassianPowerKitProfile { Return $true } -function Remove-AtlasianPowerKitProfile { +function Remove-AtlassianPowerKitProfile { param ( [Parameter(Mandatory = $true)] - [string]$ProfileName - ) - Write-Debug "Removing profile $ProfileName..." - if ($ProfileName -eq $env:AtlassianPowerKit_PROFILE_NAME) { - Clear-AtlassianPowerKitProfile | Write-Debug - } - Remove-Secret -Name $ProfileName -Vault $VAULT_NAME | Write-Debug - Write-Debug "Profile $ProfileName removed." -} -function Set-AtlassianPowerKitProfile { - param ( - [Parameter(Mandatory = $false)] - [string]$ProfileName = $false, + [string]$ProfileName, [Parameter(Mandatory = $false)] - [switch]$NoVault = $false + [string]$opEntry = 'OSMAtlassianProfiles' ) - if ($NoVault) { - Write-Debug 'NoVault switch enabled. Skipping vault actions and checking required environment variables are present' - foreach ($envVar in $REQUIRED_ENV_VARS) { - $envVarNameKey = $ENVAR_PREFIX + $envVar - $requiredEnvVar = $(Get-Item -Path "env:$envVarNameKey" -ErrorAction SilentlyContinue) - if (!$requiredEnvVar) { - Write-Error "Required environment variable $envVarNameKey not found. Exiting..." - throw "Required environment variable $envVarNameKey not found. Exiting..." - } else { - Write-Debug "Required environment variable found: $($requiredEnvVar.Name) = $(($requiredEnvVar.Value).substring(0, [System.Math]::Min(20, $requiredEnvVar.Value.Length)))..." - # if the environment variable is AtlassianAPIEndpoint, use the prefix as the profile name (setting as environment variable: AtlassianPowerKit_PROFILE_NAME) - if ($requiredEnvVar.Name -eq 'AtlassianPowerKit_AtlassianAPIEndpoint') { - $ProfileName = $requiredEnvVar.Value -Split '.' | Select-Object -First 1 - Write-Debug "Setting environment variable: PROFILE_NAME = $ProfileName" - # Create environment variable concatenated with AtlassianPowerKit_ prefix - $SetEnvar = '$env:' + $ENVAR_PREFIX + 'PROFILE_NAME = `"' + $ProfileName + '"`' - Invoke-Expression -Command $SetEnvar | Out-Null - } - } - } - } elseif ($ProfileName -ne $false) { - $VAULT_LOADED_ARRAY = Set-AtlassianPowerKitProfileFromVault -SelectedProfileName $ProfileName - $VAULT_LOADED_ARRAY | ForEach-Object { Write-Output "$($_.Name) = $($_.Value)" | Out-Null } - } else { - Write-Error 'ProfileName is required. Exiting...' - throw 'ProfileName is required. Exiting...' + + try { + $rawJson = op read "op://employee/$opEntry/notesPlain" + $profileMap = $rawJson | ConvertFrom-Json -ErrorAction Stop + } catch { + throw "āŒ Failed to read 1Password item '$opEntry'." } - #Write-Debug 'Vault loaded array:' - #$VAULT_LOADED_ARRAY | ForEach-Object { Write-Debug "$($_.Name) = $($_.Value)" } - $env:AtlassianPowerKit_AtlassianAPIHeaders = Set-AtlassianAPIHeaders - #Write-Debug "API Headers set: $($env:AtlassianPowerKit_AtlassianAPIHeaders) calling https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/_edge/tenant_info to get CloudID..." - $env:AtlassianPowerKit_CloudID = $(Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/_edge/tenant_info").cloudId - #Write-Debug "CloudID set: $($env:AtlassianPowerKit_CloudID) ..." - $PROFILE_ENVARS = Get-Item -Path "env:$ENVAR_PREFIX*" - Write-Debug "ENVARS loaded, count: $($PROFILE_ENVARS.Count) ..." - Return $PROFILE_ENVARS -} \ No newline at end of file + + if (-not $profileMap.ContainsKey($ProfileName)) { + Write-Host "āš ļø Profile '$ProfileName' not found. Nothing to remove." + return + } + + $confirm = Read-Host "Are you sure you want to delete profile '$ProfileName'? (Y/N)" + if ($confirm -ne 'Y') { + Write-Host 'āŒ Deletion aborted.' + return + } + + $profileMap.Remove($ProfileName) + + # Write back updated JSON + $tempPath = [System.IO.Path]::GetTempFileName() + $profileMap | ConvertTo-Json -Depth 10 | Set-Content -Path $tempPath + + try { + & op item edit "$opEntry" notesPlain="$(Get-Content $tempPath -Raw)" | Out-Null + Write-Host "`nāœ… Profile '$ProfileName' removed from '$opEntry'." + } finally { + Remove-Item $tempPath -Force + } +} + diff --git a/AtlassianPowerKit.psm1 b/AtlassianPowerKit.psm1 index e842be4..576d0c9 100644 --- a/AtlassianPowerKit.psm1 +++ b/AtlassianPowerKit.psm1 @@ -1,23 +1,3 @@ -<# -.SYNOPSIS - Atlassian Cloud PowerKit module for interacting with Atlassian Cloud REST APIs and other handy functions relating to the OSM project. - -.DESCRIPTION - See the [wiki](https://github.com/OrganisationServiceManagement/AtlassianPowerKit/wiki) for more information on how to use this module. - -.EXAMPLE - Use-AtlassianPowerKit - This example lists all functions in the AtlassianPowerKit module. -.EXAMPLE - Use-AtlassianPowerKit - Simply run the function to see a list of all functions in the module and nested modules. -.EXAMPLE - Get-DefinedPowerKitVariables - This example lists all variables defined in the AtlassianPowerKit module. -.LINK - GitHub: https://github.com/OrganisationServiceManagement/AtlassianPowerKit - -#> $ErrorActionPreference = 'Stop'; $DebugPreference = 'Continue' function Get-RequisitePowerKitModules { $AtlassianPowerKitRequiredModules = @('PowerShellGet', 'Microsoft.PowerShell.SecretManagement', 'Microsoft.PowerShell.SecretStore') @@ -322,9 +302,9 @@ function Import-AtlassianPowerKitProfile { Write-Debug "Only one profile found in the vault, selecting $selectedProfile" $ENVAR_ARRAY = Set-AtlassianPowerKitProfile -ProfileName $selectedProfile } else { - Write-Output 'Multiple profiles found in the vault but no profile provided, please use the -APK_Profile parameter to specify the desired profile' - foreach ($APK_PROFILENAME in $VAULT_PROFILES) { - Write-Output " AtlassianPowerkit -APK_PROFILENAME $APK_PROFILENAME" + Write-Output 'Multiple profiles found in the vault but no profile provided, please use the -OSMProfile parameter to specify the desired profile' + foreach ($OSMProfileNAME in $VAULT_PROFILES) { + Write-Output " AtlassianPowerkit -OSMProfileNAME $OSMProfileNAME" } Throw 'Ambiguous profile state' } @@ -339,7 +319,7 @@ function Import-AtlassianPowerKitProfile { function AtlassianPowerKit { param ( [Parameter(Mandatory = $false)] - [string]$APK_Profile, + [string]$OSMProfile, [Parameter(Mandatory = $false)] [switch]$ArchiveProfileDirs, [Parameter(Mandatory = $false)] @@ -405,9 +385,9 @@ function AtlassianPowerKit { } elseif ($NoVault) { Write-Debug '-NoVault flagged, attempting to load profile from environment variables' $PROFILE_ARRAY = Import-AtlassianPowerKitProfile -NoVault - } elseif ($APK_Profile) { - Write-Debug "Profile provided: $APK_Profile" - $ProfileName = $APK_Profile.Trim().ToLower() + } elseif ($OSMProfile) { + Write-Debug "Profile provided: $OSMProfile" + $ProfileName = $OSMProfile.Trim().ToLower() $PROFILE_ARRAY = Import-AtlassianPowerKitProfile -selectedProfile $ProfileName } else { Write-Debug 'No profile provided, checking if vault has only 1 profile' diff --git a/Run.ps1 b/Run.ps1 index a007acd..0e90f3c 100644 --- a/Run.ps1 +++ b/Run.ps1 @@ -13,95 +13,100 @@ $env:SECRETSTORE_PATH = $env:OSM_HOME if ($args.Count -gt 0) { # Run AtlassianPowerKit with the provided arguments AtlassianPowerKit @args -} -else { +} else { # Default command Write-Output 'No arguments provided. Starting Atlassian PowerKit...' AtlassianPowerKit } -Function Get-DeploymentConfigs { - param ( - [Parameter(Mandatory = $true)] - [string]$PROFILE_NAME - ) - Write-Host "Processing profile: $PROFILE_NAME" - # If there is a env:AtlassianPowerKit_PROFILE_NAME-ProjectList-*.json that was created in the last 12 hours, use it - $PROFILE_PROJECT_LIST = Test-ExistingConfigJSON -CONFIG_FILE_PATHPATTERN "$($env:OSM_HOME)\$PROFILE_NAME\$PROFILE_NAME-ProjectList-*.json" - if ($PROFILE_PROJECT_LIST) { - $PROJECT_LIST = Get-Content $PROFILE_PROJECT_LIST.FullName | ConvertFrom-Json -AsHashtable -NoEnumerate - } - else { - $PROJECT_LIST = AtlassianPowerKit -Profile $PROFILE_NAME -FunctionName 'Get-JiraProjectList' | ConvertFrom-Json -AsHashtable -NoEnumerate - } - #$PROJECT_LIST | ForEach-Object { Write-Host "Project: $($_.name) - $($_.key)" } - #$PROJECT_LIST | ConvertTo-Json -Depth 100 | Write-Debug - $OSM_PROJECT_LIST = $PROJECT_LIST | Where-Object { $_.key -match '.*OSM.*' -and $_.key -notin @('CUBOSM') } +#Function Get-DeploymentConfigs { +# param ( +# [Parameter(Mandatory = $true)] +# [string]$PROFILE_NAME +# ) +# Write-Host "Processing profile: $PROFILE_NAME" +# # If there is a env:AtlassianPowerKit_PROFILE_NAME-ProjectList-*.json that was created in the last 12 hours, use it +# $PROFILE_PROJECT_LIST = Test-ExistingConfigJSON -CONFIG_FILE_PATHPATTERN "$($env:OSM_HOME)\$PROFILE_NAME\$PROFILE_NAME-ProjectList-*.json" +# if ($PROFILE_PROJECT_LIST) { +# $PROJECT_LIST = Get-Content $PROFILE_PROJECT_LIST.FullName | ConvertFrom-Json -AsHashtable -NoEnumerate +# } +# else { +# $PROJECT_LIST = AtlassianPowerKit -Profile $PROFILE_NAME -FunctionName 'Get-JiraProjectList' | ConvertFrom-Json -AsHashtable -NoEnumerate +# } +# #$PROJECT_LIST | ForEach-Object { Write-Host "Project: $($_.name) - $($_.key)" } +# #$PROJECT_LIST | ConvertTo-Json -Depth 100 | Write-Debug +# $OSM_PROJECT_LIST = $PROJECT_LIST | Where-Object { $_.key -match '.*OSM.*' -and $_.key -notin @('CUBOSM') } - $JIRA_PROJECTS = $OSM_PROJECT_LIST | ForEach-Object { - $PROJECT_NAME = $($_.name) - $PROJECT_KEY = $($_.key) - # PROJECT_PROPERTIES - $PROFILE_PROJECT_PROPERTIES = Test-ExistingConfigJSON -CONFIG_FILE_PATHPATTERN "$($env:OSM_HOME)\$PROFILE_NAME\$PROFILE_NAME-$PROJECT_KEY-ProjectProperties-*.json" - if ($PROFILE_PROJECT_PROPERTIES) { - $PROFILE_PROJECT_PROPERTIES = Get-Content $PROFILE_PROJECT_PROPERTIES.FullName | ConvertFrom-Json -AsHashtable -NoEnumerate - } - else { - $PROFILE_PROJECT_PROPERTIES = AtlassianPowerKit -Profile $PROFILE_NAME -FunctionName 'Get-JiraProjectProperties' -FunctionParameters @{ PROJECT_KEY = $PROJECT_KEY } | ConvertFrom-Json -AsHashtable -NoEnumerate - } - # PROJECT_ISSUE_TYPE_SCHEMA - $PROJECT_ISSUE_TYPE_SCHEMA = Test-ExistingConfigJSON -CONFIG_FILE_PATHPATTERN "$($env:OSM_HOME)\$PROFILE_NAME\$PROFILE_NAME-$PROJECT_KEY-IssueTypeSchema-*.json" - if ($PROJECT_ISSUE_TYPE_SCHEMA) { - $PROJECT_ISSUE_TYPE_SCHEMA = Get-Content $PROJECT_ISSUE_TYPE_SCHEMA.FullName | ConvertFrom-Json -AsHashtable -NoEnumerate - } - else { - $PROJECT_ISSUE_TYPE_SCHEMA = AtlassianPowerKit -Profile $PROFILE_NAME -FunctionName 'Get-JiraCloudIssueTypeSchema' -FunctionParameters @{ PROJECT_KEY = $PROJECT_KEY } - } - # - # PROJECT_ISSUE_TYPES - $PROJECT_ISSUE_TYPES = Test-ExistingConfigJSON -CONFIG_FILE_PATHPATTERN "$($env:OSM_HOME)\$PROFILE_NAME\$PROFILE_NAME-$PROJECT_KEY-ProjectIssuesTypes-*.json" - if ($PROJECT_ISSUE_TYPES) { - $PROJECT_ISSUE_TYPES = Get-Content $PROJECT_ISSUE_TYPES.FullName | ConvertFrom-Json -AsHashtable -NoEnumerate - } - else { - $PROJECT_ISSUE_TYPES = AtlassianPowerKit -Profile $PROFILE_NAME -FunctionName 'Get-JiraProjectIssuesTypes' -FunctionParameters @{ PROJECT_KEY_OR_ID = $PROJECT_KEY } | ConvertFrom-Json -AsHashtable -NoEnumerate - } - $PROJECT_REQUEST_TYPES = Test-ExistingConfigJSON -CONFIG_FILE_PATHPATTERN "$($env:OSM_HOME)\$PROFILE_NAME\$PROFILE_NAME-$PROJECT_KEY-RequestTypeSchema-*.json" - if ($PROJECT_REQUEST_TYPES) { - $PROJECT_REQUEST_TYPES = Get-Content $PROJECT_REQUEST_TYPES.FullName | ConvertFrom-Json -AsHashtable -NoEnumerate - } - else { - $PROJECT_REQUEST_TYPES = AtlassianPowerKit -Profile $PROFILE_NAME -FunctionName 'Get-JiraServiceDeskRequestTypes' -FunctionParameters @{ PROJECT_KEY = $PROJECT_KEY } | ConvertFrom-Json -AsHashtable -NoEnumerate - } +# $JIRA_PROJECTS = $OSM_PROJECT_LIST | ForEach-Object { +# $PROJECT_NAME = $($_.name) +# $PROJECT_KEY = $($_.key) +# # PROJECT_PROPERTIES +# $PROFILE_PROJECT_PROPERTIES = Test-ExistingConfigJSON -CONFIG_FILE_PATHPATTERN "$($env:OSM_HOME)\$PROFILE_NAME\$PROFILE_NAME-$PROJECT_KEY-ProjectProperties-*.json" +# if ($PROFILE_PROJECT_PROPERTIES) { +# $PROFILE_PROJECT_PROPERTIES = Get-Content $PROFILE_PROJECT_PROPERTIES.FullName | ConvertFrom-Json -AsHashtable -NoEnumerate +# } +# else { +# $PROFILE_PROJECT_PROPERTIES = AtlassianPowerKit -Profile $PROFILE_NAME -FunctionName 'Get-JiraProjectProperties' -FunctionParameters @{ PROJECT_KEY = $PROJECT_KEY } | ConvertFrom-Json -AsHashtable -NoEnumerate +# } +# # PROJECT_ISSUE_TYPE_SCHEMA +# $PROJECT_ISSUE_TYPE_SCHEMA = Test-ExistingConfigJSON -CONFIG_FILE_PATHPATTERN "$($env:OSM_HOME)\$PROFILE_NAME\$PROFILE_NAME-$PROJECT_KEY-IssueTypeSchema-*.json" +# if ($PROJECT_ISSUE_TYPE_SCHEMA) { +# $PROJECT_ISSUE_TYPE_SCHEMA = Get-Content $PROJECT_ISSUE_TYPE_SCHEMA.FullName | ConvertFrom-Json -AsHashtable -NoEnumerate +# } +# else { +# $PROJECT_ISSUE_TYPE_SCHEMA = AtlassianPowerKit -Profile $PROFILE_NAME -FunctionName 'Get-JiraCloudIssueTypeSchema' -FunctionParameters @{ PROJECT_KEY = $PROJECT_KEY } +# } +# # +# # PROJECT_ISSUE_TYPES +# $PROJECT_ISSUE_TYPES = Test-ExistingConfigJSON -CONFIG_FILE_PATHPATTERN "$($env:OSM_HOME)\$PROFILE_NAME\$PROFILE_NAME-$PROJECT_KEY-ProjectIssuesTypes-*.json" +# if ($PROJECT_ISSUE_TYPES) { +# $PROJECT_ISSUE_TYPES = Get-Content $PROJECT_ISSUE_TYPES.FullName | ConvertFrom-Json -AsHashtable -NoEnumerate +# } +# else { +# $PROJECT_ISSUE_TYPES = AtlassianPowerKit -Profile $PROFILE_NAME -FunctionName 'Get-JiraProjectIssuesTypes' -FunctionParameters @{ PROJECT_KEY_OR_ID = $PROJECT_KEY } | ConvertFrom-Json -AsHashtable -NoEnumerate +# } +# $PROJECT_REQUEST_TYPES = Test-ExistingConfigJSON -CONFIG_FILE_PATHPATTERN "$($env:OSM_HOME)\$PROFILE_NAME\$PROFILE_NAME-$PROJECT_KEY-RequestTypeSchema-*.json" +# if ($PROJECT_REQUEST_TYPES) { +# $PROJECT_REQUEST_TYPES = Get-Content $PROJECT_REQUEST_TYPES.FullName | ConvertFrom-Json -AsHashtable -NoEnumerate +# } +# else { +# $PROJECT_REQUEST_TYPES = AtlassianPowerKit -Profile $PROFILE_NAME -FunctionName 'Get-JiraServiceDeskRequestTypes' -FunctionParameters @{ PROJECT_KEY = $PROJECT_KEY } | ConvertFrom-Json -AsHashtable -NoEnumerate +# } - # FORMS - $PROJECT_FORMS = Test-ExistingConfigJSON -CONFIG_FILE_PATHPATTERN "$($env:OSM_HOME)\$PROFILE_NAME\$PROFILE_NAME-$PROJECT_KEY-Forms-*.json" - if ($PROJECT_FORMS) { - $PROJECT_FORMS = Get-Content $PROJECT_FORMS.FullName | ConvertFrom-Json -AsHashtable -NoEnumerate - } - else { - $PROJECT_FORMS = AtlassianPowerKit -Profile $PROFILE_NAME -FunctionName 'Get-FormsForJiraProject' -FunctionParameters @{ PROJECT_KEY = $PROJECT_KEY } | ConvertFrom-Json -AsHashtable -NoEnumerate - } +# # FORMS +# $PROJECT_FORMS = Test-ExistingConfigJSON -CONFIG_FILE_PATHPATTERN "$($env:OSM_HOME)\$PROFILE_NAME\$PROFILE_NAME-$PROJECT_KEY-Forms-*.json" +# if ($PROJECT_FORMS) { +# $PROJECT_FORMS = Get-Content $PROJECT_FORMS.FullName | ConvertFrom-Json -AsHashtable -NoEnumerate +# } +# else { +# $PROJECT_FORMS = AtlassianPowerKit -Profile $PROFILE_NAME -FunctionName 'Get-FormsForJiraProject' -FunctionParameters @{ PROJECT_KEY = $PROJECT_KEY } | ConvertFrom-Json -AsHashtable -NoEnumerate +# } - # $FORMS = AtlassianPowerKit -Profile $PROFILE_NAME -FunctionName 'Get-FormsForJiraProject' -FunctionParameters @{ PROJECT_KEY = $PROJECT_KEY } - # WORKFLOW_SCHEMES - $PROJECT_WORKFLOWS_SCHEMES = Test-ExistingConfigJSON -CONFIG_FILE_PATHPATTERN "$($env:OSM_HOME)\$PROFILE_NAME\$PROFILE_NAME-$PROJECT_KEY-ProjectWorkflowSchemes-*.json" - if ($PROJECT_WORKFLOWS_SCHEMES) { - $PROJECT_WORKFLOWS_SCHEMES = Get-Content $PROJECT_WORKFLOWS_SCHEMES.FullName | ConvertFrom-Json -AsHashtable -NoEnumerate - } - else { - $PROJECT_WORKFLOWS_SCHEMES = AtlassianPowerKit -Profile $PROFILE_NAME -FunctionName 'Get-JiraProjectWorkflowSchemes' -FunctionParameters @{ PROJECT_KEY = $PROJECT_KEY } | ConvertFrom-Json -AsHashtable -NoEnumerate - } +# # $FORMS = AtlassianPowerKit -Profile $PROFILE_NAME -FunctionName 'Get-FormsForJiraProject' -FunctionParameters @{ PROJECT_KEY = $PROJECT_KEY } +# # WORKFLOW_SCHEMES +# $PROJECT_WORKFLOWS_SCHEMES = Test-ExistingConfigJSON -CONFIG_FILE_PATHPATTERN "$($env:OSM_HOME)\$PROFILE_NAME\$PROFILE_NAME-$PROJECT_KEY-ProjectWorkflowSchemes-*.json" +# if ($PROJECT_WORKFLOWS_SCHEMES) { +# $PROJECT_WORKFLOWS_SCHEMES = Get-Content $PROJECT_WORKFLOWS_SCHEMES.FullName | ConvertFrom-Json -AsHashtable -NoEnumerate +# } +# else { +# $PROJECT_WORKFLOWS_SCHEMES = AtlassianPowerKit -Profile $PROFILE_NAME -FunctionName 'Get-JiraProjectWorkflowSchemes' -FunctionParameters @{ PROJECT_KEY = $PROJECT_KEY } | ConvertFrom-Json -AsHashtable -NoEnumerate +# } + +# # Return object +# [PSCustomObject]@{ +# PROJECT_NAME = $PROJECT_NAME +# PROJECT_KEY = $PROJECT_KEY +# PROJECT_ISSUE_TYPE_SCHEMA = $PROJECT_ISSUE_TYPE_SCHEMA +# PROJECT_ISSUE_TYPES = $PROJECT_ISSUE_TYPES +# PROJECT_REQUEST_TYPES = $PROJECT_REQUEST_TYPES +# PROJECT_WORKFLOWS_SCHEMES = $PROJECT_WORKFLOWS_SCHEMES +# } +# } +# Return $JIRA_PROJECTS | ConvertTo-Json -Depth 100 -Compress +#} + + +#ED Conductig a jq; l search and returning issues. +# $JIRA_PROJECTS = Get-DeploymentConfigs -PROFILE_NAME 'OSM' +# $JIRA_PROJECTS | ForEach-Object { - # Return object - [PSCustomObject]@{ - PROJECT_NAME = $PROJECT_NAME - PROJECT_KEY = $PROJECT_KEY - PROJECT_ISSUE_TYPE_SCHEMA = $PROJECT_ISSUE_TYPE_SCHEMA - PROJECT_ISSUE_TYPES = $PROJECT_ISSUE_TYPES - PROJECT_REQUEST_TYPES = $PROJECT_REQUEST_TYPES - PROJECT_WORKFLOWS_SCHEMES = $PROJECT_WORKFLOWS_SCHEMES - } - } - Return $JIRA_PROJECTS | ConvertTo-Json -Depth 100 -Compress -} From 3d6d3b8763969e6db6cace7d75e013541cd7d34c Mon Sep 17 00:00:00 2001 From: markz0r Date: Mon, 23 Jun 2025 15:39:14 +1000 Subject: [PATCH 11/11] simplifying to config / envars --- .../AtlassianPowerKit-Shared.psd1 | 11 +- .../AtlassianPowerKit-Shared.psm1 | 422 ++++++++---------- AtlassianPowerKit.psm1 | 51 +-- README.md | 4 +- Run.ps1 | 3 +- 5 files changed, 207 insertions(+), 284 deletions(-) diff --git a/AtlassianPowerKit-Shared/AtlassianPowerKit-Shared.psd1 b/AtlassianPowerKit-Shared/AtlassianPowerKit-Shared.psd1 index bb340ea..5c080ea 100644 --- a/AtlassianPowerKit-Shared/AtlassianPowerKit-Shared.psd1 +++ b/AtlassianPowerKit-Shared/AtlassianPowerKit-Shared.psd1 @@ -70,16 +70,7 @@ # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. FunctionsToExport = @( - 'Clear-AtlassianPowerKitProfile', - 'Clear-AtlassianPowerKitProfileDirs', - 'Clear-AtlassianPowerKitVault', - 'Get-AtlassianPowerKitProfileList', - 'Get-PaginatedJSONResults', - 'Get-LevenshteinDistance', - 'Register-AtlassianPowerKitProfileInVault', - 'Set-AtlassianPowerKitProfile', - 'Remove-AtlasianPowerKitProfile', - 'Unlock-Vault' + '*' ) # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. CmdletsToExport = @() diff --git a/AtlassianPowerKit-Shared/AtlassianPowerKit-Shared.psm1 b/AtlassianPowerKit-Shared/AtlassianPowerKit-Shared.psm1 index 62cf510..a56da09 100644 --- a/AtlassianPowerKit-Shared/AtlassianPowerKit-Shared.psm1 +++ b/AtlassianPowerKit-Shared/AtlassianPowerKit-Shared.psm1 @@ -32,17 +32,44 @@ GitHub: https://github.com/markz0r/AtlassianPowerKit #> +$ErrorActionPreference = 'Stop'; $DebugPreference = 'Continue' # Vault path: $env:LOCALAPPDATA\Microsoft\PowerShell\secretmanagement\localstore\ -$script:OSMAtlassianProfilesVaultPath = if ($env:OSMAtlassianProfilesVaultPath) { +$1PASSWORD_PROFILE_URL = if ($env:OSMAtlassianProfilesVaultPath) { + Write-Debug "Using custom 1Password vault path for Atlassian profiles: $env:OSMAtlassianProfilesVaultPath" $env:OSMAtlassianProfilesVaultPath } else { - 'op://employee/OSMAtlassianProfiles/notesPlain' + $DEFAULT_1PASSWORD_VAULT_PATH = 'op://Employee/OSMAtlassianProfiles/notesPlain' + Write-Debug "Using default 1Password vault path for Atlassian profiles: $DEFAULT_1PASSWORD_VAULT_PATH" + $DEFAULT_1PASSWORD_VAULT_PATH } -$ErrorActionPreference = 'Stop'; $DebugPreference = 'Continue' $RETRY_AFTER = 60 $ENVAR_PREFIX = 'AtlassianPowerKit_' $REQUIRED_ENV_VARS = @('AtlassianAPIEndpoint', 'AtlassianAPIUserName', 'AtlassianAPIAuthString', 'PROFILE_NAME') +#Example format for 1Password json: +#{ +# "AtlassianProfile1": { +# "OSMAtlassianEndpoint": "example.atlassian.net", +# "OSMAtlassianUsername": "example@example.com", +# "OSMAtlassianAPIKey": "your_api_key" +# } +# "AtlassianProfile2": { + +# Function to ensure 1password CLI is installed and signed in +function Test-1PasswordCLI { + if (-not (Get-Command op -ErrorAction SilentlyContinue)) { + Write-Warning '1Password CLI (op) is not installed or not in PATH. Please install it from https://developer.1password.com/docs/cli/get-started/installation' + return $false + } + try { + op whoami | Write-Debug + return $true + } catch { + Write-Warning "1Password CLI is not signed in. Please sign in using 'op signin'." + return $false + } +} + function Clear-AtlassianPowerKitProfile { # Clear all environment variables starting with AtlassianPowerKit_ Get-ChildItem env:AtlassianPowerKit_* | ForEach-Object { @@ -162,26 +189,47 @@ function Get-PaginatedJSONResults { } function Get-AtlassianPowerKitProfileList { - $vaultPath = if ($env:OSMAtlassianProfilesVaultPath) { - $env:OSMAtlassianProfilesVaultPath - } else { - 'op://employee/OSMAtlassianProfiles/notesPlain' - } - - Write-Debug "Getting Atlassian profiles from vault path: $vaultPath" - - try { - $profileJson = op read $vaultPath - $profileMap = $profileJson | ConvertFrom-Json -ErrorAction Stop - $profileList = $profileMap.PSObject.Properties.Name - } catch { - Write-Warning "āŒ Failed to read or parse profiles from 1Password: $_" - Write-Warning "Running 'New-AtlassianPowerKitProfile' to create a new profile." - return @() - } - - Write-Debug "Found profiles: $($profileList -join ', ')" - return $profileList + Write-Debug 'Skipping..' + + #$vaultPath = if ($env:OSMAtlassianProfilesVaultPath) { + # $env:OSMAtlassianProfilesVaultPath + #} else { + # $DEFAULT_1PASSWORD_VAULT_PATH + #} + + #Write-Debug "Getting Atlassian profiles from vault path: $vaultPath" + ## Esnure 1password is signed in + #try { + # if (-not (Get-Command op -ErrorAction SilentlyContinue)) { + # Write-Warning '1Password CLI (op) is not installed or not in PATH. Please install it from https://developer.1password.com/docs/cli/get-started/installation' + # return @() + # } + # & op whoami | Out-Null + #} catch { + # Write-Warning "1Password CLI is not signed in. Please sign in using 'op signin'." + # return @() + #} + + #try { + # # Test if the 1Password item exists + # $profileJson = op read $vaultPath -ErrorAction Continue + # if ($profileJson) { + # $profileMap = $profileJson | ConvertFrom-Json -ErrorAction Stop + # $profileList = $profileMap.PSObject.Properties.Name + # } else { + # Write-Debug "No profiles found in 1Password at $vaultPath... create a new profile." + # New-AtlassianPowerKitProfile + # <# Action when all if and elseif conditions are false #> + + # } + #} catch { + # Write-Warning "āŒ Failed to read or parse profiles from 1Password: $_" + # Write-Warning "Running 'New-AtlassianPowerKitProfile' to create a new profile." + # return @() + #} + + #Write-Debug "Found profiles: $($profileList -join ', ')" + #return $profileList } function Get-LevenshteinDistance { @@ -210,7 +258,6 @@ function Get-LevenshteinDistance { return $d[$s.Length][$t.Length] } - # Function to set the Atlassian Cloud API headers function Test-VaultProfileLoaded { # Check if all of the $REQUIRED_ENV_VARS are set @@ -241,216 +288,96 @@ function Set-AtlassianAPIHeaders { Accept = 'application/json' } # Add atlassian headers to the profile data - $API_HEADERS = $HEADERS | ConvertTo-Json -Compress + $API_HEADERS = $HEADERS | ConvertTo-Json -Compress + return $API_HEADERS } - Return $API_HEADERS } +# Consolidated Set-AtlassianPowerKitProfile function function Set-AtlassianPowerKitProfile { param ( [Parameter(Mandatory = $false)] - [string]$ProfileName + [string]$OSMProfileName, + [Parameter(Mandatory = $false)] + [switch]$Vault = $false ) - $OSMprofile = $null - $runningInDocker = Test-Path -Path '/.dockerenv' -or $env:IN_DOCKER - - # --- OPTION 1: Docker env --- - if ($runningInDocker -and $env:OSMAtlassianProfiles) { - try { - $profileMap = $env:OSMAtlassianProfiles | ConvertFrom-Json -ErrorAction Stop - } catch { - throw 'āŒ Invalid JSON in OSMAtlassianProfiles environment variable.' - } - - if (-not $ProfileName) { - $keys = $profileMap.PSObject.Properties.Name - if ($keys.Count -eq 1) { - $ProfileName = $keys[0] - Write-Host "ā„¹ļø Defaulting to only available profile: '$ProfileName'" - } elseif ($keys.Count -gt 1) { - Write-Host 'Available profiles:' - $keys | ForEach-Object { Write-Host " - $_" } - $ProfileName = Read-Host 'Enter the profile name to load' - } else { - throw 'āŒ No profiles found in OSMAtlassianProfiles environment variable.' - } - } - - $OSMprofile = $profileMap.$ProfileName - if (-not $OSMprofile) { - throw "āŒ Profile '$ProfileName' not found in Docker OSMAtlassianProfiles." - } - } - - # --- OPTION 2: Host env --- - elseif ($env:OSMAtlassianProfiles) { - try { - $profileMap = $env:OSMAtlassianProfiles | ConvertFrom-Json -ErrorAction Stop - } catch { - throw 'āŒ Invalid JSON in OSMAtlassianProfiles environment variable.' - } - - if (-not $ProfileName) { - $keys = $profileMap.PSObject.Properties.Name - if ($keys.Count -eq 1) { - $ProfileName = $keys[0] - Write-Host "ā„¹ļø Defaulting to only available profile: '$ProfileName'" - } elseif ($keys.Count -gt 1) { - Write-Host 'Available profiles:' - $keys | ForEach-Object { Write-Host " - $_" } - $ProfileName = Read-Host 'Enter the profile name to load' - } else { - throw 'āŒ No profiles found in OSMAtlassianProfiles environment variable.' - } - } - - $OSMprofile = $profileMap.$ProfileName - if (-not $OSMprofile) { - throw "āŒ Profile '$ProfileName' not found in host OSMAtlassianProfiles env." - } - } - - # --- OPTION 3: Host with 1Password fallback --- - else { - try { - $vaultPath = if ($env:OSMAtlassianProfilesVaultPath) { - $env:OSMAtlassianProfilesVaultPath - } else { - 'op://employee/OSMAtlassianProfiles/notesPlain' - } - - $profileJson = op read $vaultPath - $profileMap = $profileJson | ConvertFrom-Json -ErrorAction Stop - - if (-not $ProfileName) { - $keys = $profileMap.PSObject.Properties.Name - if ($keys.Count -eq 1) { - $ProfileName = $keys[0] - Write-Host "ā„¹ļø Defaulting to only available profile: '$ProfileName'" - } elseif ($keys.Count -gt 1) { - Write-Host 'Available profiles:' - $keys | ForEach-Object { Write-Host " - $_" } - $ProfileName = Read-Host 'Enter the profile name to load' - } else { - throw 'āŒ No profiles found in 1Password store.' - } - } - - $OSMprofile = $profileMap.$ProfileName - if (-not $OSMprofile) { - throw "āŒ Profile '$ProfileName' not found in 1Password." - } - - } catch { - throw "āŒ Failed to read profile from 1Password: $_" - } - } - - # --- Apply profile values to ENV vars --- - $env:AtlassianPowerKit_PROFILE_NAME = $ProfileName - $env:AtlassianPowerKit_AtlassianAPIEndpoint = $OSMprofile.OSMAtlassianEndpoint - $env:AtlassianPowerKit_AtlassianAPIUserName = $OSMprofile.OSMAtlassianUsername - $env:AtlassianPowerKit_AtlassianAPIAuthString = $OSMprofile.OSMAtlassianAPIKey + #$OSMprofile = $null + #$runningInDocker = Test-Path -Path '/.dockerenv' -or $env:IN_DOCKER + + ## --- OPTION 1: Docker --- + #if ($runningInDocker -and $env:AtlassianPowerKit_PROFILE_NAME) { + # try { + # $profileMap = $env:OSMAtlassianProfiles | ConvertFrom-Json -ErrorAction Stop + # $OSMprofile = $profileMap.$OSMProfileName | ConvertTo-Json -ErrorAction Stop + # } catch { + # throw "Invalid JSON in OSMAtlassianProfiles env var: $_" + # } + # if (-not $OSMprofile) { + # throw "Profile '$OSMProfileName' not found in Docker env OSMAtlassianProfiles." + # } + #} + + ## --- OPTION 2: Host with OSMAtlassianProfiles env --- + #elseif ($env:OSMAtlassianProfiles) { + # try { + # $profileMap = $env:OSMAtlassianProfiles | ConvertFrom-Json -ErrorAction Stop + # $OSMprofile = $profileMap.$OSMProfileName | ConvertTo-Json -ErrorAction Stop + # } catch { + # throw "Invalid JSON in OSMAtlassianProfiles env var: $_" + # } + # if (-not $OSMprofile) { + # throw "Profile '$OSMProfileName' not found in host OSMAtlassianProfiles env." + # } + #} + + ## --- OPTION 3: Read from user --- + #else { + if (-not $env:AtlassianPowerKit_PROFILE_NAME) { + $OSMProfileName = Read-Host 'Enter OSM Profile Name' + $OSMProfileName = $OSMProfileName.Trim().ToLower() + $env:AtlassianPowerKit_PROFILE_NAME = $OSMProfileName + } + if (-not $env:AtlassianPowerKit_AtlassianAPIEndpoint) { + $OSMEndpoint = Read-Host "Enter Atlassian Endpoint (e.g. $OSMProfileName.atlassian.net)" + $OSMEndpoint = $OSMEndpoint.Trim().ToLower() + $env:AtlassianPowerKit_AtlassianAPIEndpoint = $OSMEndpoint + } + if (-not $env:AtlassianPowerKit_AtlassianAPIUserName) { + $OSMUsername = Read-Host 'Enter Atlassian Username (email)' + $OSMUsername = $OSMUsername.Trim().ToLower() + $env:AtlassianPowerKit_AtlassianAPIUserName = $OSMUsername + } + if (-not $env:AtlassianPowerKit_AtlassianAPIAuthString) { + $OSMAPIKey = Read-Host 'Enter Atlassian API Key (base64 encoded)' + $OSMAPIKey = $OSMAPIKey.Trim() + $CredPair = "${OSMUsername}:${OSMAPIKey}" + $AtlassianAPIAuthToken = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($CredPair)) + $env:AtlassianPowerKit_AtlassianAPIAuthString = $AtlassianAPIAuthToken + } + Write-Debug "Setting Atlassian PowerKit profile environment variables for $OSMProfileName..." $env:AtlassianPowerKit_AtlassianAPIHeaders = Set-AtlassianAPIHeaders $env:AtlassianPowerKit_CloudID = $(Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/_edge/tenant_info").cloudId - - Write-Host "āœ… Loaded profile '$ProfileName'." - return Get-Item -Path "env:$ENVAR_PREFIX*" + Return Get-Item -Path "env:$ENVAR_PREFIX*" } - -function Set-AtlassianPowerKitProfile { - param ( - [Parameter(Mandatory = $true)] - [string]$ProfileName - ) - - $OSMprofile = $null - $runningInDocker = Test-Path -Path '/.dockerenv' -or $env:IN_DOCKER - - # --- OPTION 1: Docker --- - if ($runningInDocker -and $env:OSMAtlassianProfiles) { - try { - $profileMap = $env:OSMAtlassianProfiles | ConvertFrom-Json -ErrorAction Stop - $OSMprofile = $profileMap.$ProfileName - } catch { - throw "Invalid JSON in OSMAtlassianProfiles env var: $_" - } - if (-not $profile) { - throw "Profile '$ProfileName' not found in Docker env OSMAtlassianProfiles." - } - } - - # --- OPTION 2: Host with OSMAtlassianProfiles env --- - elseif ($env:OSMAtlassianProfiles) { - try { - $profileMap = $env:OSMAtlassianProfiles | ConvertFrom-Json -ErrorAction Stop - $OSMprofile = $profileMap.$ProfileName - } catch { - throw "Invalid JSON in OSMAtlassianProfiles env var: $_" - } - if (-not $OSMprofile) { - throw "Profile '$ProfileName' not found in host OSMAtlassianProfiles env." - } - } - - # --- OPTION 3: Host with 1Password fallback --- - else { - try { - Write-Host "šŸ” Loading Atlassian profile '$ProfileName' from 1Password..." - $opItemJson = op read "op://OSM/AtlassianProfile_$ProfileName/json" - $OSMprofile = $opItemJson | ConvertFrom-Json - } catch { - Write-Warning "āš ļø 1Password item for '$ProfileName' not found." - $confirm = Read-Host 'Do you want to create it now in 1Password? (Y/N)' - if ($confirm -ne 'Y') { throw 'Cannot proceed without valid profile.' } - - $endpoint = Read-Host 'Enter Atlassian Endpoint (e.g. example.atlassian.net)' - $username = Read-Host 'Enter Atlassian Username (email)' - $apiKey = Read-Host 'Enter Atlassian API Key (base64 encoded)' - - $newProfile = @{ - OSMAtlassianEndpoint = $endpoint - OSMAtlassianUsername = $username - OSMAtlassianAPIKey = $apiKey - } - - # Save to 1Password - $tempFile = [System.IO.Path]::GetTempFileName() - $newProfile | ConvertTo-Json -Depth 3 | Set-Content $tempFile - & op item create --category 'Secure Note' --title "AtlassianProfile_$ProfileName" -InputFile $tempFile | Out-Null - Remove-Item $tempFile - - Write-Host "āœ… Profile saved to 1Password as AtlassianProfile_$ProfileName. Please re-run the command." - return - } - } - - # --- Set ENVARS --- - $env:AtlassianPowerKit_PROFILE_NAME = $ProfileName - $env:AtlassianPowerKit_AtlassianAPIEndpoint = $profile.OSMAtlassianEndpoint - $env:AtlassianPowerKit_AtlassianAPIUserName = $profile.OSMAtlassianUsername - $env:AtlassianPowerKit_AtlassianAPIAuthString = $profile.OSMAtlassianAPIKey - $env:AtlassianPowerKit_AtlassianAPIHeaders = Set-AtlassianAPIHeaders - $env:AtlassianPowerKit_CloudID = $(Invoke-RestMethod -Uri "https://$($env:AtlassianPowerKit_AtlassianAPIEndpoint)/_edge/tenant_info").cloudId - - return Get-Item -Path "env:$ENVAR_PREFIX*" -} function New-AtlassianPowerKitProfile { param ( [Parameter(Mandatory = $false)] - [string]$opEntry = 'OSMAtlassianProfiles' - ) - - $profileName = Read-Host 'Enter a unique Profile Name' - $endpoint = Read-Host 'Enter Atlassian Endpoint (e.g. example.atlassian.net)' - $username = Read-Host 'Enter Atlassian Username (email)' - $apiKeySecure = Read-Host 'Enter Atlassian API Key' -AsSecureString - - # Convert SecureString to plain text - $apiKey = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto( - [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($apiKeySecure) + [string]$opEntry = 'OSMAtlassianProfiles', + [Parameter(Mandatory = $true)] + [string]$OSMProfileName, + [Parameter(Mandatory = $true)] + [string]$OSMEndpoint, + [Parameter(Mandatory = $true)] + [string]$OSMUsername, + [Parameter(Mandatory = $true)] + [string]$OSMAPIKey ) + # Ensure all provided parameters are strings with no leading/trailing/internal whitespace and are lowercase + $endpoint = $OSMEndpoint.Trim().ToLower() + $username = $OSMUsername.Trim().ToLower() + $apiKey = $OSMAPIKey.Trim() + $profileName = $OSMProfileName.Trim().ToLower() # Build temporary env for testing $env:AtlassianPowerKit_PROFILE_NAME = $profileName @@ -470,8 +397,8 @@ function New-AtlassianPowerKitProfile { } try { - $existingJson = op read "op://OSM/$opEntry/notesPlain" - $profileMap = $existingJson | ConvertFrom-Json -ErrorAction Stop + $existingJson = op read "$1PASSWORD_PROFILE_URL" -ErrorAction Continue + $profileMap = $existingJson | ConvertFrom-Json -ErrorAction Continue if ($profileMap.$profileName) { $overwrite = Read-Host "Profile '$profileName' already exists. Overwrite? (Y/N)" if ($overwrite -ne 'Y') { @@ -480,11 +407,11 @@ function New-AtlassianPowerKitProfile { } } } catch { - Write-Host "šŸ” 1Password item '$opEntry' does not exist. Creating new profile store." - $profileMap = @{} + Write-Debug "šŸ” 1Password item '$1PASSWORD_PROFILE_URL' does not exist. Creating new profile store." } - + # Add/overwrite profile entry + $profileMap = @{} $profileMap.$profileName = @{ OSMAtlassianEndpoint = $endpoint OSMAtlassianUsername = $username @@ -495,19 +422,27 @@ function New-AtlassianPowerKitProfile { $tempPath = [System.IO.Path]::GetTempFileName() $profileMap | ConvertTo-Json -Depth 10 | Set-Content -Path $tempPath + # Get vault and title for 1Password item + # op://Employee/OSMAtlassianProfiles/notesPlain -- assumed pattern + $1PASSWORD_SPLIT_ARRAY = ( $1PASSWORD_PROFILE_URL).Split('/') + $1PASSWORD_VAULT = $1PASSWORD_SPLIT_ARRAY[2] + $1PASSWORD_TITLE = $1PASSWORD_SPLIT_ARRAY[3] + $1PASSWORD_FIELD = $1PASSWORD_SPLIT_ARRAYS[4] + try { if ($existingJson) { - & op item edit "$opEntry" notesPlain="$(Get-Content $tempPath -Raw)" | Out-Null - Write-Host "`nāœ… Updated existing 1Password item '$opEntry'." + & op item edit "op://$1PASSWORD_VAULT/$1PASSWORD_TITLE" $1PASSWORD_FIELD="$(Get-Content $tempPath -Raw)" | Write-Debug + Write-Host "`nāœ… Updated existing 1Password item $1PASSWORD_PROFILE_URL." } else { - & op item create --category 'Secure Note' --title "$opEntry" notesPlain="$(Get-Content $tempPath -Raw)" | Out-Null + & op item create --category 'Secure Note' --vault $1PASSWORD_VAULT --title $1PASSWORD_TITLE $1PASSWORD_FIELD="$(Get-Content $tempPath -Raw)" | Write-Debug Write-Host "`nāœ… Created new 1Password item '$opEntry'." } } finally { Remove-Item -Path $tempPath -Force } + $RAW_JSON = op read $1PASSWORD_PROFILE_URL + Return $RAW_JSON } - function Edit-AtlassianPowerKitProfile { param ( [Parameter(Mandatory = $false)] @@ -586,30 +521,28 @@ function Edit-AtlassianPowerKitProfile { Remove-Item $tempPath -Force } } -function Export-AtlassianPowerKitProfilesForDocker { +function Export-AtlassianPowerKitProfiles { param ( - [Parameter(Mandatory = $false)] - [string]$opEntry = 'OSMAtlassianProfiles', [Parameter(Mandatory = $false)] [switch]$AsShellExport ) - try { - $rawJson = op read "op://OSM/$opEntry/notesPlain" - $parsed = $rawJson | ConvertFrom-Json -ErrorAction Stop + # Ensure 1Password CLI is installed and authenticated + if (-not (Get-Command op -ErrorAction SilentlyContinue)) { + throw '1Password CLI (op) is not installed or not in PATH. Please install it from https://1password.com/downloads/cli/' + } + op signin | Write-Debug + # Load profiles from 1Password + $1PASSWORD_JSON = op read $1PASSWORD_PROFILE_URL + $compactJson = $1PASSWORD_JSON | ConvertTo-Json -Depth 10 -Compress + if ($AsShellExport) { + Write-Output "export OSMAtlassianProfiles='$compactJson'" + } } catch { - throw "Failed to read or parse 1Password item '$opEntry'." - } - - $compactJson = $parsed | ConvertTo-Json -Depth 10 -Compress - - if ($AsShellExport) { - Write-Output "export OSMAtlassianProfiles='$compactJson'" - } else { - return $compactJson + Write-Debug "No entry found at $1PASSWORD_PROFILE_URL. Creating new profile store." } + return $compactJson } - # Function to test if AtlassianPowerKit profile authenticates successfully function Test-AtlassianPowerKitProfile { Write-Debug 'Testing Atlassian Cloud PowerKit Profile...' @@ -631,7 +564,6 @@ function Test-AtlassianPowerKitProfile { } Return $true } - function Remove-AtlassianPowerKitProfile { param ( [Parameter(Mandatory = $true)] diff --git a/AtlassianPowerKit.psm1 b/AtlassianPowerKit.psm1 index 576d0c9..e5c7890 100644 --- a/AtlassianPowerKit.psm1 +++ b/AtlassianPowerKit.psm1 @@ -259,23 +259,23 @@ function Show-AtlassianPowerKitFunctions { Write-Host "Invoking AtlassingPowerKit Function: $SelectedFunctionName" -ForegroundColor Green return $SelectedFunctionName } -function New-AtlassianPowerKitProfile { - param ( - [Parameter(Mandatory = $false)] - [string]$PROFILE_NAME = $null - ) - if (!$PROFILE_NAME) { - $PROFILE_NAME = Read-Host -Prompt 'Enter a name for the new profile' - } - $PROFILE_NAME = $PROFILE_NAME.Trim().ToLower() - $API_ENDPOINT = Read-Host -Prompt 'Enter the Atlassian API endpoint (e.g. https://your-domain.atlassian.net)' - $API_CREDPAIR = Get-Credential -Message 'Enter your Atlassian API credentials (email and API token)' - $REGISTERED_PROFILE = Register-AtlassianPowerKitProfileInVault -ProfileName $PROFILE_NAME -AtlassianAPIEndpoint $API_ENDPOINT -AtlassianAPICredentialPair $API_CREDPAIR - $ENVAR_ARRAY = Import-AtlassianPowerKitProfile -selectedProfile $REGISTERED_PROFILE - return $ENVAR_ARRAY +#function New-AtlassianPowerKitProfile { +# param ( +# [Parameter(Mandatory = $false)] +# [string]$PROFILE_NAME = $null +# ) +# if (!$PROFILE_NAME) { +# $PROFILE_NAME = Read-Host -Prompt 'Enter a name for the new profile' +# } +# $PROFILE_NAME = $PROFILE_NAME.Trim().ToLower() +# $API_ENDPOINT = Read-Host -Prompt 'Enter the Atlassian API endpoint (e.g. https://your-domain.atlassian.net)' +# $API_CREDPAIR = Get-Credential -Message 'Enter your Atlassian API credentials (email and API token)' +# $REGISTERED_PROFILE = Register-AtlassianPowerKitProfileInVault -ProfileName $PROFILE_NAME -AtlassianAPIEndpoint $API_ENDPOINT -AtlassianAPICredentialPair $API_CREDPAIR +# $ENVAR_ARRAY = Import-AtlassianPowerKitProfile -selectedProfile $REGISTERED_PROFILE +# return $ENVAR_ARRAY -} -# Function to list availble profiles with number references for interactive selection or 'N' to create a new profile +#} +## Function to list availble profiles with number references for interactive selection or 'N' to create a new profile function Import-AtlassianPowerKitProfile { param ( [Parameter(Mandatory = $false)] @@ -286,9 +286,9 @@ function Import-AtlassianPowerKitProfile { if ($NoVault) { #Write-Debug "$($MyInvocation.InvocationName) -NoVault flag set, attempting to load profile from environment variables" $ENVAR_ARRAY = Set-AtlassianPowerKitProfile -NoVault - } elseif ($selectedProfile -ne $false) { + } elseif (! $selectedProfile) { #Write-Debug "$($MyInvocation.InvocationName) -ProfileName profided, attempting to load profile: $selectedProfile from the vault" - $ENVAR_ARRAY = Set-AtlassianPowerKitProfile -ProfileName $selectedProfile + $ENVAR_ARRAY = Set-AtlassianPowerKitProfile -OSMProfileName $selectedProfile if (!$ENVAR_ARRAY -or $ENVAR_ARRAY.Count -lt 3) { Write-Host "Could not load profile: $selectedProfile from the vault. Requesting values to add it to vault." $ENVAR_ARRAY = New-AtlassianPowerKitProfile -PROFILE_NAME $selectedProfile @@ -386,16 +386,17 @@ function AtlassianPowerKit { Write-Debug '-NoVault flagged, attempting to load profile from environment variables' $PROFILE_ARRAY = Import-AtlassianPowerKitProfile -NoVault } elseif ($OSMProfile) { - Write-Debug "Profile provided: $OSMProfile" - $ProfileName = $OSMProfile.Trim().ToLower() - $PROFILE_ARRAY = Import-AtlassianPowerKitProfile -selectedProfile $ProfileName + $OSMProfileName = $OSMProfile.Trim().ToLower() + Write-Debug "Profile provided: $OSMProfileName" + $PROFILE_ARRAY = Import-AtlassianPowerKitProfile -selectedProfile $OSMProfileName + #$PROFILE_JSON = Set-AtlassianPowerKitProfile -OSMProfileName $OSMProfile } else { - Write-Debug 'No profile provided, checking if vault has only 1 profile' - $PROFILE_ARRAY = Import-AtlassianPowerKitProfile + Write-Debug 'No profile provided.. listing what is available' + $PROFILE_ARRAY = Set-AtlassianPowerKitProfile } Write-Debug "Profile set to: $env:AtlassianPowerKit_PROFILE_NAME" $PROFILE_ARRAY | ForEach-Object { - Write-Output " $_" | Out-Null + " $_" | Write-Debug } if (!$FunctionName) { $FunctionName = Show-AtlassianPowerKitFunctions -NESTED_MODULES $NESTED_MODULES @@ -423,7 +424,7 @@ function AtlassianPowerKit { # Write call stack and sub-function error messages to the debug output Write-Debug "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ $((Get-Item -Path $PSScriptRoot).FullName) $($MyInvocation.InvocationName) FAILED: " # Write full call stack to the debug output and error message to the console - Get-PSCallStack + Get-PSCallStack | Write-Debug Write-Debug "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ $((Get-Item -Path $PSScriptRoot).FullName) $($MyInvocation.InvocationName)" Write-Error $_.Exception.Message } diff --git a/README.md b/README.md index 50f31de..0650e87 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ cd .\AtlassianPowerKit; Import-Module "AtlassianPowerKit.psd1" # Text UI AtlassianPowerKit # Direct invocation (after profile configured) -AtlassianPowerKit -FunctionName "Get-JiraIssue" -FunctionParameters @{"Key"="TEST-1"} -Profile "zoak" +AtlassianPowerKit -FunctionName "Get-JiraIssue" -FunctionParameters @{"Key"="TEST-1"} -OSMProfile "zoak" ``` ```docker @@ -37,7 +37,7 @@ docker run -it --rm -v ${PWD}/osm_home:/mnt/osm -v "$HOME/.local/share/powershel - PowerShell 7.0 or later (Core is supported on Windows, macOS, and Linux) - Alternatively, you can use the Docker image to run the module: - - https://hub.docker.com/r/markz0r/atlassian-powerkit + - - `docker run --rm -v ${PWD}\osm_home:/mnt/osm -v "$Env:LOCALAPPDATA\Microsoft\PowerShell\secretmanagement\:/root/.secretmanagement/" -it markz0r/atlassian-powerkit:latest` ## Contributing diff --git a/Run.ps1 b/Run.ps1 index 0e90f3c..4bd9891 100644 --- a/Run.ps1 +++ b/Run.ps1 @@ -1,10 +1,9 @@ # Run.ps1 # Import necessary modules -Import-Module -Name Microsoft.PowerShell.SecretManagement, Microsoft.PowerShell.SecretStore -Force +#Import-Module -Name Microsoft.PowerShell.SecretManagement, Microsoft.PowerShell.SecretStore -Force Set-Location "$env:OSM_INSTALL/AtlassianPowerKit" Import-Module "$env:OSM_INSTALL/AtlassianPowerKit/AtlassianPowerKit.psd1" -Force -$env:SECRETSTORE_PATH = $env:OSM_HOME # Load Environment Variables from the host