Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions azure-devops/azext_devops/dev/migration/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,14 @@ def load_migration_help():
helps['devops migrations create'] = """
type: command
short-summary: Create a migration for a repository.
long-summary: 'If --github-token is not provided, the CLI checks ELM_GITHUB_TOKEN and then runs GitHub device flow to acquire a token.'
long-summary: 'GitHub authentication uses device flow: the CLI prints a URL and a one-time code to complete sign-in interactively. No GitHub token or service connection is required.'
examples:
- name: Create a migration.
text: |
az devops migrations create --org https://dev.azure.com/myorg --repository-id 00000000-0000-0000-0000-000000000000 --target-repository https://github.com/OrgName/RepoName --agent-pool <your-agent-pool>
- name: Create a validate-only migration.
text: |
az devops migrations create --org https://dev.azure.com/myorg --repository-id 00000000-0000-0000-0000-000000000000 --target-repository https://github.com/OrgName/RepoName --agent-pool <your-agent-pool> --validate-only --skip-validation ActivePullRequestCount,PullRequestDeltaSize
- name: Create using a pre-generated GitHub token or PAT.
text: |
az devops migrations create --org https://dev.azure.com/myorg --repository-id 00000000-0000-0000-0000-000000000000 --target-repository https://github.com/OrgName/RepoName --github-token <token>
"""

helps['devops migrations pause'] = """
Expand Down
12 changes: 0 additions & 12 deletions azure-devops/azext_devops/dev/migration/arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,6 @@ def load_migration_arguments(self, _):
context.argument('target_owner_user_id', options_list='--target-owner-user-id',
help='Target repository owner user ID. Deprecated and ignored when server-side '
'token-based owner resolution is enabled.')
context.argument('github_token', options_list='--github-token',
help='GitHub user token used for user-identity verification on the target '
'host. Independent of --service-endpoint-id. If omitted and '
'--service-endpoint-id is not provided, the CLI checks ELM_GITHUB_TOKEN '
'and then runs GitHub device flow. When --service-endpoint-id is '
'provided, device flow is skipped; pass --github-token or set '
'ELM_GITHUB_TOKEN to supply the user token.')
context.argument('validate_only', options_list='--validate-only', action='store_true',
help='Create in validate-only mode (pre-migration checks only).')
context.argument('cutover_date', options_list='--cutover-date',
Expand All @@ -91,11 +84,6 @@ def load_migration_arguments(self, _):
'MaxPullRequestSize, MaxPushPackSize, MaxReferenceNameLength, '
'TargetRepositoryDoesNotExist, SourceRepositoryContainsLfsObjects, '
'SourceRepositoryNotReadOnly, BoardsGitHubConnectionProvisioning, All.')
context.argument('service_endpoint_id', options_list='--service-endpoint-id',
help='Service endpoint ID (GUID) for the GitHub Enterprise Server connection '
'used to sync commits to the target. Independent of user-identity '
'verification: --github-token / ELM_GITHUB_TOKEN can be supplied '
'alongside this flag. Device flow is skipped when this flag is set.')
context.argument('enable_boards_github_connection',
options_list=['--enable-boards-github-connection', '--enable-boards-gh'],
action='store_true',
Expand Down
31 changes: 6 additions & 25 deletions azure-devops/azext_devops/dev/migration/migration.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
PIPELINES_API_PATH_SUFFIX = '/pipelines'
DEVICE_FLOW_CONFIG_API_PATH = '/_apis/migrations/deviceFlowConfig'
LEGACY_DEVICE_FLOW_CONFIG_API_PATH = '/_apis/elm/migrations/deviceFlowConfig'
GITHUB_TOKEN_ENV_VAR = 'ELM_GITHUB_TOKEN'
_SKIP_VALIDATION_POLICIES = {
'none': 0,
'activepullrequestcount': 1,
Expand Down Expand Up @@ -177,15 +176,13 @@ def get_migration(repository_id=None, organization=None, detect=None):

def create_migration(*, repository_id=None, target_repository=None, target_owner_user_id=None,
validate_only=False, cutover_date=None, agent_pool=None,
skip_validation=None, service_endpoint_id=None, github_token=None,
skip_validation=None,
enable_boards_github_connection=False, enable_auto_discover_pipelines=False,
pipeline_service_connection_id=None,
organization=None, detect=None):
target_repository = _normalize_optional_text(target_repository)
target_owner_user_id = _normalize_optional_text(target_owner_user_id)
agent_pool = _normalize_optional_text(agent_pool)
service_endpoint_id = _normalize_optional_text(service_endpoint_id)
github_token = _normalize_optional_text(github_token)
skip_validation = _parse_skip_validation(skip_validation)

if not target_repository:
Expand All @@ -202,14 +199,7 @@ def create_migration(*, repository_id=None, target_repository=None, target_owner
organization = _resolve_org_for_auth(organization, detect)
repository_id = _resolve_repository_id(repository_id)
client = _get_service_client(organization)
if not service_endpoint_id:
github_token = _resolve_github_user_token(client, organization, target_repository, github_token)
else:
# SE supplies the GitHub credential used to sync commits. User-identity
# verification (gitHubUserToken) is independent: accept an explicit
# --github-token or ELM_GITHUB_TOKEN env var, but do not trigger device
# flow here so non-interactive SE-based flows aren't broken.
github_token = github_token or _normalize_optional_text(os.getenv(GITHUB_TOKEN_ENV_VAR))
github_token = _resolve_github_user_token(client, organization, target_repository)

payload = {
'targetRepository': target_repository,
Expand All @@ -225,8 +215,6 @@ def create_migration(*, repository_id=None, target_repository=None, target_owner
payload['scheduledCutoverDate'] = cutover_date
if skip_validation is not None:
payload['skipValidation'] = skip_validation
if service_endpoint_id:
payload['serviceEndpointId'] = service_endpoint_id
config_options = {}
if enable_boards_github_connection:
config_options['enableBoardsGitHubConnection'] = True
Expand All @@ -251,15 +239,7 @@ def create_migration(*, repository_id=None, target_repository=None, target_owner
raise


def _resolve_github_user_token(client, organization, target_repository, github_token=None):
token = _normalize_optional_text(github_token)
if token:
return token

env_token = _normalize_optional_text(os.getenv(GITHUB_TOKEN_ENV_VAR))
if env_token:
return env_token

def _resolve_github_user_token(client, organization, target_repository):
flow_config = _get_device_flow_config(client, organization, target_repository)
client_id = _normalize_optional_text(flow_config.get('clientId'))
enterprise_url = _normalize_optional_text(flow_config.get('enterpriseUrl'))
Expand All @@ -285,7 +265,8 @@ def _get_device_flow_config(client, organization, target_repository):
continue
if index == 1 and first_error and 'status 404' in str(ex):
raise CLIError('GitHub device-flow configuration is unavailable. '
'Provide --github-token or set ELM_GITHUB_TOKEN to continue.')
'Ensure the GitHub app is installed for the target '
'organization, then try again.')
raise

if first_error:
Expand Down Expand Up @@ -394,7 +375,7 @@ def _post_form(url, data):
if ex.code in (401, 403):
raise CLIError('GitHub device flow is unavailable for this organization. '
'This can happen if the GitHub app is not installed or the service is unavailable. '
'Try again later, or provide --github-token (or set ELM_GITHUB_TOKEN).')
'Try again later.')
detail = ''
try:
content = ex.read()
Expand Down
Loading
Loading