NASA-AMMOS / aerie-cli

An unofficial CLI for interacting with Aerie planning software
MIT License
3 stars 4 forks source link

Aerie-CLI

Aerie-CLI provides a command-line interface and user-extendable Python API for interacting with an instance of Aerie.

Note: this project is an informal CLI and is not maintained by the MPSA Aerie team.

Getting Started

This short procedure will get you up and running with the basics of the CLI.

  1. Install/update to Python >= 3.6

  2. Install Aerie-CLI from Github:

    python3 -m pip install git+https://github.com/NASA-AMMOS/aerie-cli.git@main
  3. Configure access to an Aerie host

    1. If you've been provided a Configuration JSON, reference that file

    2. If you don't have already have a Configuration JSON, copy the following to a JSON file for a local Aerie deployment (replacing the username with your own):

      [
      {
        "name": "localhost",
        "graphql_url": "http://localhost:8080/v1/graphql",
        "gateway_url": "http://localhost:9000",
        "username": "my_username"
      }
      ]
    3. Load either your given configuration(s) or the configuration above into Aerie-CLI:

      aerie-cli configurations load -i JSON_FILE
  4. Activate a configuration to start a session with an Aerie host:

    ➜  aerie-cli activate
      1) localhost
      Select an option: 1
  5. Try out a command to list the plans in Aerie:

    aerie-cli plans list
  6. Use the --help flag on any command to see available subcommands and parameters. For example:

    aerie-cli --help
    ...
    aerie-cli plans --help
    ...
    aerie-cli plans download --help

CLI Usage

Setup

Aerie-CLI uses configurations to define different Aerie hosts. Define configurations by either loading JSON configurations or manually via the CLI. Configurations persist on a per-user basis and may be shared between installations.

Defining Hosts with a Configuration File

If you have a file of configurations to load, you can use the configurations load command:

aerie-cli configurations load -i JSON_FILE

You can view the configurations you've loaded with the configurations list command:

➜  aerie-cli configurations list
Configuration file location: /Users/<username>/Library/Application Support/aerie_cli/config.json

                                         Aerie Host Configurations
┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┓
┃ Host Name ┃ GraphQL API URL                  ┃ Aerie Gateway URL     ┃ Username ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━┩
│ localhost │ http://localhost:8080/v1/graphql │ http://localhost:9000 │          │
└───────────┴──────────────────────────────────┴───────────────────────┴──────────┘

Defining hosts via the CLI

If you haven't been provided a JSON configuration for a host, you can create a configuration by running aerie-cli configurations create and follow the prompts.

Full Specification

Each configuration is stored as JSON object list entry in the configuration file provided with the configurations list command. The full contents of a host configuration are:

Field Description Required
name User-facing name of the host Yes
graphql_url URL of the Aerie instance's Hasura GraphQL API Yes
gateway_url URL of the Aerie instance's Gateway Yes
username Username for authentication with Aerie No
external_auth Specification for external authentication required to reach an Aerie instance. See Configuring for External Authentication for details No

Sessions and Roles

Aerie-CLI maintains a persistent "session" with an Aerie instance so multiple commands can run without needing to re-authenticate. To start a session, use the activate command:

➜  aerie-cli activate
    1) localhost
    ...
    Select an option: 1

Aerie uses "roles" to adjust what a client is permitted to do. To view the active configuration name and current role, use the status command:

➜  aerie-cli status
Active configuration: localhost
Active role: viewer

The default role is configured by Aerie. To change the selected role for the active Aerie-CLI session, use the role command:

➜  aerie-cli role
Active Role: viewer
    1) aerie_admin
    2) user
    3) viewer
Select an option: 1
Changed role to: aerie_admin

At any time, the active session can be closed with the deactivate command.

Commands

Commands are the main functions available via the CLI and are broken down into several levels. For example, the top-level plans command has sub-commands for list, upload, simulate, and more. From any command or sub-command, use the --help flag to learn about what commands are available or what arguments are required.

Help at script level:

aerie-cli --help

Help at command level:

aerie-cli plans --help

Help at sub-command level:

aerie-cli plans download --help

Interactive vs. Non-Interactive

If a command is invoked without the necessary arugments, interactive prompts are provided:

➜  aerie-cli plans download
    Id: 42
    Output: sample-output.json

Alternatively, arguments can be provided using flags:

➜  aerie-cli plans download --id 42 --output sample-output.json

Advanced Topics

Configuring for External Authentication

Aerie-CLI configurations include a mechanism to authenticate against an external authentication service which may require additional credentials as cookies for accessing Aerie. Aerie-CLI will issue a post request with given JSON data to a provided authentication endpoint and persist any returned cookies in a browser-like manner for the remainder of the Aerie-CLI session.

An external authentication service is configured using the key external_auth in the JSON configuration file as follows:


  {
    "name": "my_host",
    "graphql_url": "https://hostname/v1/graphql",
    "gateway_url": "https://hostname/gateway",
    "username": "my_username",
    "external_auth": {
      "auth_url": "https://auth_service/route",
      "static_post_vars": {
        "username": "my_username"
      },
      "secret_post_vars": [
        "password"
      ]
    }
  }

