Skip to content

Commit 75ab14d

Browse files
Add ELM migrations commands (#1481)
* Add ELM migrations commands * Add migration create options * Fix style checks * Use org base for ELM migrations * Adjust BuildWheel task for Windows paths * Allow GitHub.com targets and fix help YAML * Simplify migration resume flow * Add migrations command reference * Fix BuildWheel task on Windows * Fix markdown lint spacing * Fix markdown lint and flake8 indentation errors * Add --include-inactive help example and unit test for list migrations * Fix convert_date_string_to_iso8601 returning datetime instead of string for tz-aware inputs * Apply dev feedback: remove URL validation, fix validate-only default, unify flag naming, generic skip-validation help * Update ELM migrations TSG with current params, codedev.ms troubleshooting, output formats * TSG: add complete command/param reference, list step, cutover cancel, all optional params * TSG: restructure as end-to-end guide with setup, lifecycle, walkthrough, and scenarios * TSG: add fresh-user onboarding, concrete examples, status interpretation table, shell note * docs: update TSG to use ADO org URL instead of separate ELM service URL * docs: update migrations.md to use ADO org URL instead of ELM service URL * Fix statusRequested field handling in pause/resume and update help URLs * Make --agent-pool optional; server assigns default pool - Remove required check for --agent-pool in create_migration - Conditionally include agentPoolName in payload only when provided - Server auto-assigns EnterpriseLiveMigrationPool when omitted - Update tests: replace required-agent-pool tests with optional behavior tests * Update migration changes and tests * Promote validate-only migrations via PUT state update * Improve migrations CLI UX validations and guidance * Expand ELM migration docs and TSG guidance * Support skip-validation names and integer values * Add project filter support for migrations list * Fix flake8 W503 in migration state check * Fix markdown lint spacing and list formatting * Fix remaining markdown lint issues in migration docs * Fix Run_Style_Check missing dependency on Build_Publish_Azure_CLI_Test_SDK --------- Co-authored-by: Bhuvan Shah <bhuvanshah@microsoft.com>
1 parent 5cd827f commit 75ab14d

File tree

19 files changed

+1949
-8
lines changed

19 files changed

+1949
-8
lines changed

.flake8

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,8 @@ exclude =
2121
scripts
2222
doc
2323
build_scripts
24+
env
25+
venv
26+
.venv
2427
*/test/*
2528
*/devops_sdk/*

.github/workflows/pr-workflow.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ jobs:
140140
path: ${{ github.workspace }}/azure-devops/htmlcov
141141

142142
Run_Style_Check:
143+
needs: Build_Publish_Azure_CLI_Test_SDK
143144
runs-on: macOS-latest
144145
steps:
145146
- uses: actions/checkout@v4

.vscode/tasks.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,18 @@
33
"tasks": [
44
{
55
"label": "BuildWheel",
6-
"command": "${command:python.interpreterPath}",
6+
"type": "shell",
7+
"command": "python",
78
"args": [
89
"setup.py",
910
"sdist",
1011
"bdist_wheel"
1112
],
12-
"type": "shell",
1313
"options": {
14-
"cwd": "${workspaceRoot}/azure-devops/"
14+
"cwd": "${workspaceRoot}/azure-devops/",
15+
"env": {
16+
"PATH": "${workspaceRoot}\\env\\Scripts;${env:PATH}"
17+
}
1518
},
1619
"presentation": {
1720
"echo": true,

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ $az [group] [subgroup] [command] {parameters}
2828
```
2929

3030
Adding the Azure DevOps Extension adds `devops`, `pipelines`, `artifacts`, `boards` and `repos` groups.
31+
Enterprise live migrations are available under `az devops migrations`.
3132
For usage and help content for any command, pass in the -h parameter, for example:
3233

3334
```bash
@@ -43,6 +44,7 @@ Group
4344

4445
Subgroups:
4546
admin : Manage administration operations.
47+
migrations : Manage enterprise live migrations.
4648
extension : Manage extensions.
4749
project : Manage team projects.
4850
security : Manage security related operations.
@@ -64,6 +66,7 @@ Commands:
6466
- Checkout the CLI docs at [docs.microsoft.com - Azure DevOps CLI](https://docs.microsoft.com/azure/devops/cli/).
6567
- Check out other examples in the [How-to guides](https://docs.microsoft.com/azure/devops/cli/?view=azure-devops#how-to-guides) section.
6668
- You can view the various commands and its usage here - [docs.microsoft.com - Azure DevOps Extension Reference](https://docs.microsoft.com/en-us/cli/azure/devops?view=azure-cli-latest)
69+
- Enterprise live migrations guide: [doc/migrations.md](doc/migrations.md)
6770

6871
## Contribute
6972

azure-devops/azext_devops/__init__.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ def load_command_table(self, args):
2121
load_admin_commands(self, args)
2222
from azext_devops.dev.boards.commands import load_work_commands
2323
load_work_commands(self, args)
24+
from azext_devops.dev.migration.commands import load_migration_commands
25+
load_migration_commands(self, args)
2426
from azext_devops.dev.pipelines.commands import load_build_commands
2527
load_build_commands(self, args)
2628
from azext_devops.dev.repos.commands import load_code_commands
@@ -36,6 +38,8 @@ def load_arguments(self, command):
3638
load_admin_arguments(self, command)
3739
from azext_devops.dev.boards.arguments import load_work_arguments
3840
load_work_arguments(self, command)
41+
from azext_devops.dev.migration.arguments import load_migration_arguments
42+
load_migration_arguments(self, command)
3943
from azext_devops.dev.pipelines.arguments import load_build_arguments
4044
load_build_arguments(self, command)
4145
from azext_devops.dev.repos.arguments import load_code_arguments
@@ -47,8 +51,8 @@ def load_arguments(self, command):
4751

4852
@staticmethod
4953
def post_parse_args(_cli_ctx, **kwargs):
50-
if (kwargs.get('command', None) and
51-
kwargs['command'].startswith(('devops', 'boards', 'artifacts', 'pipelines', 'repos'))):
54+
command = kwargs.get('command', None)
55+
if command and command.startswith(('devops', 'boards', 'artifacts', 'pipelines', 'repos', 'migrations')):
5256
from azext_devops.dev.common.telemetry import set_tracking_data
5357
# we need to set tracking data only after we know that all args are valid,
5458
# otherwise we may log EUII data that a user inadvertently sent as an argument

azure-devops/azext_devops/dev/common/arguments.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@ def convert_date_string_to_iso8601(value, argument=None):
2525
if d.tzinfo is None:
2626
from dateutil.tz import tzlocal
2727
d = d.replace(tzinfo=tzlocal())
28-
d = d.isoformat()
29-
return d
28+
return d.isoformat()
3029

3130

3231
def convert_date_only_string_to_iso8601(value, argument=None):
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# --------------------------------------------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License. See License.txt in the project root for license information.
4+
# --------------------------------------------------------------------------------------------
5+
6+
from ._help import load_migration_help
7+
8+
load_migration_help()
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# --------------------------------------------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License. See License.txt in the project root for license information.
4+
# --------------------------------------------------------------------------------------------
5+
6+
from collections import OrderedDict
7+
from azext_devops.dev.common.format import trim_for_display, date_time_to_only_date
8+
9+
10+
_TARGET_TRUNCATION_LENGTH = 60
11+
12+
13+
def transform_migrations_table_output(result):
14+
migrations = _unwrap_migration_list(result)
15+
table_output = []
16+
for item in migrations:
17+
table_output.append(_transform_migration_row(item))
18+
return table_output
19+
20+
21+
def transform_migration_table_output(result):
22+
if result is None:
23+
return []
24+
return [_transform_migration_row(result)]
25+
26+
27+
def _unwrap_migration_list(result):
28+
if isinstance(result, dict) and 'value' in result:
29+
return result['value']
30+
if isinstance(result, list):
31+
return result
32+
return []
33+
34+
35+
def _transform_migration_row(row):
36+
table_row = OrderedDict()
37+
table_row['RepositoryId'] = row.get('repositoryId')
38+
table_row['TargetRepository'] = trim_for_display(row.get('targetRepository'),
39+
_TARGET_TRUNCATION_LENGTH)
40+
table_row['Status'] = row.get('status')
41+
table_row['Stage'] = row.get('stage')
42+
table_row['ValidateOnly'] = row.get('validateOnly')
43+
table_row['CutoverDate'] = date_time_to_only_date(row.get('scheduledCutoverDate'))
44+
table_row['CodeSyncDate'] = date_time_to_only_date(row.get('codeSyncDate'))
45+
table_row['PrSyncDate'] = date_time_to_only_date(row.get('pullRequestSyncDate'))
46+
return table_row
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# --------------------------------------------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License. See License.txt in the project root for license information.
4+
# --------------------------------------------------------------------------------------------
5+
6+
from knack.help_files import helps
7+
8+
9+
def load_migration_help():
10+
helps['devops migrations'] = """
11+
type: group
12+
short-summary: Manage enterprise live migrations.
13+
long-summary: 'This command group is a part of the azure-devops extension. For ELM migrations, --org should be your Azure DevOps organization URL (for example: https://dev.azure.com/myorg).'
14+
"""
15+
16+
helps['devops migrations list'] = """
17+
type: command
18+
short-summary: List migrations in an organization.
19+
examples:
20+
- name: List migrations.
21+
text: |
22+
az devops migrations list --org https://dev.azure.com/myorg
23+
- name: List all migrations including inactive ones.
24+
text: |
25+
az devops migrations list --org https://dev.azure.com/myorg --include-inactive
26+
"""
27+
28+
helps['devops migrations status'] = """
29+
type: command
30+
short-summary: Get migration status for a repository.
31+
examples:
32+
- name: Get migration status by repository id.
33+
text: |
34+
az devops migrations status --org https://dev.azure.com/myorg --repository-id 00000000-0000-0000-0000-000000000000
35+
"""
36+
37+
helps['devops migrations create'] = """
38+
type: command
39+
short-summary: Create a migration for a repository.
40+
examples:
41+
- name: Create a migration.
42+
text: |
43+
az devops migrations create --org https://dev.azure.com/myorg --repository-id 00000000-0000-0000-0000-000000000000 --target-repository https://github.com/OrgName/RepoName --target-owner-user-id OwnerUserId --agent-pool MigrationPool
44+
- name: Create a validate-only migration.
45+
text: |
46+
az devops migrations create --org https://dev.azure.com/myorg --repository-id 00000000-0000-0000-0000-000000000000 --target-repository https://github.com/OrgName/RepoName --target-owner-user-id OwnerUserId --agent-pool MigrationPool --validate-only --skip-validation ActivePullRequestCount,PullRequestDeltaSize
47+
"""
48+
49+
helps['devops migrations pause'] = """
50+
type: command
51+
short-summary: Pause an active migration.
52+
"""
53+
54+
helps['devops migrations resume'] = """
55+
type: command
56+
short-summary: Resume a stopped (paused, failed) migration.
57+
examples:
58+
- name: Resume using the current mode.
59+
text: |
60+
az devops migrations resume --org https://dev.azure.com/myorg --repository-id 00000000-0000-0000-0000-000000000000
61+
- name: Resume in validate-only mode.
62+
text: |
63+
az devops migrations resume --org https://dev.azure.com/myorg --repository-id 00000000-0000-0000-0000-000000000000 --validate-only
64+
- name: Continue migration (clears validate-only mode).
65+
text: |
66+
az devops migrations resume --org https://dev.azure.com/myorg --repository-id 00000000-0000-0000-0000-000000000000 --migration
67+
"""
68+
69+
helps['devops migrations abandon'] = """
70+
type: command
71+
short-summary: Abandon and delete a migration.
72+
"""
73+
74+
helps['devops migrations cutover'] = """
75+
type: group
76+
short-summary: Manage migration cutover.
77+
"""
78+
79+
helps['devops migrations cutover set'] = """
80+
type: command
81+
short-summary: Schedule cutover for a migration.
82+
examples:
83+
- name: Schedule cutover.
84+
text: |
85+
az devops migrations cutover set --org https://dev.azure.com/myorg --repository-id 00000000-0000-0000-0000-000000000000 --date 2030-12-31T11:59:00Z
86+
"""
87+
88+
helps['devops migrations cutover cancel'] = """
89+
type: command
90+
short-summary: Cancel a scheduled cutover.
91+
"""
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# --------------------------------------------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License. See License.txt in the project root for license information.
4+
# --------------------------------------------------------------------------------------------
5+
6+
from azext_devops.dev.common.arguments import convert_date_string_to_iso8601
7+
from azext_devops.dev.team.arguments import load_global_args
8+
9+
10+
# pylint: disable=too-many-statements
11+
def load_migration_arguments(self, _):
12+
with self.argument_context('devops migrations') as context:
13+
load_global_args(context)
14+
context.argument('repository_id', options_list='--repository-id',
15+
help='ID of the Azure Repos repository (GUID).')
16+
17+
with self.argument_context('devops migrations list') as context:
18+
context.argument('include_inactive', options_list='--include-inactive', action='store_true',
19+
help='Include inactive (completed, abandoned, failed) migrations in the results.')
20+
context.argument('project', options_list='--project',
21+
help='Optional project name or ID to filter migrations.')
22+
23+
with self.argument_context('devops migrations create') as context:
24+
context.argument('target_repository', options_list='--target-repository',
25+
help='Target repository URL (must start with http:// or https://).')
26+
context.argument('target_owner_user_id', options_list='--target-owner-user-id',
27+
help='Target repository owner user ID.')
28+
context.argument('validate_only', options_list='--validate-only', action='store_true',
29+
help='Create in validate-only mode (pre-migration checks only).')
30+
context.argument('cutover_date', options_list='--cutover-date',
31+
type=convert_date_string_to_iso8601,
32+
help='Scheduled cutover date/time (ISO 8601).')
33+
context.argument('agent_pool', options_list='--agent-pool',
34+
help='Agent pool name to use for migration work.')
35+
context.argument('skip_validation', options_list='--skip-validation',
36+
help='Validation policies to skip. Accepts either a comma-separated list of '
37+
'policy names (for example, AgentPoolExists,MaxRepoSize) or a non-negative '
38+
'integer bitmask.')
39+
40+
with self.argument_context('devops migrations cutover set') as context:
41+
context.argument('cutover_date', options_list='--date',
42+
type=convert_date_string_to_iso8601,
43+
help='The date and time for cutover (ISO 8601).')
44+
45+
with self.argument_context('devops migrations resume') as context:
46+
context.argument('validate_only', options_list='--validate-only', action='store_true',
47+
help='Resume in validate-only mode.')
48+
context.argument('migration', options_list='--migration', action='store_true',
49+
help='Promote a succeeded validate-only migration to a full migration '
50+
'(sets validateOnly=false and statusRequested=active).')

0 commit comments

Comments
 (0)