Skip to content
Open
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
54 changes: 54 additions & 0 deletions examples/cli_examples/msp/msp_add.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/usr/bin/env python3
# _ __
# | |/ /___ ___ _ __ ___ _ _ ®
# | ' </ -_) -_) '_ \/ -_) '_|
# |_|\_\___\___| .__/\___|_|
# |_|
#
# Keeper CLI for Python
# Copyright 2025 Keeper Security Inc.
# Contact: commander@keepersecurity.com
#
# Example: msp-add — add a managed company to the MSP tenant.
#

import logging
from typing import List, Optional

from keepercli.commands.msp import MspAddCommand

from msp_common import run_example

logging.basicConfig(level=logging.INFO, format='%(message)s')

# Edit before running.
MC_NAME = 'CLI MSP Example MC'
PLAN = 'business' # business, businessPlus, enterprise, enterprisePlus
SEATS: Optional[int] = 10
NODE: Optional[str] = None # node name or ID; None = enterprise root
FILE_PLAN: Optional[str] = None
ADDONS: Optional[List[str]] = None # e.g. ['connection_manager:25']


def main(context):
kwargs = {
'name': MC_NAME,
'plan': PLAN,
}
if SEATS is not None:
kwargs['seats'] = SEATS
if NODE:
kwargs['node'] = NODE
if FILE_PLAN:
kwargs['file_plan'] = FILE_PLAN
if ADDONS:
kwargs['addon'] = ADDONS
MspAddCommand().execute(context=context, **kwargs)


if __name__ == '__main__':
run_example(
description='Add a managed company using the msp-add CLI command',
epilog='Example:\n python msp_add.py\n\nEdit MC_NAME, PLAN, and other constants in this file first.',
execute_fn=main,
)
46 changes: 46 additions & 0 deletions examples/cli_examples/msp/msp_billing_report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!/usr/bin/env python3
# _ __
# | |/ /___ ___ _ __ ___ _ _ ®
# | ' </ -_) -_) '_ \/ -_) '_|
# |_|\_\___\___| .__/\___|_|
# |_|
#
# Keeper CLI for Python
# Copyright 2025 Keeper Security Inc.
# Contact: commander@keepersecurity.com
#
# Example: msp-billing-report — MSP billing report.
#

import logging
from typing import Optional

from keepercli.commands.msp import MspBillingReportCommand

from msp_common import run_example

logging.basicConfig(level=logging.INFO, format='%(message)s')

MONTH: Optional[str] = None # YYYY-MM, e.g. '2025-01'
SHOW_DATE = None
SHOW_COMPANY = None
OUTPUT_FORMAT = 'table'


def main(context):
kwargs = {'format': OUTPUT_FORMAT}
if MONTH:
kwargs['month'] = MONTH
if SHOW_DATE is True:
kwargs['show_date'] = True
if SHOW_COMPANY is True:
kwargs['show_company'] = True
MspBillingReportCommand().execute(context=context, **kwargs)


if __name__ == '__main__':
run_example(
description='Generate MSP billing report (msp-billing-report)',
epilog='Example:\n python msp_billing_report.py',
execute_fn=main,
)
88 changes: 88 additions & 0 deletions examples/cli_examples/msp/msp_common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# _ __
# | |/ /___ ___ _ __ ___ _ _ ®
# | ' </ -_) -_) '_ \/ -_) '_|
# |_|\_\___\___| .__/\___|_|
# |_|
#
# Keeper CLI for Python
# Copyright 2025 Keeper Security Inc.
# Contact: commander@keepersecurity.com
#
# Shared helpers for MSP CLI examples in this folder.
#

import argparse
import json
import os
import sys
from typing import Callable, Optional

from keepercli.login import LoginFlow
from keepercli.params import KeeperConfig, KeeperParams


def get_default_config_path() -> str:
"""Resolve config.json (cwd) or ~/.keeper/config.json."""
file_name = 'config.json'
if os.path.isfile(file_name):
return os.path.abspath(file_name)
keeper_dir = os.path.join(os.path.expanduser('~'), '.keeper')
if not os.path.exists(keeper_dir):
os.mkdir(keeper_dir)
return os.path.join(keeper_dir, file_name)


def login_to_keeper_with_config(filename: str) -> KeeperParams:
"""Authenticate and return KeeperParams (enterprise loader available for MSP admin)."""
if not os.path.exists(filename):
raise FileNotFoundError(f'Config file {filename} not found')
with open(filename, 'r') as f:
config_data = json.load(f)

keeper_config = KeeperConfig(config_filename=filename, config=config_data)
auth = LoginFlow.login(keeper_config)
if not auth:
raise RuntimeError('Failed to authenticate with Keeper')

context = KeeperParams(keeper_config=keeper_config)
context.set_auth(auth)
return context


def add_config_argument(parser: argparse.ArgumentParser) -> None:
default_config_path = get_default_config_path()
parser.add_argument(
'-c', '--config',
default=default_config_path,
help=f'Configuration file (default: {default_config_path})',
)


def run_example(
description: str,
epilog: str,
execute_fn: Callable[[KeeperParams], None],
) -> None:
"""Parse --config, login, run execute_fn(context), then clear session."""
parser = argparse.ArgumentParser(
description=description,
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=epilog,
)
add_config_argument(parser)
args = parser.parse_args()

if not os.path.exists(args.config):
print(f'Config file {args.config} not found')
sys.exit(1)

