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
2 changes: 1 addition & 1 deletion .github/workflows/python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ on:
env:
REGISTRY_NAME: ${{ vars.REGISTRY_NAME }}
REGISTRY_URI: ${{ vars.REGISTRY_URI }}
POETRY_VERSION: 2.2.1
POETRY_VERSION: 2.3.4

jobs:
build:
Expand Down
1,079 changes: 1,066 additions & 13 deletions poetry.lock

Large diffs are not rendered by default.

54 changes: 32 additions & 22 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,33 @@
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

[tool.poetry]
[project]
name = "nrfcloud-utils"
version = "0.0.1"
description = "Scripts and utilities for working with the nRF Cloud"
authors = ["Nordic Semiconductor ASA"]
license = "BSD-3-Clause"
readme = "README.md"
repository = "https://github.com/nRFCloud/utils"
packages = [
{ include = "nrfcloud_utils", from = "src" }
requires-python = ">=3.10,<4.0"
authors = [
{ name = "Nordic Semiconductor ASA" }
]
dependencies = [
"cbor2>=5.4.2.post1",
"cryptography>=46.0.5",
"cffi>=2.0.0",
"requests>=2.32.0",
"urllib3>=2.2.2",
"semver>=3.0.0",
"pyjwt>=2.3.0",
"inquirer>=3.4.0",
"nrfcredstore>=2.0.9",
]

[tool.poetry.dependencies]
python = "^3.10"
cbor2 = "^5.4.2.post1"
cryptography = "^46.0.5"
cffi = "^2.0.0"
requests = "^2.32.0"
urllib3 = "^2.2.2"
semver = "^3.0.0"
pyjwt = "^2.3.0"
inquirer = "^3.4.0"
nrfcredstore = "^2.0.9"

[tool.poetry.group.dev.dependencies]
pytest = "^7.3.1"
pytest-cov = "^4.0.0"
pytest-watch = "^4.2.0"
[project.urls]
repository = "https://github.com/nRFCloud/utils"

[tool.poetry.scripts]
[project.scripts]
nrfcloud-utils = "nrfcloud_utils.cli:run"
claim_and_provision_device = "nrfcloud_utils.claim_and_provision_device:run"
claim_devices = "nrfcloud_utils.claim_devices:run"
create_ca_cert = "nrfcloud_utils.create_ca_cert:run"
Expand All @@ -44,5 +41,18 @@ nrf_cloud_device_mgmt = "nrfcloud_utils.nrf_cloud_device_mgmt:run"
nrf_cloud_onboard = "nrfcloud_utils.nrf_cloud_onboard:run"
nrf93_onboard = "nrfcloud_utils.nrf93_onboard:run"

[dependency-groups]
dev = [
"pytest>=7.3.1",
"pytest-cov>=4.0.0",
"pytest-watch>=4.2.0",
"poetry (>=2.3.4,<3.0.0)", # used for development today. might be migrated to uv in the future.
]

[tool.poetry]
packages = [
{ include = "nrfcloud_utils", from = "src" }
]

[tool.pytest.ini_options]
addopts = ["--import-mode=importlib"]
10 changes: 8 additions & 2 deletions src/nrfcloud_utils/claim_and_provision_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@
IMEI_LEN = 15
DEV_ID_MAX_LEN = 64

def parse_args(in_args):
def get_parser():
parser = argparse.ArgumentParser(description="nRF Cloud Claim and Provision",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
add_help=False)
parser_add_comms_args(parser)
parser.add_argument("--dv", type=int, help="Number of days cert is valid",
default=(10 * 365))
Expand Down Expand Up @@ -81,6 +82,11 @@ def parse_args(in_args):
choices=['debug', 'info', 'warning', 'error', 'critical'],
help='Set the logging level'
)
return parser

def parse_args(in_args):
_p = get_parser()
parser = argparse.ArgumentParser(parents=[_p], description=_p.description, formatter_class=_p.formatter_class)
args = parser.parse_args(in_args)
setup_logging(level=args.log_level, use_color=not args.plain)
return args
Expand Down
10 changes: 8 additions & 2 deletions src/nrfcloud_utils/claim_devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@
IMEI_LEN = 15
MAX_CSV_ROWS = 1000

def parse_args(in_args):
def get_parser():
parser = argparse.ArgumentParser(description="nRF Cloud Claim Devices",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
add_help=False)

parser.add_argument("--csv", type=str,
help="Filepath to attestation token CSV file",
Expand All @@ -39,6 +40,11 @@ def parse_args(in_args):
choices=['debug', 'info', 'warning', 'error', 'critical'],
help='Set the logging level'
)
return parser

