Authoring Command Modules
=========================

The document provides instructions and guidelines on how to author command modules. For other help, please see the following:

**On-boarding Guide**:<br>https://github.com/Azure/azure-cli/blob/dev/doc/onboarding_guide.md

**Module Authoring**:<br>You are here!

**Command Authoring**:<br>https://github.com/Azure/azure-cli/blob/dev/doc/authoring_command_modules/authoring_commands.md

**Command Guidelines**:<br>https://github.com/Azure/azure-cli/blob/dev/doc/command_guidelines.md

**Help Authoring**:<br>https://github.com/Azure/azure-cli/blob/dev/doc/authoring_help.md

**Test Authoring**:<br>https://github.com/Azure/azure-cli/blob/dev/doc/authoring_tests.md

**Generating Documentation**:<br>https://review.docs.microsoft.com/help/onboard/admin/reference/cli/azure-cli-ci?branch=master#documenting-a-new-azure-cli-module

<a name="heading_set_up"></a>Set Up
------

Create you dev environment if you haven't already. This is how to do that.  

Clone the repo, enter the repo directory then create your virtual environment.  

For example:
```
git clone https://github.com/Azure/azure-cli.git
cd azure-cli
virtualenv env
source env/bin/activate
python scripts/dev_setup.py
```

After this, you should be able to run the CLI with `az`.