context: Optional[KeeperParams] = None
try:
context = login_to_keeper_with_config(args.config)
execute_fn(context)
except Exception as e:
print(f'Error: {e}')
sys.exit(1)
finally:
if context:
context.clear_session()
44 changes: 44 additions & 0 deletions examples/cli_examples/msp/msp_convert_node.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env python3
# _ __
# | |/ /___ ___ _ __ ___ _ _ ®
# | ' </ -_) -_) '_ \/ -_) '_|
# |_|\_\___\___| .__/\___|_|
# |_|
#
# Keeper CLI for Python
# Copyright 2025 Keeper Security Inc.
# Contact: commander@keepersecurity.com
#
# Example: msp-convert-node — convert an enterprise subtree into a managed company.
#

import logging
from typing import Optional

from keepercli.commands.msp import MspConvertNodeCommand

from msp_common import run_example

logging.basicConfig(level=logging.INFO, format='%(message)s')

# Edit before running.
NODE = 'root' # node name or ID (subtree root)
SEATS: Optional[int] = None
PLAN: Optional[str] = None # defaults to business on the server


def main(context):
kwargs = {'node': NODE}
if SEATS is not None:
kwargs['seats'] = SEATS
if PLAN:
kwargs['plan'] = PLAN
MspConvertNodeCommand().execute(context=context, **kwargs)


if __name__ == '__main__':
run_example(
description='Convert a node subtree to a managed company (msp-convert-node)',
epilog='Example:\n python msp_convert_node.py',
execute_fn=main,
)
42 changes: 42 additions & 0 deletions examples/cli_examples/msp/msp_copy_role.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/usr/bin/env python3
# _ __
# | |/ /___ ___ _ __ ___ _ _ ®
# | ' </ -_) -_) '_ \/ -_) '_|
# |_|\_\___\___| .__/\___|_|
# |_|
#
# Keeper CLI for Python
# Copyright 2025 Keeper Security Inc.
# Contact: commander@keepersecurity.com
#
# Example: msp-copy-role — copy MSP roles (and enforcements) to managed companies.
#

import logging
from typing import List

from keepercli.commands.msp import MspCopyRoleCommand

from msp_common import run_example

logging.basicConfig(level=logging.INFO, format='%(message)s')

# Edit before running.
ROLES: List[str] = ['Keeper Administrator']
MANAGED_COMPANIES: List[str] = ['CLI MSP Example MC']


def main(context):
MspCopyRoleCommand().execute(
context=context,
role=ROLES,
mc=MANAGED_COMPANIES,
)


if __name__ == '__main__':
run_example(
description='Copy roles to managed companies (msp-copy-role)',
epilog='Example:\n python msp_copy_role.py',
execute_fn=main,
)
39 changes: 39 additions & 0 deletions examples/cli_examples/msp/msp_down.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/usr/bin/env python3
# _ __
# | |/ /___ ___ _ __ ___ _ _ ®
# | ' </ -_) -_) '_ \/ -_) '_|
# |_|\_\___\___| .__/\___|_|
# |_|
#
# Keeper CLI for Python
# Copyright 2025 Keeper Security Inc.
# Contact: commander@keepersecurity.com
#
# Example: msp-down — download / refresh MSP enterprise data from the cloud.
#

import logging

from keepercli.commands.msp import MspDownCommand

from msp_common import run_example

logging.basicConfig(level=logging.INFO, format='%(message)s')

# Set True to clear sync token and reload from scratch; None leaves reset off.
RESET = None


def main(context):
kwargs = {}
if RESET is True:
kwargs['reset'] = True
MspDownCommand().execute(context=context, **kwargs)


if __name__ == '__main__':
run_example(
description='Refresh MSP data using the msp-down CLI command',
epilog='Example:\n python msp_down.py',
execute_fn=main,
)
50 changes: 50 additions & 0 deletions examples/cli_examples/msp/msp_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/usr/bin/env python3
# _ __
# | |/ /___ ___ _ __ ___ _ _ ®
# | ' </ -_) -_) '_ \/ -_) '_|
# |_|\_\___\___| .__/\___|_|
# |_|
#
# Keeper CLI for Python
# Copyright 2025 Keeper Security Inc.
# Contact: commander@keepersecurity.com
#
# Example: msp-info — MSP details and managed companies.
#

import logging
from typing import Optional

from keepercli.commands.msp import MspInfoCommand

from msp_common import run_example

logging.basicConfig(level=logging.INFO, format='%(message)s')

# Optional filters (set to None to omit).
MANAGED_COMPANY: Optional[str] = None
SHOW_PRICING = None
SHOW_RESTRICTION = None
VERBOSE = None
OUTPUT_FORMAT = 'table' # 'table' or 'json'


def main(context):
kwargs = {'format': OUTPUT_FORMAT}
if MANAGED_COMPANY:
kwargs['managed_company'] = MANAGED_COMPANY
if SHOW_PRICING is True:
kwargs['pricing'] = True
if SHOW_RESTRICTION is True:
kwargs['restriction'] = True
if VERBOSE is True:
kwargs['verbose'] = True
MspInfoCommand().execute(context=context, **kwargs)


if __name__ == '__main__':
run_example(
description='Display MSP info using the msp-info CLI command',
epilog='Example:\n python msp_info.py',
execute_fn=main,
)
Loading