batch-shipyard/shipyard.py

2070 строки
61 KiB
Python
Executable File

#!/usr/bin/env python3
# Copyright (c) Microsoft Corporation
#
# All rights reserved.
#
# MIT License
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
# compat imports
from __future__ import absolute_import, division, print_function
from builtins import ( # noqa
bytes, dict, int, list, object, range, str, ascii, chr, hex, input,
next, oct, open, pow, round, super, filter, map, zip)
# stdlib imports
import json
import logging
try:
import pathlib2 as pathlib
except ImportError:
import pathlib
# non-stdlib imports
import click
import ruamel.yaml
# local imports
import convoy.clients
import convoy.fleet
import convoy.settings
import convoy.util
import convoy.validator
# create logger
logger = logging.getLogger('shipyard')
# global defines
_CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
class CliContext(object):
"""CliContext class: holds context for CLI commands"""
def __init__(self):
"""Ctor for CliContext"""
self.show_config = False
self.verbose = False
self.yes = False
self.raw = None
self.config = None
self.conf_jobs = None
self.conf_fs = None
# clients
self.batch_mgmt_client = None
self.batch_client = None
self.blob_client = None
self.table_client = None
self.keyvault_client = None
self.resource_client = None
self.compute_client = None
self.network_client = None
self.storage_mgmt_client = None
# aad/keyvault options
self.keyvault_uri = None
self.keyvault_credentials_secret_id = None
self.aad_authority_url = None
self.aad_directory_id = None
self.aad_application_id = None
self.aad_auth_key = None
self.aad_user = None
self.aad_password = None
self.aad_cert_private_key = None
self.aad_cert_thumbprint = None
self.aad_endpoint = None
# management options
self.subscription_id = None
@staticmethod
def ensure_pathlib_conf(conf):
# type: (Any) -> pathlib.Path
"""Ensure conf object is a pathlib object
:param str or pathlib.Path or None conf: conf object
:rtype: pathlib.Path or None
:return: conf object as pathlib
"""
if conf is not None and not isinstance(conf, pathlib.Path):
conf = pathlib.Path(conf)
return conf
def initialize_for_fs(self):
# type: (CliContext) -> None
"""Initialize context for fs commands
:param CliContext self: this
"""
self._read_credentials_config()
self._set_global_cli_options()
try:
self.keyvault_client = convoy.clients.create_keyvault_client(self)
except KeyError:
logger.error(
'Are you missing your configuration files or pointing to '
'the wrong location?')
raise
self._init_config(
skip_global_config=False, skip_pool_config=True, fs_storage=True)
self.resource_client, self.compute_client, self.network_client, \
self.storage_mgmt_client, _, _ = \
convoy.clients.create_all_clients(self)
# inject storage account keys if via aad
convoy.fleet.fetch_storage_account_keys_from_aad(
self.storage_mgmt_client, self.config, fs_storage=True)
self.blob_client, _ = convoy.clients.create_storage_clients()
self._cleanup_after_initialize(
skip_global_config=False, skip_pool_config=True)
def initialize_for_keyvault(self):
# type: (CliContext) -> None
"""Initialize context for keyvault commands
:param CliContext self: this
"""
self._read_credentials_config()
self._set_global_cli_options()
try:
self.keyvault_client = convoy.clients.create_keyvault_client(self)
except KeyError:
logger.error(
'Are you missing your configuration files or pointing to '
'the wrong location?')
raise
self._init_config(
skip_global_config=True, skip_pool_config=True, fs_storage=False)
self._cleanup_after_initialize(
skip_global_config=True, skip_pool_config=True)
def initialize_for_batch(self):
# type: (CliContext) -> None
"""Initialize context for batch commands
:param CliContext self: this
"""
self._read_credentials_config()
self._set_global_cli_options()
try:
self.keyvault_client = convoy.clients.create_keyvault_client(self)
except KeyError:
logger.error(
'Are you missing your configuration files or pointing to '
'the wrong location?')
raise
self._init_config(
skip_global_config=False, skip_pool_config=False, fs_storage=False)
self.resource_client, self.compute_client, self.network_client, \
self.storage_mgmt_client, self.batch_mgmt_client, \
self.batch_client = \
convoy.clients.create_all_clients(self, batch_clients=True)
# inject storage account keys if via aad
convoy.fleet.fetch_storage_account_keys_from_aad(
self.storage_mgmt_client, self.config, fs_storage=False)
self.blob_client, self.table_client = \
convoy.clients.create_storage_clients()
self._cleanup_after_initialize(
skip_global_config=False, skip_pool_config=False)
def initialize_for_storage(self):
# type: (CliContext) -> None
"""Initialize context for storage commands
:param CliContext self: this
"""
self._read_credentials_config()
self._set_global_cli_options()
self.keyvault_client = convoy.clients.create_keyvault_client(self)
self._init_config(
skip_global_config=False, skip_pool_config=False, fs_storage=False)
# inject storage account keys if via aad
_, _, _, self.storage_mgmt_client, _, _ = \
convoy.clients.create_all_clients(self)
convoy.fleet.fetch_storage_account_keys_from_aad(
self.storage_mgmt_client, self.config, fs_storage=False)
self.blob_client, self.table_client = \
convoy.clients.create_storage_clients()
self._cleanup_after_initialize(
skip_global_config=False, skip_pool_config=False)
def _set_global_cli_options(self):
# type: (CliContext) -> None
"""Set global cli options
:param CliContext self: this
"""
if self.config is None:
self.config = {}
# set internal config kv pairs
self.config['_verbose'] = self.verbose
self.config['_auto_confirm'] = self.yes
self.config['_raw'] = self.raw
# increase detail in logger formatters
if self.verbose:
convoy.util.set_verbose_logger_handlers()
def _cleanup_after_initialize(
self, skip_global_config, skip_pool_config):
# type: (CliContext) -> None
"""Cleanup after initialize_for_* funcs
:param CliContext self: this
:param bool skip_global_config: skip global config
:param bool skip_pool_config: skip pool config
"""
# free conf objects
del self.conf_credentials
del self.conf_fs
if not skip_global_config:
del self.conf_config
if not skip_pool_config:
del self.conf_pool
del self.conf_jobs
# free cli options
del self.verbose
del self.yes
del self.raw
del self.aad_authority_url
del self.aad_directory_id
del self.aad_application_id
del self.aad_auth_key
del self.aad_user
del self.aad_password
del self.aad_cert_private_key
del self.aad_cert_thumbprint
del self.aad_endpoint
del self.keyvault_credentials_secret_id
del self.subscription_id
# free clients that won't be used
del self.storage_mgmt_client
def _read_config_file(self, config_file):
# type: (CliContext, pathlib.Path) -> None
"""Read a yaml/json file into self.config
:param CliContext self: this
:param pathlib.Path config_file: config file to load
"""
with config_file.open('r') as f:
if self.config is None:
self.config = ruamel.yaml.load(
f, Loader=ruamel.yaml.RoundTripLoader)
else:
self.config = convoy.util.merge_dict(
self.config,
ruamel.yaml.load(f, Loader=ruamel.yaml.RoundTripLoader))
def _form_conf_path(self, conf_var, prefix):
"""Form configuration file path with configdir if applicable
:param CliContext self: this
:param any conf_var: conf var
:param str prefix: configuration file prefix
:rtype: pathlib.Path
:return: new configuration file path
"""
# use configdir if available
if conf_var is None:
cd = self.configdir or '.'
pathyaml = pathlib.Path(cd, '{}.yaml'.format(prefix))
if pathyaml.exists():
return pathyaml
path = pathlib.Path(cd, '{}.yml'.format(prefix))
if path.exists():
return path
path = pathlib.Path(cd, '{}.json'.format(prefix))
if path.exists():
return path
return pathyaml
else:
return conf_var
def _read_credentials_config(self):
# type: (CliContext) -> None
"""Read credentials config file only
:param CliContext self: this
"""
self.conf_credentials = self._form_conf_path(
self.conf_credentials, 'credentials')
if self.conf_credentials is not None:
self.conf_credentials = CliContext.ensure_pathlib_conf(
self.conf_credentials)
if self.conf_credentials.exists():
self._read_config_file(self.conf_credentials)
def _init_config(
self, skip_global_config=False, skip_pool_config=False,
fs_storage=False):
# type: (CliContext, bool, bool, bool) -> None
"""Initializes configuration of the context
:param CliContext self: this
:param bool skip_global_config: skip global config
:param bool skip_pool_config: skip pool config
:param bool fs_storage: adjust storage settings for fs
"""
# reset config
self.config = None
self._set_global_cli_options()
# set/validate credentials config
self.conf_credentials = self._form_conf_path(
self.conf_credentials, 'credentials')
self.conf_credentials = CliContext.ensure_pathlib_conf(
self.conf_credentials)
convoy.validator.validate_config(
convoy.validator.ConfigType.Credentials, self.conf_credentials)
# set/validate global config
if not skip_global_config:
self.conf_config = self._form_conf_path(self.conf_config, 'config')
if self.conf_config is None:
raise ValueError('config conf file was not specified')
self.conf_config = CliContext.ensure_pathlib_conf(self.conf_config)
convoy.validator.validate_config(
convoy.validator.ConfigType.Global, self.conf_config)
# set/validate batch config
if not skip_pool_config:
# set/validate pool config
self.conf_pool = self._form_conf_path(self.conf_pool, 'pool')
if self.conf_pool is None:
raise ValueError('pool conf file was not specified')
self.conf_pool = CliContext.ensure_pathlib_conf(self.conf_pool)
convoy.validator.validate_config(
convoy.validator.ConfigType.Pool, self.conf_pool)
# set/validate jobs config
self.conf_jobs = self._form_conf_path(self.conf_jobs, 'jobs')
self.conf_jobs = CliContext.ensure_pathlib_conf(self.conf_jobs)
convoy.validator.validate_config(
convoy.validator.ConfigType.Jobs, self.conf_jobs)
# set/validate fs config
self.conf_fs = self._form_conf_path(self.conf_fs, 'fs')
self.conf_fs = CliContext.ensure_pathlib_conf(self.conf_fs)
convoy.validator.validate_config(
convoy.validator.ConfigType.RemoteFS, self.conf_fs)
# fetch credentials from keyvault, if conf file is missing
kvcreds = None
if self.conf_credentials is None or not self.conf_credentials.exists():
kvcreds = convoy.fleet.fetch_credentials_conf_from_keyvault(
self.keyvault_client, self.keyvault_uri,
self.keyvault_credentials_secret_id)
# read credentials conf, perform special keyvault processing if
# required sections are missing
if kvcreds is None:
self._read_config_file(self.conf_credentials)
kv = convoy.settings.credentials_keyvault(self.config)
self.keyvault_uri = self.keyvault_uri or kv.keyvault_uri
self.keyvault_credentials_secret_id = (
self.keyvault_credentials_secret_id or
kv.keyvault_credentials_secret_id
)
if self.keyvault_credentials_secret_id is not None:
try:
convoy.settings.credentials_batch(self.config)
if len(list(convoy.settings.iterate_storage_credentials(
self.config))) == 0:
raise KeyError()
except KeyError:
# fetch credentials from keyvault
self.config = \
convoy.fleet.fetch_credentials_conf_from_keyvault(
self.keyvault_client, self.keyvault_uri,
self.keyvault_credentials_secret_id)
else:
self.config = kvcreds
del kvcreds
# re-populate global cli options again
self._set_global_cli_options()
# parse any keyvault secret ids from credentials
convoy.fleet.fetch_secrets_from_keyvault(
self.keyvault_client, self.config)
# read rest of config files
if not skip_global_config:
self._read_config_file(self.conf_config)
# read fs config regardless of skip setting
if self.conf_fs is not None and self.conf_fs.exists():
self._read_config_file(self.conf_fs)
if not skip_pool_config:
self._read_config_file(self.conf_pool)
if self.conf_jobs is not None:
self.conf_jobs = CliContext.ensure_pathlib_conf(self.conf_jobs)
if self.conf_jobs.exists():
self._read_config_file(self.conf_jobs)
# adjust settings
convoy.fleet.initialize_globals(convoy.settings.verbose(self.config))
if not skip_global_config:
convoy.fleet.populate_global_settings(self.config, fs_storage)
# show config if specified
if self.show_config:
logger.debug('config:\n' + json.dumps(self.config, indent=4))
# disable azure storage/cosmosdb logging: setting logger level
# to CRITICAL effectively disables logging from azure storage/cosmosdb
az_logger = logging.getLogger('azure.storage')
az_logger.setLevel(logging.CRITICAL)
az_logger = logging.getLogger('azure.cosmosdb')
az_logger.setLevel(logging.CRITICAL)
# create a pass decorator for shared context between commands
pass_cli_context = click.make_pass_decorator(CliContext, ensure=True)
def _confirm_option(f):
def callback(ctx, param, value):
clictx = ctx.ensure_object(CliContext)
clictx.yes = value
return value
return click.option(
'-y', '--yes',
expose_value=False,
is_flag=True,
help='Assume yes for all confirmation prompts',
callback=callback)(f)
def _log_file_option(f):
def callback(ctx, param, value):
clictx = ctx.ensure_object(CliContext)
clictx.logfile = value
return value
return click.option(
'--log-file',
expose_value=False,
envvar='SHIPYARD_LOG_FILE',
help='Log to file',
callback=callback)(f)
def _show_config_option(f):
def callback(ctx, param, value):
clictx = ctx.ensure_object(CliContext)
clictx.show_config = value
return value
return click.option(
'--show-config',
expose_value=False,
is_flag=True,
help='Show configuration',
callback=callback)(f)
def _verbose_option(f):
def callback(ctx, param, value):
clictx = ctx.ensure_object(CliContext)
clictx.verbose = value
return value
return click.option(
'-v', '--verbose',
expose_value=False,
is_flag=True,
help='Verbose output',
callback=callback)(f)
def _raw_option(f):
def callback(ctx, param, value):
clictx = ctx.ensure_object(CliContext)
clictx.raw = value
return value
return click.option(
'--raw',
expose_value=False,
is_flag=True,
help='Output data as returned by the service for supported '
'operations as raw json',
callback=callback)(f)
def _azure_keyvault_uri_option(f):
def callback(ctx, param, value):
clictx = ctx.ensure_object(CliContext)
clictx.keyvault_uri = value
return value
return click.option(
'--keyvault-uri',
expose_value=False,
envvar='SHIPYARD_KEYVAULT_URI',
help='Azure KeyVault URI',
callback=callback)(f)
def _azure_keyvault_credentials_secret_id_option(f):
def callback(ctx, param, value):
clictx = ctx.ensure_object(CliContext)
clictx.keyvault_credentials_secret_id = value
return value
return click.option(
'--keyvault-credentials-secret-id',
expose_value=False,
envvar='SHIPYARD_KEYVAULT_CREDENTIALS_SECRET_ID',
help='Azure KeyVault credentials secret id',
callback=callback)(f)
def _aad_directory_id_option(f):
def callback(ctx, param, value):
clictx = ctx.ensure_object(CliContext)
clictx.aad_directory_id = value
return value
return click.option(
'--aad-directory-id',
expose_value=False,
envvar='SHIPYARD_AAD_DIRECTORY_ID',
help='Azure Active Directory directory (tenant) id',
callback=callback)(f)
def _aad_application_id_option(f):
def callback(ctx, param, value):
clictx = ctx.ensure_object(CliContext)
clictx.aad_application_id = value
return value
return click.option(
'--aad-application-id',
expose_value=False,
envvar='SHIPYARD_AAD_APPLICATION_ID',
help='Azure Active Directory application (client) id',
callback=callback)(f)
def _aad_auth_key_option(f):
def callback(ctx, param, value):
clictx = ctx.ensure_object(CliContext)
clictx.aad_auth_key = value
return value
return click.option(
'--aad-auth-key',
expose_value=False,
envvar='SHIPYARD_AAD_AUTH_KEY',
help='Azure Active Directory authentication key',
callback=callback)(f)
def _aad_authority_url_option(f):
def callback(ctx, param, value):
clictx = ctx.ensure_object(CliContext)
clictx.aad_authority_url = value
return value
return click.option(
'--aad-authority-url',
expose_value=False,
envvar='SHIPYARD_AAD_AUTHORITY_URL',
help='Azure Active Directory authority URL',
callback=callback)(f)
def _aad_user_option(f):
def callback(ctx, param, value):
clictx = ctx.ensure_object(CliContext)
clictx.aad_user = value
return value
return click.option(
'--aad-user',
expose_value=False,
envvar='SHIPYARD_AAD_USER',
help='Azure Active Directory user',
callback=callback)(f)
def _aad_password_option(f):
def callback(ctx, param, value):
clictx = ctx.ensure_object(CliContext)
clictx.aad_password = value
return value
return click.option(
'--aad-password',
expose_value=False,
envvar='SHIPYARD_AAD_PASSWORD',
help='Azure Active Directory password',
callback=callback)(f)
def _aad_cert_private_key_option(f):
def callback(ctx, param, value):
clictx = ctx.ensure_object(CliContext)
clictx.aad_cert_private_key = value
return value
return click.option(
'--aad-cert-private-key',
expose_value=False,
envvar='SHIPYARD_AAD_CERT_PRIVATE_KEY',
help='Azure Active Directory private key for X.509 certificate',
callback=callback)(f)
def _aad_cert_thumbprint_option(f):
def callback(ctx, param, value):
clictx = ctx.ensure_object(CliContext)
clictx.aad_cert_thumbprint = value
return value
return click.option(
'--aad-cert-thumbprint',
expose_value=False,
envvar='SHIPYARD_AAD_CERT_THUMBPRINT',
help='Azure Active Directory certificate SHA1 thumbprint',
callback=callback)(f)
def _aad_endpoint_option(f):
def callback(ctx, param, value):
clictx = ctx.ensure_object(CliContext)
clictx.aad_endpoint = value
return value
return click.option(
'--aad-endpoint',
expose_value=False,
envvar='SHIPYARD_AAD_ENDPOINT',
help='Azure Active Directory endpoint',
callback=callback)(f)
def _azure_subscription_id_option(f):
def callback(ctx, param, value):
clictx = ctx.ensure_object(CliContext)
clictx.subscription_id = value
return value
return click.option(
'--subscription-id',
expose_value=False,
envvar='SHIPYARD_SUBSCRIPTION_ID',
help='Azure Subscription ID',
callback=callback)(f)
def _configdir_option(f):
def callback(ctx, param, value):
clictx = ctx.ensure_object(CliContext)
clictx.configdir = value
return value
return click.option(
'--configdir',
expose_value=False,
envvar='SHIPYARD_CONFIGDIR',
help='Configuration directory where all configuration files can be '
'found. Each config file must be named exactly the same as the '
'regular switch option, e.g., pool.yaml for --pool. Individually '
'specified config options take precedence over this option. This '
'defaults to "." if no other configuration option is specified.',
callback=callback)(f)
def _credentials_option(f):
def callback(ctx, param, value):
clictx = ctx.ensure_object(CliContext)
clictx.conf_credentials = value
return value
return click.option(
'--credentials',
expose_value=False,
envvar='SHIPYARD_CREDENTIALS_CONF',
help='Credentials config file',
callback=callback)(f)
def _config_option(f):
def callback(ctx, param, value):
clictx = ctx.ensure_object(CliContext)
clictx.conf_config = value
return value
return click.option(
'--config',
expose_value=False,
envvar='SHIPYARD_CONFIG_CONF',
help='Global config file',
callback=callback)(f)
def _pool_option(f):
def callback(ctx, param, value):
clictx = ctx.ensure_object(CliContext)
clictx.conf_pool = value
return value
return click.option(
'--pool',
expose_value=False,
envvar='SHIPYARD_POOL_CONF',
help='Pool config file',
callback=callback)(f)
def _jobs_option(f):
def callback(ctx, param, value):
clictx = ctx.ensure_object(CliContext)
clictx.conf_jobs = value
return value
return click.option(
'--jobs',
expose_value=False,
envvar='SHIPYARD_JOBS_CONF',
help='Jobs config file',
callback=callback)(f)
def fs_option(f):
def callback(ctx, param, value):
clictx = ctx.ensure_object(CliContext)
clictx.conf_fs = value
return value
return click.option(
'--fs',
expose_value=False,
envvar='SHIPYARD_FS_CONF',
help='RemoteFS config file',
callback=callback)(f)
def _storage_cluster_id_argument(f):
def callback(ctx, param, value):
return value
return click.argument(
'storage-cluster-id',
callback=callback)(f)
def common_options(f):
f = _config_option(f)
f = _credentials_option(f)
f = _configdir_option(f)
f = _verbose_option(f)
f = _show_config_option(f)
f = _raw_option(f)
# f = _log_file_option(f)
f = _confirm_option(f)
return f
def aad_options(f):
f = _aad_cert_thumbprint_option(f)
f = _aad_cert_private_key_option(f)
f = _aad_password_option(f)
f = _aad_user_option(f)
f = _aad_authority_url_option(f)
f = _aad_auth_key_option(f)
f = _aad_application_id_option(f)
f = _aad_directory_id_option(f)
f = _aad_endpoint_option(f)
return f
def batch_options(f):
f = _azure_subscription_id_option(f)
f = _jobs_option(f)
f = _pool_option(f)
return f
def keyvault_options(f):
f = _azure_keyvault_credentials_secret_id_option(f)
f = _azure_keyvault_uri_option(f)
return f
def fs_options(f):
f = _azure_subscription_id_option(f)
f = fs_option(f)
return f
def fs_cluster_options(f):
f = fs_options(f)
f = _storage_cluster_id_argument(f)
return f
@click.group(context_settings=_CONTEXT_SETTINGS)
@click.version_option(version=convoy.__version__)
@click.pass_context
def cli(ctx):
"""Batch Shipyard: Provision and execute container workloads on
Azure Batch"""
pass
@cli.group()
@pass_cli_context
def account(ctx):
"""Batch account actions"""
pass
@account.command('info')
@click.option('--name', help='Batch account name')
@click.option('--resource-group', help='Batch account resource group')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def account_info(ctx, name, resource_group):
"""Retrieve Batch account information and quotas"""
ctx.initialize_for_batch()
convoy.fleet.action_account_info(
ctx.batch_mgmt_client, ctx.config, name, resource_group)
@account.command('list')
@click.option('--resource-group', help='Scope query to resource group')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def account_list(ctx, resource_group):
"""Retrieve a list of Batch accounts and associated quotas in
subscription"""
ctx.initialize_for_batch()
convoy.fleet.action_account_list(
ctx.batch_mgmt_client, ctx.config, resource_group)
@account.command('quota')
@click.argument('location')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def account_quota(ctx, location):
"""Retrieve Batch account quota at the subscription level for the
specified location"""
ctx.initialize_for_batch()
convoy.fleet.action_account_quota(
ctx.batch_mgmt_client, ctx.config, location)
@cli.group()
@pass_cli_context
def fs(ctx):
"""Filesystem in Azure actions"""
pass
@fs.group()
@pass_cli_context
def cluster(ctx):
"""Filesystem storage cluster in Azure actions"""
pass
@cluster.command('add')
@common_options
@fs_cluster_options
@aad_options
@pass_cli_context
def fs_cluster_add(ctx, storage_cluster_id):
"""Create a filesystem storage cluster in Azure"""
ctx.initialize_for_fs()
convoy.fleet.action_fs_cluster_add(
ctx.resource_client, ctx.compute_client, ctx.network_client,
ctx.blob_client, ctx.config, storage_cluster_id)
@cluster.command('resize')
@common_options
@fs_cluster_options
@aad_options
@pass_cli_context
def fs_cluster_resize(ctx, storage_cluster_id):
"""Resize a filesystem storage cluster in Azure. Only increasing the
storage cluster size is supported."""
ctx.initialize_for_fs()
convoy.fleet.action_fs_cluster_resize(
ctx.compute_client, ctx.network_client, ctx.blob_client, ctx.config,
storage_cluster_id)
@cluster.command('expand')
@click.option(
'--no-rebalance', is_flag=True,
help='Do not rebalance filesystem, if applicable')
@common_options
@fs_cluster_options
@aad_options
@pass_cli_context
def fs_cluster_expand(ctx, storage_cluster_id, no_rebalance):
"""Expand a filesystem storage cluster in Azure"""
ctx.initialize_for_fs()
convoy.fleet.action_fs_cluster_expand(
ctx.compute_client, ctx.network_client, ctx.config,
storage_cluster_id, not no_rebalance)
@cluster.command('del')
@click.option(
'--delete-resource-group', is_flag=True,
help='Delete all resources in the storage cluster resource group')
@click.option(
'--delete-data-disks', is_flag=True,
help='Delete all attached managed data disks')
@click.option(
'--delete-virtual-network', is_flag=True, help='Delete virtual network')
@click.option(
'--generate-from-prefix', is_flag=True,
help='Generate resources to delete from storage cluster hostname prefix')
@click.option(
'--no-wait', is_flag=True, help='Do not wait for deletion to complete')
@common_options
@fs_cluster_options
@aad_options
@pass_cli_context
def fs_cluster_del(
ctx, storage_cluster_id, delete_resource_group, delete_data_disks,
delete_virtual_network, generate_from_prefix, no_wait):
"""Delete a filesystem storage cluster in Azure"""
ctx.initialize_for_fs()
convoy.fleet.action_fs_cluster_del(
ctx.resource_client, ctx.compute_client, ctx.network_client,
ctx.blob_client, ctx.config, storage_cluster_id,
delete_resource_group, delete_data_disks, delete_virtual_network,
generate_from_prefix, not no_wait)
@cluster.command('suspend')
@click.option(
'--no-wait', is_flag=True, help='Do not wait for suspension to complete')
@common_options
@fs_cluster_options
@aad_options
@pass_cli_context
def fs_cluster_suspend(ctx, storage_cluster_id, no_wait):
"""Suspend a filesystem storage cluster in Azure"""
ctx.initialize_for_fs()
convoy.fleet.action_fs_cluster_suspend(
ctx.compute_client, ctx.config, storage_cluster_id, not no_wait)
@cluster.command('start')
@click.option(
'--no-wait', is_flag=True, help='Do not wait for restart to complete')
@common_options
@fs_cluster_options
@aad_options
@pass_cli_context
def fs_cluster_start(ctx, storage_cluster_id, no_wait):
"""Starts a previously suspended filesystem storage cluster in Azure"""
ctx.initialize_for_fs()
convoy.fleet.action_fs_cluster_start(
ctx.compute_client, ctx.network_client, ctx.config,
storage_cluster_id, not no_wait)
@cluster.command('status')
@click.option(
'--detail', is_flag=True, help='Detailed storage cluster status')
@click.option(
'--hosts', is_flag=True,
help='Output /etc/hosts compatible name resolution for GlusterFS clusters')
@common_options
@fs_cluster_options
@aad_options
@pass_cli_context
def fs_cluster_status(ctx, storage_cluster_id, detail, hosts):
"""Query status of a filesystem storage cluster in Azure"""
ctx.initialize_for_fs()
convoy.fleet.action_fs_cluster_status(
ctx.compute_client, ctx.network_client, ctx.config,
storage_cluster_id, detail, hosts)
@cluster.command('ssh')
@click.option(
'--cardinal',
help='Zero-based cardinal number of remote fs vm to connect to',
type=int)
@click.option(
'--hostname', help='Hostname of remote fs vm to connect to')
@click.option(
'--tty', is_flag=True, help='Allocate a pseudo-tty')
@common_options
@fs_cluster_options
@click.argument('command', nargs=-1)
@aad_options
@pass_cli_context
def fs_cluster_ssh(ctx, storage_cluster_id, cardinal, hostname, tty, command):
"""Interactively login via SSH to a filesystem storage cluster virtual
machine in Azure"""
ctx.initialize_for_fs()
convoy.fleet.action_fs_cluster_ssh(
ctx.compute_client, ctx.network_client, ctx.config,
storage_cluster_id, cardinal, hostname, tty, command)
@fs.group()
@pass_cli_context
def disks(ctx):
"""Managed disk actions"""
pass
@disks.command('add')
@common_options
@fs_options
@aad_options
@pass_cli_context
def fs_disks_add(ctx):
"""Create managed disks in Azure"""
ctx.initialize_for_fs()
convoy.fleet.action_fs_disks_add(
ctx.resource_client, ctx.compute_client, ctx.config)
@disks.command('del')
@click.option(
'--all', is_flag=True, help='Delete all disks in resource group')
@click.option(
'--delete-resource-group', is_flag=True,
help='Delete specified resource group')
@click.option(
'--name', help='Delete disk with specified name only')
@click.option(
'--resource-group',
help='Delete disks matching specified resource group only')
@click.option(
'--no-wait', is_flag=True,
help='Do not wait for disk deletion to complete')
@common_options
@fs_options
@aad_options
@pass_cli_context
def fs_disks_del(
ctx, all, delete_resource_group, name, resource_group, no_wait):
"""Delete managed disks in Azure"""
ctx.initialize_for_fs()
convoy.fleet.action_fs_disks_del(
ctx.resource_client, ctx.compute_client, ctx.config, name,
resource_group, all, delete_resource_group, not no_wait)
@disks.command('list')
@click.option(
'--resource-group',
help='List disks matching specified resource group only')
@click.option(
'--restrict-scope', is_flag=True,
help='List disks present only in configuration if they exist')
@common_options
@fs_options
@aad_options
@pass_cli_context
def fs_disks_list(ctx, resource_group, restrict_scope):
"""List managed disks in resource group"""
ctx.initialize_for_fs()
convoy.fleet.action_fs_disks_list(
ctx.compute_client, ctx.config, resource_group, restrict_scope)
@cli.group()
@pass_cli_context
def storage(ctx):
"""Storage actions"""
pass
@storage.command('del')
@click.option(
'--clear-tables', is_flag=True, help='Clear tables instead of deleting')
@click.option(
'--poolid', help='Delete storage containers for the specified pool')
@common_options
@batch_options
@keyvault_options
@pass_cli_context
def storage_del(ctx, clear_tables, poolid):
"""Delete Azure Storage containers used by Batch Shipyard"""
ctx.initialize_for_storage()
convoy.fleet.action_storage_del(
ctx.blob_client, ctx.table_client, ctx.config, clear_tables, poolid)
@storage.command('clear')
@click.option(
'--poolid', help='Clear storage containers for the specified pool')
@common_options
@batch_options
@keyvault_options
@pass_cli_context
def storage_clear(ctx, poolid):
"""Clear Azure Storage containers used by Batch Shipyard"""
ctx.initialize_for_storage()
convoy.fleet.action_storage_clear(
ctx.blob_client, ctx.table_client, ctx.config, poolid)
@storage.group()
@pass_cli_context
def sas(ctx):
"""SAS key actions"""
pass
@sas.command('create')
@click.option(
'--create', is_flag=True, help='Create permission')
@click.option(
'--delete', is_flag=True, help='Delete permission')
@click.option(
'--file', is_flag=True, help='Create file SAS instead of blob SAS')
@click.option(
'--read', is_flag=True, help='Read permission')
@click.option(
'--write', is_flag=True, help='Write permission')
@click.argument('storage-account')
@click.argument('path')
@common_options
@batch_options
@keyvault_options
@pass_cli_context
def sas_create(ctx, create, delete, file, read, write, storage_account, path):
"""Create an object-level SAS key"""
ctx.initialize_for_storage()
convoy.fleet.action_storage_sas_create(
ctx.config, storage_account, path, file, create, read, write, delete)
@cli.group()
@pass_cli_context
def keyvault(ctx):
"""KeyVault actions"""
pass
@keyvault.command('add')
@click.argument('name')
@common_options
@keyvault_options
@aad_options
@pass_cli_context
def keyvault_add(ctx, name):
"""Add a credentials config file as a secret to Azure KeyVault"""
ctx.initialize_for_keyvault()
convoy.fleet.action_keyvault_add(
ctx.keyvault_client, ctx.config, ctx.keyvault_uri, name)
@keyvault.command('del')
@click.argument('name')
@common_options
@keyvault_options
@aad_options
@pass_cli_context
def keyvault_del(ctx, name):
"""Delete a secret from Azure KeyVault"""
ctx.initialize_for_keyvault()
convoy.fleet.action_keyvault_del(
ctx.keyvault_client, ctx.keyvault_uri, name)
@keyvault.command('list')
@common_options
@keyvault_options
@aad_options
@pass_cli_context
def keyvault_list(ctx):
"""List secret ids and metadata in an Azure KeyVault"""
ctx.initialize_for_keyvault()
convoy.fleet.action_keyvault_list(ctx.keyvault_client, ctx.keyvault_uri)
@cli.group()
@pass_cli_context
def cert(ctx):
"""Certificate actions"""
pass
@cert.command('create')
@click.option('--file-prefix', help='Certificate file prefix')
@click.option('--pfx-password', help='PFX password')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def cert_create(ctx, file_prefix, pfx_password):
"""Create a certificate to use with a Batch account"""
ctx.initialize_for_batch()
convoy.fleet.action_cert_create(ctx.config, file_prefix, pfx_password)
@cert.command('add')
@click.option('--file', help='Certificate file to add')
@click.option(
'--pem-no-certs', is_flag=True, help='Do not export certs from PEM file')
@click.option(
'--pem-public-key', is_flag=True, help='Add public key only from PEM file')
@click.option('--pfx-password', help='PFX password')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def cert_add(ctx, file, pem_no_certs, pem_public_key, pfx_password):
"""Add a certificate to a Batch account"""
ctx.initialize_for_batch()
convoy.fleet.action_cert_add(
ctx.batch_client, ctx.config, file, pem_no_certs, pem_public_key,
pfx_password)
@cert.command('list')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def cert_list(ctx):
"""List all certificates in a Batch account"""
ctx.initialize_for_batch()
convoy.fleet.action_cert_list(ctx.batch_client, ctx.config)
@cert.command('del')
@click.option(
'--sha1', multiple=True, help='SHA1 thumbprint of certificate to delete')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def cert_del(ctx, sha1):
"""Delete certificates from a Batch account"""
ctx.initialize_for_batch()
convoy.fleet.action_cert_del(ctx.batch_client, ctx.config, sha1)
@cli.group()
@pass_cli_context
def pool(ctx):
"""Pool actions"""
pass
@pool.command('listskus')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def pool_listskus(ctx):
"""List available VM configurations available to the Batch account"""
ctx.initialize_for_batch()
convoy.fleet.action_pool_listskus(ctx.batch_client, ctx.config)
@pool.command('add')
@common_options
@fs_option
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def pool_add(ctx):
"""Add a pool to the Batch account"""
ctx.initialize_for_batch()
convoy.fleet.action_pool_add(
ctx.resource_client, ctx.compute_client, ctx.network_client,
ctx.batch_mgmt_client, ctx.batch_client, ctx.blob_client,
ctx.table_client, ctx.config)
@pool.command('list')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def pool_list(ctx):
"""List all pools in the Batch account"""
ctx.initialize_for_batch()
convoy.fleet.action_pool_list(ctx.batch_client, ctx.config)
@pool.command('del')
@click.option(
'--poolid', help='Delete the specified pool')
@click.option(
'--wait', is_flag=True, help='Wait for pool deletion to complete')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def pool_del(ctx, poolid, wait):
"""Delete a pool from the Batch account"""
ctx.initialize_for_batch()
convoy.fleet.action_pool_delete(
ctx.batch_client, ctx.blob_client, ctx.table_client, ctx.config,
pool_id=poolid, wait=wait)
@pool.command('resize')
@click.option(
'--wait', is_flag=True, help='Wait for pool resize to complete')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def pool_resize(ctx, wait):
"""Resize a pool"""
ctx.initialize_for_batch()
convoy.fleet.action_pool_resize(
ctx.batch_client, ctx.blob_client, ctx.config, wait=wait)
@pool.command('ssh')
@click.option(
'--cardinal',
help='Zero-based cardinal number of compute node in pool to connect to',
type=int)
@click.option(
'--nodeid', help='NodeId of compute node in pool to connect to')
@click.option(
'--tty', is_flag=True, help='Allocate a pseudo-tty')
@click.argument('command', nargs=-1)
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def pool_ssh(ctx, cardinal, nodeid, tty, command):
"""Interactively login via SSH to a node in a pool"""
ctx.initialize_for_batch()
convoy.fleet.action_pool_ssh(
ctx.batch_client, ctx.config, cardinal, nodeid, tty, command)
@pool.command('rdp')
@click.option(
'--cardinal',
help='Zero-based cardinal number of compute node in pool to connect to',
type=int)
@click.option(
'--no-auto', is_flag=True,
help='Do not automatically login if RDP password is present')
@click.option(
'--nodeid', help='NodeId of compute node in pool to connect to')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def pool_rdp(ctx, cardinal, no_auto, nodeid):
"""Interactively login via RDP to a node in a pool"""
ctx.initialize_for_batch()
convoy.fleet.action_pool_rdp(
ctx.batch_client, ctx.config, cardinal, nodeid, no_auto=no_auto)
@pool.command('stats')
@click.option('--poolid', help='Get stats on specified pool')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def pool_stats(ctx, poolid):
"""Get statistics about a pool"""
ctx.initialize_for_batch()
convoy.fleet.action_pool_stats(
ctx.batch_client, ctx.config, pool_id=poolid)
@pool.group()
@pass_cli_context
def autoscale(ctx):
"""Autoscale actions"""
pass
@autoscale.command('disable')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def autoscale_disable(ctx):
"""Disable autoscale on a pool"""
ctx.initialize_for_batch()
convoy.fleet.action_pool_autoscale_disable(ctx.batch_client, ctx.config)
@autoscale.command('enable')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def autoscale_enable(ctx):
"""Enable autoscale on a pool"""
ctx.initialize_for_batch()
convoy.fleet.action_pool_autoscale_enable(ctx.batch_client, ctx.config)
@autoscale.command('evaluate')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def autoscale_evaluate(ctx):
"""Evaluate autoscale formula"""
ctx.initialize_for_batch()
convoy.fleet.action_pool_autoscale_evaluate(ctx.batch_client, ctx.config)
@autoscale.command('lastexec')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def autoscale_lastexec(ctx):
"""Get the result of the last execution of the autoscale formula"""
ctx.initialize_for_batch()
convoy.fleet.action_pool_autoscale_lastexec(ctx.batch_client, ctx.config)
@pool.group()
@pass_cli_context
def images(ctx):
"""Container images actions"""
pass
@images.command('update')
@click.option(
'--docker-image', help='Docker image[:tag] to update')
@click.option(
'--docker-image-digest', help='Digest to update Docker image to')
@click.option(
'--singularity-image', help='Singularity image[:tag] to update')
@click.option(
'--ssh', is_flag=True, help='Update over SSH instead of using a Batch job')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def images_update(
ctx, docker_image, docker_image_digest, singularity_image, ssh):
"""Update container images in a pool"""
ctx.initialize_for_batch()
convoy.fleet.action_pool_images_update(
ctx.batch_client, ctx.config, docker_image, docker_image_digest,
singularity_image, ssh)
@images.command('list')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def images_list(ctx):
"""List container images in a pool"""
ctx.initialize_for_batch()
convoy.fleet.action_pool_images_list(ctx.batch_client, ctx.config)
@pool.group()
@pass_cli_context
def user(ctx):
"""Remote user actions"""
pass
@user.command('add')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def user_add(ctx):
"""Add a remote user to all nodes in pool"""
ctx.initialize_for_batch()
convoy.fleet.action_pool_user_add(ctx.batch_client, ctx.config)
@user.command('del')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def user_del(ctx):
"""Delete a remote user from all nodes in pool"""
ctx.initialize_for_batch()
convoy.fleet.action_pool_user_del(ctx.batch_client, ctx.config)
@pool.group()
@pass_cli_context
def nodes(ctx):
"""Compute node actions"""
pass
@nodes.command('grls')
@click.option(
'--no-generate-tunnel-script', is_flag=True,
help='Disable generating an SSH tunnel script')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def nodes_grls(ctx, no_generate_tunnel_script):
"""Get remote login settings for all nodes in pool"""
ctx.initialize_for_batch()
convoy.fleet.action_pool_nodes_grls(
ctx.batch_client, ctx.config, no_generate_tunnel_script)
@nodes.command('list')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def nodes_list(ctx):
"""List nodes in pool"""
ctx.initialize_for_batch()
convoy.fleet.action_pool_nodes_list(ctx.batch_client, ctx.config)
@nodes.command('zap')
@click.option(
'--no-remove', is_flag=True, help='Do not remove exited containers')
@click.option(
'--stop', is_flag=True, help='Use docker stop instead of kill')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def nodes_zap(ctx, no_remove, stop):
"""Zap all container processes on nodes in pool"""
ctx.initialize_for_batch()
convoy.fleet.action_pool_nodes_zap(
ctx.batch_client, ctx.config, not no_remove, stop)
@nodes.command('prune')
@click.option(
'--volumes', is_flag=True, help='Prune volumes as well')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def nodes_prune(ctx, volumes):
"""Prune container/image data on nodes in pool"""
ctx.initialize_for_batch()
convoy.fleet.action_pool_nodes_prune(ctx.batch_client, ctx.config, volumes)
@nodes.command('ps')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def nodes_ps(ctx):
"""List running containers on nodes in pool"""
ctx.initialize_for_batch()
convoy.fleet.action_pool_nodes_ps(ctx.batch_client, ctx.config)
@nodes.command('del')
@click.option(
'--all-start-task-failed',
is_flag=True,
help='Delete all nodes in start task failed state')
@click.option(
'--all-starting',
is_flag=True,
help='Delete all nodes in starting state')
@click.option(
'--all-unusable',
is_flag=True,
help='Delete all nodes in unusable state')
@click.option(
'--nodeid', multiple=True,
help='NodeId of compute node in pool to delete')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def nodes_del(
ctx, all_start_task_failed, all_starting, all_unusable, nodeid):
"""Delete a node or nodes from a pool"""
ctx.initialize_for_batch()
convoy.fleet.action_pool_nodes_del(
ctx.batch_client, ctx.config, all_start_task_failed, all_starting,
all_unusable, nodeid)
@nodes.command('reboot')
@click.option(
'--all-start-task-failed',
is_flag=True,
help='Reboot all nodes in start task failed state')
@click.option(
'--nodeid', multiple=True,
help='NodeId of compute node in pool to reboot')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def nodes_reboot(ctx, all_start_task_failed, nodeid):
"""Reboot a node or nodes in a pool"""
ctx.initialize_for_batch()
convoy.fleet.action_pool_nodes_reboot(
ctx.batch_client, ctx.config, all_start_task_failed, nodeid)
@cli.group()
@pass_cli_context
def jobs(ctx):
"""Jobs actions"""
pass
@jobs.command('add')
@click.option(
'--recreate', is_flag=True,
help='Recreate any completed jobs with the same id')
@click.option(
'--tail',
help='Tails the specified file of the last job and task added')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def jobs_add(ctx, recreate, tail):
"""Add jobs"""
ctx.initialize_for_batch()
convoy.fleet.action_jobs_add(
ctx.resource_client, ctx.compute_client, ctx.network_client,
ctx.batch_mgmt_client, ctx.batch_client, ctx.blob_client,
ctx.table_client, ctx.keyvault_client, ctx.config, recreate, tail)
@jobs.command('list')
@click.option(
'--jobid', help='Get the specified job id')
@click.option(
'--jobscheduleid', help='Terminate just the specified job schedule id')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def jobs_list(ctx, jobid, jobscheduleid):
"""List jobs"""
ctx.initialize_for_batch()
convoy.fleet.action_jobs_list(
ctx.batch_client, ctx.config, jobid, jobscheduleid)
@jobs.command('term')
@click.option(
'--all-jobs', is_flag=True, help='Terminate all jobs in Batch account')
@click.option(
'--all-jobschedules', is_flag=True,
help='Terminate all job schedules in Batch account')
@click.option(
'--jobid', help='Terminate just the specified job id')
@click.option(
'--jobscheduleid', help='Terminate just the specified job schedule id')
@click.option(
'--termtasks', is_flag=True, help='Terminate tasks running in job first')
@click.option(
'--wait', is_flag=True, help='Wait for jobs termination to complete')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def jobs_term(
ctx, all_jobs, all_jobschedules, jobid, jobscheduleid, termtasks,
wait):
"""Terminate jobs and job schedules"""
ctx.initialize_for_batch()
convoy.fleet.action_jobs_del_or_term(
ctx.batch_client, ctx.blob_client, ctx.table_client, ctx.config,
False, all_jobs, all_jobschedules, jobid, jobscheduleid, termtasks,
wait)
@jobs.command('del')
@click.option(
'--all-jobs', is_flag=True, help='Delete all jobs in Batch account')
@click.option(
'--all-jobschedules', is_flag=True,
help='Delete all job schedules in Batch account')
@click.option(
'--jobid', help='Delete just the specified job id')
@click.option(
'--jobscheduleid', help='Delete just the specified job schedule id')
@click.option(
'--termtasks', is_flag=True, help='Terminate tasks running in job first')
@click.option(
'--wait', is_flag=True, help='Wait for jobs deletion to complete')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def jobs_del(
ctx, all_jobs, all_jobschedules, jobid, jobscheduleid, termtasks,
wait):
"""Delete jobs and job schedules"""
ctx.initialize_for_batch()
convoy.fleet.action_jobs_del_or_term(
ctx.batch_client, ctx.blob_client, ctx.table_client, ctx.config,
True, all_jobs, all_jobschedules, jobid, jobscheduleid, termtasks,
wait)
@jobs.command('cmi')
@click.option(
'--delete', is_flag=True,
help='Delete all cleanup multi-instance jobs in Batch account')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def jobs_cmi(ctx, delete):
"""Cleanup non-native multi-instance jobs"""
ctx.initialize_for_batch()
convoy.fleet.action_jobs_cmi(ctx.batch_client, ctx.config, delete)
@jobs.command('migrate')
@click.option(
'--jobid', help='Migrate only the specified job id')
@click.option(
'--jobscheduleid', help='Migrate only the specified job schedule id')
@click.option(
'--poolid', help='Target specified pool id rather than from configuration')
@click.option(
'--requeue', is_flag=True, help='Requeue running tasks in job')
@click.option(
'--terminate', is_flag=True, help='Terminate running tasks in job')
@click.option(
'--wait', is_flag=True, help='Wait for running tasks to complete in job')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def jobs_migrate(ctx, jobid, jobscheduleid, poolid, requeue, terminate, wait):
"""Migrate jobs or job schedules to another pool"""
ctx.initialize_for_batch()
convoy.fleet.action_jobs_migrate(
ctx.batch_client, ctx.config, jobid, jobscheduleid, poolid, requeue,
terminate, wait)
@jobs.command('disable')
@click.option(
'--jobid', help='Disable only the specified job id')
@click.option(
'--jobscheduleid', help='Disable only the specified job schedule id')
@click.option(
'--requeue', is_flag=True, help='Requeue running tasks in job')
@click.option(
'--terminate', is_flag=True, help='Terminate running tasks in job')
@click.option(
'--wait', is_flag=True, help='Wait for running tasks to complete in job')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def jobs_disable(ctx, jobid, jobscheduleid, requeue, terminate, wait):
"""Disable jobs and job schedules"""
ctx.initialize_for_batch()
convoy.fleet.action_jobs_disable(
ctx.batch_client, ctx.config, jobid, jobscheduleid, requeue,
terminate, wait)
@jobs.command('enable')
@click.option(
'--jobid', help='Enable only the specified job id')
@click.option(
'--jobscheduleid', help='Enable only the specified job schedule id')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def jobs_enable(ctx, jobid, jobscheduleid):
"""Enable jobs and job schedules"""
ctx.initialize_for_batch()
convoy.fleet.action_jobs_enable(
ctx.batch_client, ctx.config, jobid, jobscheduleid)
@jobs.command('stats')
@click.option('--jobid', help='Get stats only on the specified job id')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def jobs_stats(ctx, jobid):
"""Get statistics about jobs"""
ctx.initialize_for_batch()
convoy.fleet.action_jobs_stats(ctx.batch_client, ctx.config, job_id=jobid)
@jobs.group()
@pass_cli_context
def tasks(ctx):
"""Tasks actions"""
pass
@tasks.command('list')
@click.option(
'--all', is_flag=True, help='List tasks in all jobs in account')
@click.option(
'--jobid', help='List tasks in the specified job id')
@click.option(
'--poll-until-tasks-complete', is_flag=True,
help='Poll until all tasks are in completed state')
@click.option(
'--taskid', help='Get specified task within the specified job id')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def tasks_list(ctx, all, jobid, poll_until_tasks_complete, taskid):
"""List tasks within jobs"""
ctx.initialize_for_batch()
convoy.fleet.action_jobs_tasks_list(
ctx.batch_client, ctx.config, all, jobid,
poll_until_tasks_complete, taskid)
@tasks.command('term')
@click.option(
'--force', is_flag=True,
help='Force docker kill signal to task regardless of state')
@click.option(
'--jobid', help='Terminate tasks in the specified job id')
@click.option(
'--taskid', help='Terminate tasks in the specified task id')
@click.option(
'--wait', is_flag=True, help='Wait for task termination to complete')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def tasks_term(ctx, force, jobid, taskid, wait):
"""Terminate specified tasks in jobs"""
ctx.initialize_for_batch()
convoy.fleet.action_jobs_tasks_term(
ctx.batch_client, ctx.config, jobid, taskid, wait, force)
@tasks.command('del')
@click.option(
'--jobid', help='Delete tasks in the specified job id')
@click.option(
'--taskid', help='Delete tasks in the specified task id')
@click.option(
'--wait', is_flag=True, help='Wait for task deletion to complete')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def tasks_del(ctx, jobid, taskid, wait):
"""Delete specified tasks in jobs"""
ctx.initialize_for_batch()
convoy.fleet.action_jobs_tasks_del(
ctx.batch_client, ctx.config, jobid, taskid, wait)
@cli.group()
@pass_cli_context
def data(ctx):
"""Data actions"""
pass
@data.command('ingress')
@click.option(
'--to-fs', help='Ingress data to specified remote filesystem')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def data_ingress(ctx, to_fs):
"""Ingress data into Azure"""
ctx.initialize_for_batch()
convoy.fleet.action_data_ingress(
ctx.batch_client, ctx.compute_client, ctx.network_client, ctx.config,
to_fs)
@data.group()
@pass_cli_context
def files(ctx):
"""Compute node file actions"""
pass
@files.command('list')
@click.option(
'--jobid', help='List files from the specified job id')
@click.option(
'--taskid', help='List files from the specified task id')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def files_list(ctx, jobid, taskid):
"""List files for tasks in jobs"""
ctx.initialize_for_batch()
convoy.fleet.action_data_files_list(
ctx.batch_client, ctx.config, jobid, taskid)
@files.command('stream')
@click.option(
'--disk', is_flag=True,
help='Write streamed data to disk and suppress console output')
@click.option(
'--filespec', help='File specification as jobid,taskid,filename')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def files_stream(ctx, disk, filespec):
"""Stream a file as text to the local console or as binary to disk"""
ctx.initialize_for_batch()
convoy.fleet.action_data_files_stream(
ctx.batch_client, ctx.config, filespec, disk)
@files.command('task')
@click.option(
'--all', is_flag=True, help='Retrieve all files for given job/task')
@click.option(
'--filespec',
help='File specification as jobid,taskid,filename or '
'jobid,taskid,include_pattern if invoked with --all')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def files_task(ctx, all, filespec):
"""Retrieve file(s) from a job/task"""
ctx.initialize_for_batch()
convoy.fleet.action_data_files_task(
ctx.batch_client, ctx.config, all, filespec)
@files.command('node')
@click.option(
'--all', is_flag=True, help='Retrieve all files for given compute node')
@click.option(
'--filespec', help='File specification as nodeid,filename or '
'nodeid,include_pattern if invoked with --all')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def files_node(ctx, all, filespec):
"""Retrieve file(s) from a compute node"""
ctx.initialize_for_batch()
convoy.fleet.action_data_files_node(
ctx.batch_client, ctx.config, all, filespec)
@cli.group()
@pass_cli_context
def diag(ctx):
"""Diagnostics actions"""
pass
@diag.group()
@pass_cli_context
def logs(ctx):
"""Diagnostic log actions"""
pass
@logs.command('upload')
@click.option(
'--cardinal',
help='Zero-based cardinal number of compute node in pool to egress '
'service logs from',
type=int)
@click.option(
'--nodeid', help='NodeId of compute node in to egress service logs from')
@click.option(
'--wait', is_flag=True, help='Wait for log upload to complete')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def diag_logs_upload(ctx, cardinal, nodeid, wait):
"""Upload Batch Service Logs from compute node"""
ctx.initialize_for_batch()
convoy.fleet.action_diag_logs_upload(
ctx.batch_client, ctx.blob_client, ctx.config, cardinal, nodeid, wait)
@cli.group()
@pass_cli_context
def misc(ctx):
"""Miscellaneous actions"""
pass
@misc.command('tensorboard')
@click.option(
'--jobid', help='Tensorboard to the specified job id')
@click.option(
'--taskid', help='Tensorboard to the specified task id')
@click.option(
'--logdir', help='logdir for Tensorboard')
@click.option(
'--image',
help='Use specified TensorFlow Docker image instead. tensorboard.py '
'must be in the expected location in the Docker image.')
@common_options
@batch_options
@keyvault_options
@aad_options
@pass_cli_context
def misc_tensorboard(ctx, jobid, taskid, logdir, image):
"""Create a tunnel to a Tensorboard instance for a specific task"""
ctx.initialize_for_batch()
convoy.fleet.action_misc_tensorboard(
ctx.batch_client, ctx.config, jobid, taskid, logdir, image)
if __name__ == '__main__':
convoy.util.setup_logger(logger)
cli()