def parse_args(in_args):
_p = get_parser()
parser = argparse.ArgumentParser(parents=[_p], description=_p.description, formatter_class=_p.formatter_class)
args = parser.parse_args(in_args)
setup_logging(level=args.log_level, use_color=not args.plain)
return args
Expand Down
74 changes: 74 additions & 0 deletions src/nrfcloud_utils/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/usr/bin/env python3
#
# Copyright (c) 2025 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: BSD-3-Clause
import argparse
import sys

from nrfcloud_utils import (
claim_and_provision_device,
claim_devices,
create_ca_cert,
create_device_credentials,
create_proxy_jwt,
device_credentials_installer,
gather_attestation_tokens,
modem_credentials_parser,
nrf_cloud_device_mgmt,
nrf_cloud_onboard,
nrf93_onboard,
)

_MODULES = {
"claim_and_provision_device": claim_and_provision_device,
"claim_devices": claim_devices,
"create_ca_cert": create_ca_cert,
"create_device_credentials": create_device_credentials,
"create_proxy_jwt": create_proxy_jwt,
"device_credentials_installer": device_credentials_installer,
"gather_attestation_tokens": gather_attestation_tokens,
"modem_credentials_parser": modem_credentials_parser,
"nrf_cloud_device_mgmt": nrf_cloud_device_mgmt,
"nrf_cloud_onboard": nrf_cloud_onboard,
"nrf93_onboard": nrf93_onboard,
}


def _build_parser():
parser = argparse.ArgumentParser(
prog="nrfcloud-utils",
description="Scripts and utilities for working with nRF Cloud",
)
subparsers = parser.add_subparsers(dest="command", metavar="command", required=False)
for name, mod in _MODULES.items():
_p = mod.get_parser()
subparsers.add_parser(
name,
parents=[_p],
description=_p.description,
formatter_class=_p.formatter_class,
help=_p.description,
)
return parser


def run():
parser = _build_parser()
# parse_args() handles --help at both levels and validates the subcommand name
# and its args (using each module's get_parser() for the subparser definition).
# We then re-dispatch to the module's own main() so setup_logging and other
# module init runs exactly as it does when the command is called standalone.
args = parser.parse_args()

if not args.command:
parser.print_help()
sys.exit(0)

cmd = args.command
sys.argv = [cmd] + sys.argv[2:]
_MODULES[cmd].main(sys.argv[1:])


if __name__ == "__main__":
run()
10 changes: 8 additions & 2 deletions src/nrfcloud_utils/create_ca_cert.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@

logger = logging.getLogger(__name__)

def parse_args(in_args):
parser = argparse.ArgumentParser(description="Create CA Certificate")
def get_parser():
parser = argparse.ArgumentParser(description="Create CA Certificate",
add_help=False)
parser.add_argument("-c", type=str, help="2 character country code", default="NO")
parser.add_argument("--st", type=str, help="State or Province", default="")
parser.add_argument("-l", type=str, help="Locality", default="")
Expand All @@ -50,6 +51,11 @@ def parse_args(in_args):
choices=['debug', 'info', 'warning', 'error', 'critical'],
help='Set the logging level'
)
return parser

def parse_args(in_args):
_p = get_parser()
parser = argparse.ArgumentParser(parents=[_p], description=_p.description, formatter_class=_p.formatter_class)
args = parser.parse_args(in_args)
setup_logging(level=args.log_level)
return args
Expand Down
10 changes: 8 additions & 2 deletions src/nrfcloud_utils/create_device_credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@

logger = logging.getLogger(__name__)

def parse_args(in_args):
parser = argparse.ArgumentParser(description="Create Device Credentials")
def get_parser():
parser = argparse.ArgumentParser(description="Create Device Credentials",
add_help=False)
parser.add_argument("--ca", type=str, required=True, help="Filepath to your CA cert PEM", default="")
parser.add_argument("--ca-key", type=str, required=True, help="Filepath to your CA's private key PEM", default="")
parser.add_argument("-c", type=str, help="2 character country code; required if CSR is not provided", default="NO")
Expand Down Expand Up @@ -55,6 +56,11 @@ def parse_args(in_args):
choices=['debug', 'info', 'warning', 'error', 'critical'],
help='Set the logging level'
)
return parser

def parse_args(in_args):
_p = get_parser()
parser = argparse.ArgumentParser(parents=[_p], description=_p.description, formatter_class=_p.formatter_class)
args = parser.parse_args(in_args)
setup_logging(level=args.log_level)
if len(args.csr) == 0 and len(args.cn) == 0:
Expand Down
10 changes: 8 additions & 2 deletions src/nrfcloud_utils/create_proxy_jwt.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@

logger = logging.getLogger(__name__)

def parse_args(in_args):
def get_parser():
parser = argparse.ArgumentParser(description="Create JWT for proxy (cloud-to-cloud) requests to nRF Cloud",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
add_help=False)

