diff --git a/.github/workflows/migrate-work-items.yml b/.github/workflows/migrate-work-items.yml index 043ee35..dca78ec 100644 --- a/.github/workflows/migrate-work-items.yml +++ b/.github/workflows/migrate-work-items.yml @@ -2,51 +2,51 @@ name: Migrate Work Items on: workflow_dispatch: - inputs: + inputs: ado-org: - description: 'ado-org' + description: "ado-org" required: true - default: 'jjohanning0798' + default: "jjohanning0798" ado-project: - description: 'ado-project' + description: "ado-project" required: true - default: 'PartsUnlimited' + default: "PartsUnlimited" ado_area_path: - description: 'ADO area path to migrate - uses the UNDER operator' + description: "ADO area path to migrate - uses the UNDER operator" required: true - default: 'migrate' + default: "migrate" ado_migrate_closed_workitems: - description: 'Migrate closed work items' + description: "Migrate closed work items" required: true type: boolean - default: 'true' + default: true ado_production_run: description: tag migrated work items with migrated-to-github and add discussion comment required: true type: boolean - default: 'true' + default: true gh-org: - description: 'gh-org' + description: "gh-org" required: true - default: 'joshjohanning-org' + default: "joshjohanning-org" gh-repo: - description: 'gh-org' + description: "gh-org" required: true - default: 'migrate-ado-workitems' + default: "migrate-ado-workitems" gh_update_assigned_to: - description: 'Update Assigned To' + description: "Update Assigned To" required: true type: boolean - default: 'true' + default: true gh_assigned_to_user_suffix: - description: 'EMU suffix' + description: "EMU suffix" required: true - default: '_corp' + default: "_corp" gh_add_ado_comments: - description: 'Add ADO Comments' + description: "Add ADO Comments" required: true type: boolean - default: 'true' + default: true jobs: migrate: @@ -54,25 +54,25 @@ jobs: steps: - uses: actions/checkout@main - + - uses: tibdex/github-app-token@v1 id: get_installation_token - with: + with: app_id: 179484 installation_id: 23995058 private_key: ${{ secrets.PRIVATE_KEY }} - + - name: run migration shell: pwsh run: | # run migration script - + # cleaning up bools $ado_migrate_closed_workitems=$false $ado_production_run=$false $gh_update_assigned_to=$false $gh_add_ado_comments=$false - + if("${{ github.event.inputs.ado_migrate_closed_workitems }}" -eq "true") { $ado_migrate_closed_workitems=$true } @@ -84,10 +84,9 @@ jobs: if("${{ github.event.inputs.gh_update_assigned_to }}" -eq "true") { $gh_update_assigned_to=$true } - + if("${{ github.event.inputs.gh_add_ado_comments }}" -eq "true") { $gh_add_ado_comments=$true } - + ./ado_workitems_to_github_issues.ps1 -ado_pat "${{ SECRETS.ADO_PAT }}" -ado_org "${{ github.event.inputs.ado-org }}" -ado_project "${{ github.event.inputs.ado-project }}" -ado_area_path "${{ github.event.inputs.ado_area_path }}" -ado_migrate_closed_workitems $ado_migrate_closed_workitems -ado_production_run $ado_production_run -gh_pat "${{ steps.get_installation_token.outputs.token }}" -gh_org "${{ github.event.inputs.gh-org }}" -gh_repo "${{ github.event.inputs.gh-repo }}" -gh_update_assigned_to $gh_update_assigned_to -gh_assigned_to_user_suffix "${{ github.event.inputs.gh_assigned_to_user_suffix }}" -gh_add_ado_comments $gh_add_ado_comments - \ No newline at end of file diff --git a/.github/workflows/pull_request_workflow.yaml b/.github/workflows/pull_request_workflow.yaml new file mode 100644 index 0000000..955e5b0 --- /dev/null +++ b/.github/workflows/pull_request_workflow.yaml @@ -0,0 +1,14 @@ +on: + pull_request: + +permissions: read-all + +jobs: + pull_request_workflow: + uses: exit83/shared-workflows/.github/workflows/code_quality.yaml@main + with: + jest: 'no' + playwright: 'no' + pre_commit: 'yes' + trivy_vulnerability_scan: 'yes' + secrets: inherit diff --git a/.markdownlint.yaml b/.markdownlint.yaml new file mode 100644 index 0000000..5094d75 --- /dev/null +++ b/.markdownlint.yaml @@ -0,0 +1,18 @@ +# MARKDOWN LINT CONFIGURATION +# https://github.com/DavidAnson/markdownlint/blob/main/schema/.markdownlint.yaml + +# Default state for all rules +default: true + +# MD013/line-length - Line length +MD013: + # Include tables + tables: false + +# MD024/no-duplicate-heading/no-duplicate-header - Multiple headings with the same content +MD024: + # Only check sibling headings + siblings_only: true + +# MD036/no-emphasis-as-heading/no-emphasis-as-header - Emphasis used instead of a heading +MD036: false diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..4ca2e99 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,61 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: + - repo: https://github.com/gitguardian/ggshield + rev: v1.29.0 + hooks: + - id: ggshield + language_version: python3 + stages: [commit] + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: trailing-whitespace + args: [--markdown-linebreak-ext=md] + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + - id: no-commit-to-branch + args: [--branch=main] + - repo: https://github.com/igorshubovych/markdownlint-cli + rev: v0.40.0 + hooks: + - id: markdownlint + args: [--config=.markdownlint.yaml] + - repo: https://github.com/python-jsonschema/check-jsonschema + rev: 0.28.3 + hooks: + - id: check-dependabot + - id: check-github-workflows + args: ["--verbose"] + - id: check-github-actions + - repo: https://github.com/antonbabenko/pre-commit-terraform + rev: v1.79.1 + hooks: + - id: terraform_fmt + args: + - --args=-recursive + - id: terraform_tflint + args: + # Permanently Disabled Checks + - --args=--disable-rule=terraform_required_version # We manage TF Version in GH Workflow + # Disabled for this Project + # Temporarily Disabled Checks + - id: terraform_checkov + args: + - --args=--quiet + # - --args=--compact + # Permanently Disabled Checks + - --args=--skip-check CKV_AZURE_189 # We use Azure KeyVault on GH Actions + - --args=--skip-check CKV_AZURE_109 # Maintaining a list of allowed GH Action Worker IPs is not feasible + - --args=--skip-check CKV_AZURE_190 # No longer valid https://github.com/bridgecrewio/checkov/issues/4307 + - --args=--skip-check CKV2_AZURE_1 # Customer managed keys + - --args=--skip-check CKV2_AZURE_18 # Customer managed keys + - --args=--skip-check CKV2_AZURE_32 # KeyVault Private endpoints are overly complicated for 99% of our clients + - --args=--skip-check CKV2_AZURE_33 # Storage Account Private endpoints are overly complicated for 99% of our clients + - --args=--skip-check CKV2_AZURE_47 # That only apply for no Azure Cloud (Azure Gov, China, etc) + - --args=--skip-check CKV2_AZURE_41 # The SAS accounts, not need limited for the duration. In general we not use sas accounts + - --args=--skip-check CKV_AZURE_33 # The rule only apply for Queue, and we not use Queue in general + + # Disabled for this Project + # Temporarily Disabled Checks diff --git a/README.md b/README.md index 6bda3c2..39a1951 100644 --- a/README.md +++ b/README.md @@ -2,61 +2,74 @@ PowerShell script to migrate Azure DevOps work items to GitHub Issues -### Prerequisites +## Prerequisites -1. Install az devops and github cli where this is running (ie: action or locally; GitHub-hosted runners already have) -2. In GitHub, [create a label](https://docs.github.com/en/issues/using-labels-and-milestones-to-track-work/managing-labels) for EACH work item type that is being migrated (as lower case) +1. Install az devops and github cli where this is running (ie: action or locally; + GitHub-hosted runners already have) +2. In GitHub, [create a label](https://docs.github.com/en/issues/using-labels-and-milestones-to-track-work/managing-labels) + for EACH work item type that is being migrated (as lower case) - ie: "user story", "bug", "task", "feature" 3. Define under what area path you want to migrate - - You can modify the WIQL if you want to use a different way to migrate work items, such as `[TAG] = "migrate"` + - You can modify the WIQL if you want to use a different way to migrate workitems, + such as `[TAG] = "migrate"` -### Things it migrates +## Things it migrates 1. Title 2. Description (or for a bug, repro steps and/or system info) 3. State (if the work item is done / closed, it will be closed in GitHub) -4. It will try to assign the work item to the correct user in GitHub - based on ADO email before the `@` +4. It will try to assign the work item to the correct user in GitHub - based on + ADO email before the `@` - This uses the `-gh_update_assigned_to` and `-gh_assigned_to_user_suffix` options - Users have to be added to GitHub org 5. Migrate acceptance criteria as part of issue body (if present) 6. Adds in the following as a comment to the issue: - - Original work item url + - Original work item url - Basic details in a collapsed markdown table - Entire work item as JSON in a collapsed section -7. Creates tag "copied-to-github" and a comment on the ADO work item with `-$ado_production_run $true"`. The tag prevents duplicate copying. +7. Creates tag "copied-to-github" and a comment on the ADO work item with + `-$ado_production_run $true"`. The tag prevents duplicate copying. + +## To Do -### To Do 1. Provide user mapping option -### Things it won't ever migrate +## Things it won't ever migrate + 1. Created date/update dates -### Example +## Example - [Screenshot](https://user-images.githubusercontent.com/19912012/157745772-69f5cf75-5407-491e-a754-d94b188378ff.png) - [Migrated GitHub Issue](https://github.com/joshjohanning-org/migrate-ado-workitems/issues/296) ## Instructions for Running in Actions -The recommendation is to use a GitHub App to run the migration - a GitHub app has higher rate limits than using a user PAT. +The recommendation is to use a GitHub App to run the migration - a GitHub app +has higher rate limits than using a user PAT. -1. Create GitHub App with (can use this [reference](https://josh-ops.com/posts/github-apps/#creating-a-github-app)). Use the following permissions: - + Repo: `Contents:Read` - + Repo: `Issues:Read and write` - + Org: `Members:Read` +1. Create GitHub App with (can use this [reference](https://josh-ops.com/posts/github-apps/#creating-a-github-app)). +Use the following permissions: + - Repo: `Contents:Read` + - Repo: `Issues:Read and write` + - Org: `Members:Read` 1. Create Private Key for GitHub App 1. Obtain App ID and Installation ID - see [the instructions for using smee.io](https://josh-ops.com/posts/github-apps/#creating-a-github-app) 1. Create the following action secrets: - + `ADO_PAT`: Azure DevOps PAT with appropriate permissions to read work items - + `PRIVATE_KEY`: The contents of the private key created and downloaded in step #2 -1. Use the [action](.github/workflows/migrate-work-items.yml) and update the App ID and Installation ID obtained in step #3 -1. Update any defaults in the [action](.github/workflows/migrate-work-items.yml) (ie: Azure DevOps organization and project, GitHub organization and repo) + - `ADO_PAT`: Azure DevOps PAT with appropriate permissions to read work items + - `PRIVATE_KEY`: The contents of the private key created and downloaded in + step #2 +1. Use the [action](.github/workflows/migrate-work-items.yml) and update the App + ID and Installation ID obtained in step #3 +1. Update any defaults in the [action](.github/workflows/migrate-work-items.yml) + (ie: Azure DevOps organization and project, GitHub organization and repo) 1. Ensure the action exists in the repo's default branch 1. Run the workflow ## Instructions for Running Locally -Using the GitHub app might be better so you don't reach a limit on your GitHub account on creating new issues 😀 +Using the GitHub app might be better so you don't reach a limit on your GitHub +account on creating new issues 😀 ```pwsh ./ado_workitems_to_github_issues.ps1 ` @@ -91,4 +104,5 @@ Using the GitHub app might be better so you don't reach a limit on your GitHub a | `-gh_assigned_to_user_suffix` | No | `""` | Used in conjunction with `-gh_update_assigned_to`, used to suffix the username, e.g. if using GitHub Enterprise Managed User (EMU) instance | | `-gh_add_ado_comments` | No | `$false` | Switch to add ADO comments as a section with the migrated work item | -+ **Note**: With `-gh_update_assigned_to $true`, you/your users will receive a lot of emails from GitHub when the user is assigned to the issue \ No newline at end of file +- **Note**: With `-gh_update_assigned_to $true`, you/your users will receive a +lot of emails from GitHub when the user is assigned to the issue diff --git a/ado_workitems_to_github_issues.ps1 b/ado_workitems_to_github_issues.ps1 index f9d85bc..d572913 100644 --- a/ado_workitems_to_github_issues.ps1 +++ b/ado_workitems_to_github_issues.ps1 @@ -4,7 +4,7 @@ # Prerequisites: # 1. Install az devops and github cli -# 2. create a label for EACH work item type that is being migrated (as lower case) +# 2. create a label for EACH work item type that is being migrated (as lower case) # - ie: "user story", "bug", "task", "feature" # 3. define under what area path you want to migrate # - You can modify the WIQL if you want to use a different way to migrate work items, such as [TAG] = "migrate" @@ -20,7 +20,7 @@ # 4. It will try to assign the work item to the correct user in GitHub - based on ADO email (-gh_update_assigned_to and -gh_assigned_to_user_suffix options) - they of course have to be in GitHub already # 5. Migrate acceptance criteria as part of issue body (if present) # 6. Adds in the following as a comment to the issue: -# a. Original work item url +# a. Original work item url # b. Basic details in a collapsed markdown table # c. Entire work item as JSON in a collapsed section # 7. Creates tag "copied-to-github" and a comment on the ADO work item with `-$ado_production_run $true` . The tag prevents duplicate copying. @@ -117,14 +117,14 @@ ForEach($workitem in $query) { $ado_assigned_to_display_name = "" $ado_assigned_to_unique_name = "" } - + # create the details table $ado_details_beginning="`n`n
Original Work Item Details