[Author your command module...](#heading_author_command_mod)

Now, install your command module into the environment with pip in editable mode.  
Where `<path_to_your_command_module>` is the path to the directory containing your `setup.py` file.
```
pip install -e <path_to_your_command_module>
```

If installation was successful, you should be able to run `pip list` and see your command module.
```
$ pip list
...
azure-cli-example (0.0.1, /Users/myuser/Repos/azure-cli-example)
...
```

Also, you can run `az` and if your command module contributes any commands, they should appear.
If your commands aren't showing with `az`, use `az --debug` to help debug. There could have been an exception
thrown whilst attempting to load your module.

**Note for locally installed Azure SDKs:**  
If you're receiving Azure import errors, run `pip install -I azure-nspkg==1.0.0` in your virtual environment
after installing your SDK locally.


<a name="heading_author_command_mod"></a>Authoring command modules
------
Currently, all command modules should start with `azure-cli-`.  
When the CLI loads, it search for packages installed that start with that prefix.

The `example_module_template` directory gives an example command module with other useful examples.

Command modules should have the following structure:
```
.
|-- README.rst
|-- azure
|   |-- __init__.py
|   `-- cli
|       |-- __init__.py
|       `-- command_modules
|           |-- __init__.py
|           `-- <MODULE_NAME>
|               `-- __init__.py
`-- setup.py
`-- HISTORY.rst
```

**Create an \_\_init__.py for your module**

In the \_\_init__ file, you will declare a command loader class that inherits from AzCommandsLoader. You will typically override the following three methods:
  - `__init__` - Useful for setting metadata that applies to the entire module. For performance reasons, no heavy processing should be done here. See command authoring for more info.
  - `load_commands_table` - Register command groups and commands here. It is common to store the implementation of this method in
                            a file named `commands.py` but for very small modules this may not be necessary. See command authoring for
                            more info.
  - `load_arguments` - Apply metadata to your command arguments. It is common to store the implementation of this method in a file 
                       named `_params.py` but for very small modules this may not be necessary. See command authoring for more info.

**__init__.py**
```Python
from azure.cli.core import AzCommandsLoader
from azure.cli.command_modules.mymod._help import helps  # pylint: disable=unused-import

class MyModCommandsLoader(AzCommandsLoader):

    def load_command_table(self, args):
      from azure.cli.core.commands import CliCommandType

      mymod_custom = CliCommandType(
        operations_tmpl='azure.mgmt.mymod.operations#MyModOperations.{}',
      )

      with self.command_group('myfoo', mymod_custom) as g:
        g.command('create', 'create_myfoo')

COMMAND_LOADER_CLS = MyModCommandsLoader
```

**custom.py**
```python
def create_myfoo(cmd, myfoo_name, resource_group_name, location=None):
    from azure.mgmt.example.models import MyFoo
    from azure.cli.command_modules.example._client_factory import cf_mymod
    client = cf_mymod(cmd.cli_ctx)

    foo = MyFoo(location=location)
    return client.create_or_update(myfoo_name, resource_group_name, foo)
```

The snippet above shows what it takes to author a basic command.
1. Create a CliCommandType which holds the metadata for your command. 
2. Create a command group in which your command will exist, passing the command type created in the previous step.
3. Register your command with the `command` method, defining first the name of the command and then the name of the method which will execute.
4. Define the callable that will execute:
    The CLI inspects the callable to determine required params, defaults and help text and more.  
    Try out the example to see these in action!

When running the command with the `--help` flag, you should see the command.
You can also now execute the command for yourself.
```
$ az myfoo create --help

Command
    az myfoo create

Arguments
    --myfoo-name          [Required]: The argument that is required.
    --resource-group-name [Required]: Also required.
    --location                      : Optional arg.
...

$ az myfoo create --myfoo-name foo --resource-group-name myrg
{
  "name": "foo",
  "resourceGroup": "myrg",
  "location": None
}
```

Testing
-------

Discover tests

```
azdev test --discover
```

Run all tests in a module:

```
azdev test MODULE [--live] [--series] [--discover] [--dest-file FILENAME]
```

Run an individual test:

```
azdev test TEST [TEST ...] [--live] [--series] [--discover] [--dest-file FILENAME]
```
For example `azdev test test_myfoo`

Run a test when there is a conflict (for example, both 'azure-cli-core' and 'azure-cli-network' have 'test_foo'):
```
azdev test MODULE.TEST [--live]
```

The list of failed tests are displayed at the end of a run and dumped to the file specified with `--dest-file` or `test_failures.txt` if nothing is provided. This allows for conveniently replaying failed tests:

```
azdev test --src-file test_failures.txt [--live] [--series] [--discover]
```

Relying on the default filename, the list of failed tests should grow shorter as you fix the cause of the failures until there are no more failing tests.

Style Checks
------------

```
azdev style --module <module> [--pylint] [--pep8]
```

Submitting Pull Requests
------------------------

### Format PR Title

History notes are auto-generated based on commit messages and descriptions starting from [S165](https://github.com/Azure/azure-cli/milestone/82). The commmit message for the squashed merge commit defaults to the PR title. Starting from 01/30/2020, we require all the PR titles to follow the below format:
1. [**Mandatory**] Each commit message must start with `[Component Name]` or `{Component Name}`. 
    * `Component Name` shall be replaced by the real ones such as `Storage`, `Compute`. It could be the name of a command module, but in title case with necessary spaces for better readability, such as `API Management`, `Managed Service`. Other possible component names include but are not limited to: `Packaging`, `Misc.`, `Aladdin`.
    * `[]` means this change is customer-facing and the message will be put into `HISTORY.rst`. `{}` means this change is not customer-facing and the message will **NOT** be included in `HISTORY.rst`.
    * If the component name is `Core`, the message will be written in `src/azure-cli-core/HISTORY.rst`. Otherwise, the message will be written in `src/azure-cli/HISTORY.rst`.
2. [**Mandatory**] If it's a breaking change, the second part should be `BREAKING CHANGE` followed by a colon. In the case of hotfix, put `Hotfix` in this part. For other cases, this part should be empty.
3. [**Recommendation**] If the change can be mapped into a command, then the next part could be the command name, followed by a colon.
4. [**Recommendation**] Use the right verb with present-tense in original form to descibe what is done:
    * **Add** for new features.
    * **Change** for changes in existing functionality.
    * **Deprecate** for once-stable features removed in upcoming releases.
    * **Remove** for deprecated features removed in this release.
    * **Fix** for any bug fixes.

An example title of customer-facing change PR:

>[Storage] BREAKING CHANGE: az storage remove: remove --auth-mode argument

An example title of non-customer-facing change PR:

>{Aladdin} Add help example for dns

### Format PR Description

If you would like to write multiple history notes for one PR, please write the notes under `History Notes` section in the PR description,  following the same format described above. In this case, the PR title should be a summary of all the changes in this PR and will not be put into `HISTORY.rst`.

Other than that, you can put any reasonable information in the description above the `History Notes` part.

### Only Modify HISTORY.rst for Hotfix
As described above, history notes will be auto-generated and inserted into `HISTORY.rst` during release. When submitting a normal PR, you don't need to modify `HISTORY.rst` manually. In the case of hotfix, you should create a hotfix branch based on release branch and submit a PR to merge hotfix into release. In this PR, if you have customer-facing changes, you need to modify `HISTORY.rst` to add history notes. Later on you also need to merge the hotfix branch into dev branch and the second part of the PR title should be `Hotfix`. The auto-generation of history notes for the next release will ignore the commit that contains `Hotfix`.

An example title of hotfix change PR:

>{Packaging} Hotfix: fix dependency error