parser.add_argument("--key", type=str, required=True,
help="Required filepath to the ES256 private key PEM (Service Key) used for JWT signing. \
Expand All @@ -43,6 +44,11 @@ def parse_args(in_args):
choices=['debug', 'info', 'warning', 'error', 'critical'],
help='Set the logging level'
)
return parser

def parse_args(in_args):
_p = get_parser()
parser = argparse.ArgumentParser(parents=[_p], description=_p.description, formatter_class=_p.formatter_class)
args = parser.parse_args(in_args)
setup_logging(level=args.log_level)
return args
Expand Down
10 changes: 8 additions & 2 deletions src/nrfcloud_utils/device_credentials_installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@
MIN_REQD_MFW_VER = "1.3.0"
MIN_REQD_MFW_VER_FOR_VERIFY = "1.3.2"

def parse_args(in_args):
def get_parser():
parser = argparse.ArgumentParser(description="Device Credentials Installer",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
add_help=False)
parser_add_comms_args(parser)
parser.add_argument("--dv", type=int, help="Number of days cert is valid",
default=(10 * 365))
Expand Down Expand Up @@ -105,6 +106,11 @@ def parse_args(in_args):
choices=['debug', 'info', 'warning', 'error', 'critical'],
help='Set the logging level'
)
return parser

def parse_args(in_args):
_p = get_parser()
parser = argparse.ArgumentParser(parents=[_p], description=_p.description, formatter_class=_p.formatter_class)
args = parser.parse_args(in_args)
setup_logging(level=args.log_level, use_color=not args.plain)
return args
Expand Down
10 changes: 8 additions & 2 deletions src/nrfcloud_utils/gather_attestation_tokens.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@

IMEI_LEN = 15

def parse_args(in_args):
def get_parser():
parser = argparse.ArgumentParser(description="Gather Attestation Tokens",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
add_help=False)
parser_add_comms_args(parser)
parser.add_argument("--csv", type=str,
help="Filepath to attestation token CSV file",
Expand All @@ -40,6 +41,11 @@ def parse_args(in_args):
choices=['debug', 'info', 'warning', 'error', 'critical'],
help='Set the logging level'
)
return parser

def parse_args(in_args):
_p = get_parser()
parser = argparse.ArgumentParser(parents=[_p], description=_p.description, formatter_class=_p.formatter_class)
args = parser.parse_args(in_args)
setup_logging(level=args.log_level, use_color=not args.plain)
return args
Expand Down
10 changes: 8 additions & 2 deletions src/nrfcloud_utils/modem_credentials_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@
-5: 'nordic_base_rd_key'
}

def parse_args(in_args):
parser = argparse.ArgumentParser(description="Modem Credentials Parser")
def get_parser():
parser = argparse.ArgumentParser(description="Modem Credentials Parser",
add_help=False)
parser.add_argument("-k", "--keygen", type=str, help="base64url string: KEYGEN output", default="")
parser.add_argument("-a", "--attest", type=str, help="base64url string: ATTESTTOKEN output", default="")
parser.add_argument("-s", "--save", action='store_true', help="Save PEM file(s): <UUID>_<sec_tag>_<type>.pem")
Expand All @@ -56,6 +57,11 @@ def parse_args(in_args):
parser.add_argument("-P", "--plain",
help="bool: Plain output (no colors)",
action='store_true', default=False)
return parser

def parse_args(in_args):
_p = get_parser()
parser = argparse.ArgumentParser(parents=[_p], description=_p.description, formatter_class=_p.formatter_class)
args = parser.parse_args(in_args)
setup_logging(level=args.log_level, use_color=not args.plain)
return args
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,11 @@ def get_nrf93m1_identity_key(cred_if):
logger.error('Failed to parse identity key from response')
return None

def parse_args(in_args):
def get_parser():
parser = argparse.ArgumentParser(
description="nRF93M1 - Onboard Device using Registration token JWT",
formatter_class=argparse.ArgumentDefaultsHelpFormatter
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
add_help=False
)
parser.add_argument("--port", type=str, required=True,
help="Serial port for the nRF93M1 device (e.g., /dev/ttyACM0 or COM3)")
Expand All @@ -115,7 +116,11 @@ def parse_args(in_args):
metavar="TAG",
help="Tags to assign to the device on nRF Cloud. "
"Each tag must match /[a-zA-Z0-9_.,@\\/:#-]{0,799}/")
return parser

def parse_args(in_args):
_p = get_parser()
parser = argparse.ArgumentParser(parents=[_p], description=_p.description, formatter_class=_p.formatter_class)
args = parser.parse_args(in_args)

# Setup logging
Expand Down
Loading
Loading