" + "`n`n" $ado_details_beginning | Add-Content -Path ./temp_comment_body.txt -Encoding ASCII; $ado_details= "| Created date | Created by | Changed date | Changed By | Assigned To | State | Type | Area Path | Iteration Path|`n|---|---|---|---|---|---|---|---|---|`n" $ado_details+="| $($details.fields.{System.CreatedDate}) | $($details.fields.{System.CreatedBy}.displayName) | $($details.fields.{System.ChangedDate}) | $($details.fields.{System.ChangedBy}.displayName) | $ado_assigned_to_display_name | $($details.fields.{System.State}) | $($details.fields.{System.WorkItemType}) | $($details.fields.{System.AreaPath}) | $($details.fields.{System.IterationPath}) |`n`n" $ado_details | Add-Content -Path ./temp_comment_body.txt -Encoding ASCII; - $ado_details_end="`n" + "`n

" + $ado_details_end="`n" + "`n

" $ado_details_end | Add-Content -Path ./temp_comment_body.txt -Encoding ASCII; # prepare the comment @@ -138,7 +138,7 @@ ForEach($workitem in $query) { $base64 = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes(":$ado_pat")) $headers.Add("Authorization", "Basic $base64") $response = Invoke-RestMethod "https://dev.azure.com/$ado_org/$ado_project/_apis/wit/workItems/$($workitem.id)/comments?api-version=7.1-preview.3" -Method 'GET' -Headers $headers - + if($response.count -gt 0) { $ado_comments_details="" $ado_original_workitem_json_beginning="`n`n
Work Item Comments ($($response.count))