Here, static_post_vars is an object containing fixed values to include in the post request payload such as usernames and other persistent, non-sensitive fields. secret_post_vars is a list of keys for credentials which may be sensitive or time-varying. The user will be prompted to provide the "secret" values using hidden entry in the terminal when activating a session with external authentication.

In this example, the user would be prompted to enter a value for "password" and, assuming they enter "my_password", the post request JSON would include the following:

{
  "username": "my_username",
  "password": "my_password"
}

Using a Hasura Admin Secret

In some cases, an admin secret may be used to permit otherwise prohibited requests through Hasura (the software behind the Aerie API). When running a command, the user may add the --hasura-admin-secret flag after the aerie-cli command to use these elevated privileges for the following command.


Python API

Quickstart Guide

Instead of using the CLI for interactive use cases, the underlying classes and methods behind Aerie-CLI can be invoked directly in Python scripts.

The key constructs are:

The following example defines an AerieHost using the necessary URLs, authenticates with a command-line prompt for the user's password, and issues a simple request using one of the built-in requests.

from aerie_cli.aerie_client import AerieClient
from aerie_cli.aerie_host import AerieHost

from getpass import getpass

# These URLs define the Aerie host
GRAPHQL_URL = "http://myhostname:8080/v1/graphql"
GATEWAY_URL = "http://myhostname:9000"

# User credentials. The password may be omitted on Aerie instances with authentication disabled
USERNAME = "myusername"
PASSWORD = getpass(prompt='Password: ')

# Define the Aerie host and provide user credentials
aerie_host = AerieHost(GRAPHQL_URL, GATEWAY_URL)
aerie_host.authenticate(USERNAME, PASSWORD)

# AerieClient takes in a host and returns an object to issue requests to that host
client = AerieClient(aerie_host)

# Simple example of a request to get an activity plan using the plan ID
plan = client.get_activity_plan_by_id(42)
print(plan.name)

Look through the available methods in the provided AerieClient class to find ones that suit your needs.

Adding Methods

If you need to write a custom query, you can extend the AerieClient class and add your own method. Access the Aerie host using the aerie_host property. For example:


# ...

class MyCustomAerieClient(AerieClient):
    def get_plan_id_by_name(self, plan_name: str) -> int:
        my_query = """
        query GetPlanIdByName($plan_name: String!) {
            plan(where: { name: { _eq: $plan_name } }) {
                id
            }
        }
        """

        # Pass variables for the query as keyword arguments
        resp = self.aerie_host.post_to_graphql(
            my_query,
            plan_name=plan_name
        )
        return resp[0]["id"]

Now, you can use your custom method like any other:

# ...
client = MyCustomAerieClient(aerie_host)
plan_id = client.get_plan_id_by_name("my-plan-name")
print(plan_id)

Using the Active CLI Session

If your application will be run by a user who may also be using the CLI, you may reduce the amount of code required to configure an Aerie host and instead just use the active session. Aerie-CLI provides a utility to retrieve an AerieClient instance from the active CLI session:

from aerie_cli.utils.sessions import get_active_session_client

# client is an instance of `AerieClient`
client = get_active_session_client()

# Issue requests like normal
plan = client.get_activity_plan_by_id(...)

Advanced Authentication

If you have needs for authentication (e.g., a custom token system) that aren't provided by Aerie-CLI, you can use any features supported by the Python requests module's Session class. Instantiate a session object, manipulate/add headers/cookies/SSL certificates/etc. as necessary, and use to instantiate an AerieHostSession:

# ...
from requests import Session

my_custom_requests_session = Session()
# Manipulate as necessary
# ...

aerie_host = AerieHost(
    GRAPHQL_URL,
    GATEWAY_URL,
    session=my_custom_requests_session
)
aerie_host.authenticate(...)
client = AerieClient(aerie_host)

# Use client as normal

Contributing

Contributor Installation

The recommended develper installation method is in a virutual environment with an editatable install via Pip.

See this primer or virtual environments to get set up. A quick and easy method is using Python's native venv package from the root of the Aerie-CLI repo:

python3 -m venv venv
source venv/bin/activate

Then, install Aerie-CLI in editable mode via Pip:

python3 -m pip install -e .

Now, your installation of Aerie-CLI will update as you make changes to the source code.

Dependency Management

Dependencies are currently managed via Poetry. For more information on dependency and project management, see the poetry docs.

Testing

Aerie-CLI has unit tests and integration tests built with the pytest library.

Unit Tests

Unit tests can be run anytime and reference local test files. test_aerie_client.py is where unit tests are added to exercise particular methods of the AerieClient class using mocked Aerie API responses.

Run the unit tests using pytest from the tests/unit_tests directory:

cd tests
python3 -m pytest .

Integration Tests

A separate suite of tests is designed to validate CLI functionality against a local instance of Aerie. See the integration testing documentation for details.

The integration tests are based on Typer testing documentation found here.

Releases

Aerie-CLI generally follows the gitflow workflow model for managing releases:

Version numbers are managed using semantic versioning in pyproject.toml:

The full workflow for releasing a new version of Aerie-CLI: