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
93 changes: 93 additions & 0 deletions src/migrate/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Principal Engineer Review Mode — migrate extension

This file is a standing instruction for **any** agent doing coding work anywhere under
`src/migrate/`. Treat every change as if you are the **principal engineer** who must approve
the pull request. Do not merely make code work — make it the code a principal engineer would
sign off on.

## Non-negotiable review discipline

Before finishing ANY migrate task, rigorously self-review against these criteria and reject
your own work if it fails:

1. **Simplicity** — Is this the simplest solution that fully solves the problem? Remove any
complexity that does not earn its place.
2. **Reuse first** — Prefer existing helpers, patterns, and abstractions over new ones. Search
before you write. (`shared/`, `runbook/`, existing `ArmClient`/`files` patterns.)
3. **Architecture fit** — The change must match the established structure (REST via `ArmClient`,
`shared/files.py` for archive/IO, `runbook/cmds/*` for command logic, `transformers.py` for
table shaping). No parallel or competing mechanisms.
4. **No speculative code** — Do not add constants, parameters, branches, or error handling for
cases that cannot occur or are unproven. Validate only at real system boundaries.
5. **No duplicate logic** — Collapse repeated iterate/parse/classify/format loops into a single
source of truth. Duplication is a defect.
6. **Root-cause fixes only** — Fix the underlying cause, never paper over a symptom. State the
root cause explicitly in your summary.
7. **Net code growth** — Prefer changes that remove more than they add. Justify every new
abstraction with a concrete, present-day need and a net-complexity benefit.
8. **Security by design** — Prefer designing hazards out (e.g. flatten to basename to eliminate
zip-slip) over runtime guards. Keep the OWASP Top 10 in mind for every I/O boundary.
9. **Maintenance score** — Rate the resulting code 1–10 for maintainability. Do not ship below
**9**. If below 9, keep simplifying.
10. **PR approval test** — Ask: "Would I approve this PR as principal engineer?" If not, revise.

## Mandatory concluding deliverable

Every non-trivial migrate change MUST end with a **10-point engineering review** covering:

1. Selected design and why it won.
2. Alternatives considered and why they were rejected.
3. What existing code was reused.
4. What was refactored/consolidated.
5. Duplicate logic removed.
6. New abstractions introduced and their justification.
7. Net lines added vs. removed.
8. Remaining technical debt (with explicit `TODO(confirm)` where behavior is unverified).
9. Maintenance score (1–10) with rationale.
10. Why this is the simplest correct solution.

## Verification gate (always run before declaring done)

- `python -m pytest migrate/azext_migrate/tests/latest/runbook/test_runbook_unit.py -q`
- `python -m azdev style migrate`
- `python -m azdev linter migrate` (the trailing `ERROR: invalid git repo: None` is harmless)

## Tests move with the code — never leave a reconciliation gap

Code and its tests are ONE change. A task is not done until the tests that cover the changed
behavior are updated in the SAME change and the suite is green.

- **Every code change updates its tests in lockstep.** If you change a contract (request body,
command signature, transformer columns, file/archive handling, action verb, call kwargs), update
the covering unit/scenario tests in the same edit. Never defer test updates to "later" or to a
separate reconciliation pass.
- **Green-before-done.** Run the unit suite (see Verification gate) and confirm it passes before
declaring any change complete. A change that leaves failing tests is an unfinished change.
- **Tests must load the source under `src/migrate/azext_migrate/`, not build artifacts.** Run
pytest with `cwd = src/migrate`. A stale `build/lib/azext_migrate` copy can shadow/merge with the
source package and mask source/test drift (a suite may appear to pass against the stale copy).
If collection counts look inflated or failures vanish inexplicably, delete `src/migrate/build/`
(a regenerable artifact) and clear `__pycache__`, then re-run against source.
- **Root cause of drift:** code advanced while its tests were not updated in the same change. Do not
recreate that state. When source and tests disagree, the source is authoritative only because it
was reviewed — still confirm the current behavior is intended before aligning the test to it.

## Domain facts to preserve

- Downloaded runbook archive members:
- `runbook.json` → the **definition** (`{"runbookSpec": {...}}`).
- `user-input(s).json` → the **parameters** (`{"runbookInputs": {...}}`). `definition download`
writes this alongside the definition (per-step `configurationStatus` is derived from it), but
table/CLI output (`show`, `visualize` grid) still renders the definition only.
- `derived-input(s).json` → same shape as user-inputs; **never downloaded/rendered** by any CLI.
It is distinguishable from user-inputs ONLY by filename, so it is excluded by name.
- Archive members are classified by **content**, not filename suffix (member naming varies across
services, e.g. `rb-<name>-spec.json` vs `runbook.json`). See `shared/files.py::_classify_archive`
as the single source of truth.
- **UpdateStep/AddStep `dependsOn` write contract (verified against live service):** each entry is a
System.Text.Json polymorphic `RunbookStepDependency`. The discriminator property is the verbatim
(non-camelCased) `"Mode"` whose value is the integer enum ordinal (`0` = step gate,
`1` = migration-entity gate), and it must appear first. A `--depends-on <stepId>` maps to
`{"Mode": 0, "stepId": "<id>"}`. See `models.py::_depends_on_refs`. NOTE: the GET (read) model
differs — it emits `{"step": "<id>", "mode": "migrationEntity"}` (property `step`, string `mode`).
Read/write are NOT symmetric; do not assume round-trip.
16 changes: 16 additions & 0 deletions src/migrate/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@

Release History
===============
3.0.0b6
+++++++++++++++
* Add ``az migrate runbook`` commands (generate, show, list, update,
regenerate, delete, wait).
* Add ``az migrate runbook definition`` commands (show, download,
visualize).
* Add ``az migrate runbook definition step`` commands (add, update,
remove) and ``az migrate runbook definition workstream`` commands
(split, merge).
* Add ``az migrate runbook parameter`` and
``az migrate runbook execution parameter`` commands (download, upload).
* Add ``az migrate runbook execution`` commands (start, show, list,
pause, resume, cancel, visualize).
* Add ``az migrate runbook execution step`` commands (retry, approve,
complete).

3.0.0b5
+++++++++++++++
* Change migrate command parameter name.
Expand Down
5 changes: 5 additions & 0 deletions src/migrate/azext_migrate/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,16 @@ def load_command_table(self, args):
args=args
)
load_command_table(self, args)
from azext_migrate.runbook.commands import (
load_runbook_command_table)
load_runbook_command_table(self)
return self.command_table

def load_arguments(self, command):
from azext_migrate._params import load_arguments
load_arguments(self, command)
from azext_migrate.runbook.params import load_runbook_arguments
load_runbook_arguments(self, command)


COMMAND_LOADER_CLS = MigrateCommandsLoader
1 change: 1 addition & 0 deletions src/migrate/azext_migrate/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# --------------------------------------------------------------------------------------------
# coding=utf-8
from knack.help_files import helps # pylint: disable=unused-import
from azext_migrate.runbook import _help as _runbook_help # noqa: F401


helps['migrate'] = """
Expand Down
2 changes: 1 addition & 1 deletion src/migrate/azext_migrate/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

def load_arguments(self, _):
project_name_type = CLIArgumentType(
options_list=['--project-name'],
options_list=['--project-name', '-p'],
help='Name of the Azure Migrate project.',
id_part='name'
)
Expand Down
4 changes: 4 additions & 0 deletions src/migrate/azext_migrate/runbook/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------
Loading
Loading