Skip to content

Commit 2e6b07c

Browse files
committed
Add ELM migrations commands
1 parent a4a2615 commit 2e6b07c

12 files changed

Lines changed: 631 additions & 1 deletion

File tree

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: 5 additions & 1 deletion
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
@@ -48,7 +52,7 @@ def load_arguments(self, command):
4852
@staticmethod
4953
def post_parse_args(_cli_ctx, **kwargs):
5054
if (kwargs.get('command', None) and
51-
kwargs['command'].startswith(('devops', 'boards', 'artifacts', 'pipelines', 'repos'))):
55+
kwargs['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
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') or row.get('repositoryID') or row.get('repository')
38+
table_row['TargetRepository'] = trim_for_display(row.get('targetRepo') or row.get('targetRepository'),
39+
_TARGET_TRUNCATION_LENGTH)
40+
table_row['State'] = row.get('state')
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('cutoverDate') or 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('prSyncDate'))
46+
return table_row
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
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.
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://codedev.ms/elmo1
23+
"""
24+
25+
helps['devops migrations status'] = """
26+
type: command
27+
short-summary: Get migration status for a repository.
28+
examples:
29+
- name: Get migration status by repository id.
30+
text: |
31+
az devops migrations status --org https://codedev.ms/elmo1 --repository-id 00000000-0000-0000-0000-000000000000
32+
"""
33+
34+
helps['devops migrations create'] = """
35+
type: command
36+
short-summary: Create a migration for a repository.
37+
examples:
38+
- name: Create a validation-only migration.
39+
text: |
40+
az devops migrations create --org https://codedev.ms/elmo1 --repository-id 00000000-0000-0000-0000-000000000000 \
41+
--target-repository https://microsoft.ghe.com/1ES/Gardener --target-owner-user-id GeoffCoxMSFT --validate-only
42+
"""
43+
44+
helps['devops migrations pause'] = """
45+
type: command
46+
short-summary: Pause an active migration.
47+
"""
48+
49+
helps['devops migrations resume'] = """
50+
type: command
51+
short-summary: Resume a paused migration.
52+
"""
53+
54+
helps['devops migrations abandon'] = """
55+
type: command
56+
short-summary: Abandon and delete a migration.
57+
"""
58+
59+
helps['devops migrations set-validate-only'] = """
60+
type: command
61+
short-summary: Set validate-only mode on or off.
62+
examples:
63+
- name: Turn validate-only on.
64+
text: |
65+
az devops migrations set-validate-only --org https://codedev.ms/elmo1 --repository-id 00000000-0000-0000-0000-000000000000 --on
66+
- name: Turn validate-only off.
67+
text: |
68+
az devops migrations set-validate-only --org https://codedev.ms/elmo1 --repository-id 00000000-0000-0000-0000-000000000000 --off
69+
"""
70+
71+
helps['devops migrations migrate'] = """
72+
type: command
73+
short-summary: Start full migration after validation.
74+
"""
75+
76+
helps['devops migrations cutover'] = """
77+
type: group
78+
short-summary: Manage migration cutover.
79+
"""
80+
81+
helps['devops migrations cutover set'] = """
82+
type: command
83+
short-summary: Schedule cutover for a migration.
84+
examples:
85+
- name: Schedule cutover.
86+
text: |
87+
az devops migrations cutover set --org https://codedev.ms/elmo1 --repository-id 00000000-0000-0000-0000-000000000000 \
88+
--scheduled-cutover-date 2030-12-31T11:59:00Z
89+
"""
90+
91+
helps['devops migrations cutover cancel'] = """
92+
type: command
93+
short-summary: Cancel a scheduled cutover.
94+
"""
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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 azure.cli.core.commands.parameters import get_three_state_flag
7+
from azext_devops.dev.common.arguments import convert_date_string_to_iso8601
8+
from azext_devops.dev.team.arguments import load_global_args
9+
10+
11+
# pylint: disable=too-many-statements
12+
def load_migration_arguments(self, _):
13+
with self.argument_context('devops migrations') as context:
14+
load_global_args(context)
15+
context.argument('repository_id', options_list='--repository-id',
16+
help='ID of the repository (GUID).')
17+
18+
with self.argument_context('devops migrations create') as context:
19+
context.argument('target_repository', options_list='--target-repository',
20+
help='Target GitHub repository URL. Example: https://microsoft.ghe.com/OrgName/RepoName')
21+
context.argument('target_owner_user_id', options_list='--target-owner-user-id',
22+
help='Target repository owner user ID.')
23+
context.argument('validate_only', options_list='--validate-only',
24+
help='Validate only (true/false). Defaults to true.',
25+
arg_type=get_three_state_flag())
26+
context.argument('scheduled_cutover_date', options_list='--scheduled-cutover-date',
27+
type=convert_date_string_to_iso8601,
28+
help='Scheduled cutover date/time (ISO 8601).')
29+
30+
with self.argument_context('devops migrations cutover set') as context:
31+
context.argument('scheduled_cutover_date', options_list='--scheduled-cutover-date',
32+
type=convert_date_string_to_iso8601,
33+
help='Scheduled cutover date/time (ISO 8601).')
34+
35+
with self.argument_context('devops migrations set-validate-only') as context:
36+
context.argument('on', options_list='--on', action='store_true',
37+
help='Set validate-only to true.')
38+
context.argument('off', options_list='--off', action='store_true',
39+
help='Set validate-only to false.')
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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 azure.cli.core.commands import CliCommandType
7+
from azext_devops.dev.common.exception_handler import azure_devops_exception_handler
8+
from ._format import transform_migrations_table_output, transform_migration_table_output
9+
10+
11+
migrationOps = CliCommandType(
12+
operations_tmpl='azext_devops.dev.migration.migration#{}',
13+
exception_handler=azure_devops_exception_handler
14+
)
15+
16+
17+
def load_migration_commands(self, _):
18+
with self.command_group('devops migrations', command_type=migrationOps) as g:
19+
g.command('list', 'list_migrations', table_transformer=transform_migrations_table_output)
20+
g.command('status', 'get_migration', table_transformer=transform_migration_table_output)
21+
g.command('create', 'create_migration', table_transformer=transform_migration_table_output)
22+
g.command('pause', 'pause_migration', table_transformer=transform_migration_table_output)
23+
g.command('resume', 'resume_migration', table_transformer=transform_migration_table_output)
24+
g.command('set-validate-only', 'set_validate_only', table_transformer=transform_migration_table_output)
25+
g.command('migrate', 'migrate_migration', table_transformer=transform_migration_table_output)
26+
g.command('abandon', 'delete_migration',
27+
confirmation='Are you sure you want to abandon this migration?')
28+
29+
with self.command_group('devops migrations cutover', command_type=migrationOps) as g:
30+
g.command('set', 'schedule_cutover', table_transformer=transform_migration_table_output)
31+
g.command('cancel', 'cancel_cutover', table_transformer=transform_migration_table_output)

0 commit comments

Comments
 (0)