" + "`n`n" @@ -153,13 +153,13 @@ ForEach($workitem in $query) { $ado_original_workitem_json_end | Add-Content -Path ./temp_comment_body.txt -Encoding ASCII; } } - + # setting the label on the issue to be the work item type $work_item_type = $details.fields.{System.WorkItemType}.ToLower() # create the issue $issue_url=gh issue create --body-file ./temp_issue_body.txt --repo "$gh_org/$gh_repo" --title "$title" --label $work_item_type - + if (![string]::IsNullOrEmpty($issue_url.Trim())) { Write-Host " Issue created: $issue_url"; $count++; @@ -167,7 +167,7 @@ ForEach($workitem in $query) { else { throw "Issue creation failed."; } - + # update assigned to in GitHub if the option is set - tries to use ado email to map to github username if ($gh_update_assigned_to -eq $true -and $ado_assigned_to_unique_name -ne "") { $gh_assignee=$ado_assigned_to_unique_name.Split("@")[0] @@ -187,7 +187,7 @@ ForEach($workitem in $query) { if ($ado_production_run) { $workitemTags = $workitem.fields.'System.Tags'; $discussion = "This work item was copied to github as issue $issue_url"; - az boards work-item update --id "$workitemId" --fields "System.Tags=copied-to-github; $workitemTags" --discussion "$discussion" | Out-Null; + az boards work-item update --id "$workitemId" --fields "System.Tags=copied-to-github; $workitemTags" --discussion "$discussion" | Out-Null; } # close out the issue if it's closed on the Azure Devops side @@ -195,6 +195,6 @@ ForEach($workitem in $query) { if ($ado_closure_states.Contains($details.fields.{System.State})) { gh issue close $issue_url } - + } Write-Host "Total items copied: $count"