class BuildCommand(Command): """ Builds a kit. """ arguments = [ Argument('-v', '--version', dest='kit_version', default=None, help='Override the x.y.z version in kit.json'), Argument('-i', '--ignore-directory-version', dest='ignore_directory_version', action='store_true', default=False, help='Do not rename package to tortuga_kits/<name>_<version>') ] name = 'build' help = 'Builds the kit in the current directory' def execute(self, args: argparse.Namespace): builder = KitBuilder( version=args.kit_version, ignore_directory_version=args.ignore_directory_version) builder.build()
class InstallCommand(Command): """ Install an extension. """ name = 'install' help = 'Install an extension' arguments = [ Argument('name', help='The name of the extension'), Argument('--upgrade', action='store_true', default=False, help='Upgrade the extension to the latest version') ] def execute(self, args: argparse.Namespace): available_extensions = get_available_extensions(args) if args.name not in available_extensions: raise Exception('{} is not a valid extension name'.format( args.name)) installer = get_installer(args) pip_cmd = [ 'pip', 'install', '--extra-index-url', get_python_package_repo(installer), '--trusted-host', installer ] if args.upgrade: pip_cmd.append('--upgrade') pip_cmd.append(args.name) subprocess.Popen(pip_cmd).wait()
class ListCommand(Command): """ This command lists all items on an API endpoint. """ name = 'list' help = 'List all {}' arguments = [ Argument( '-q', '--query', type=str, nargs='*', help='List query parameters' ) ] def get_help(self): # # Since this class can be used for multiple endpoints, we want to # customize the help text by inserting the endpoint name as # required # endpoint: str = self.parent.name return super().get_help().format(endpoint) def execute(self, args: argparse.Namespace): config: TortugaScriptConfig = self.get_config() ws_client = get_client(config, self.parent.name) query = args.query if not query: query = [] params = self._parse_params(query) pretty_print(ws_client.list(**params), args.fmt) def _parse_params(self, query: List[str]) -> Dict[str, str]: """ Takes a list of --query parameters and turns them into a dict suitable for passing to a funtion as **kwargs :param List[str] query: a list of "key=value" query arguments to to parse :return Dict[str, str]: a dictionary of {key: value} pairs """ params = {} for q in query: parts = q.split('=') params[parts[0]] = parts[1] return params
class ExtensionsCommand(RootCommand): """ Command for managing Tortuga CLI extensions. """ name = 'extensions' help = 'Manage Tortuga CLI extensions' sub_commands = [ListCommand(), InstallCommand()] arguments = [Argument('--all', action='store_true', default=False)]
class TortugaScript(Cli): """ The Tortuga Cli implementation. """ command_package = 'tortuga.scripts.tortuga.commands' arguments = [ Argument('-v', '--version', action='store_true', dest='version', default=False, help='print version and exit'), Argument('-d', '--debug', dest='debug', help='set debug level; valid values are: critical, error, ' 'warning, info, debug'), Argument('--config', dest='config', help='Path to config file (defaults to ~/.tortuga/config)'), Argument('--url', help='Web service URL'), Argument('--username', dest='username', help='Web service user name'), Argument('--password', dest='password', help='Web service password'), Argument('--token', dest='token', help='Web service token'), Argument('--no-verify', dest='verify', default=True, action='store_false', help="Don't verify the API SSL certificate"), Argument( '--json', dest='fmt', default='yaml', action='store_const', const='json', help='Output as JSON', ) ] def __init__(self): super().__init__() self._config: Optional[TortugaScriptConfig] = None def get_command_package(self): return 'tortuga.cli.commands' def pre_execute(self, args: argparse.Namespace): self._version(args) self._set_log_level(args) self._load_config(args) def _version(self, args: argparse.Namespace): """ Implements the --version argument. """ if args.version: cm = ConfigManager() print('{0} version: {1}'.format(os.path.basename(sys.argv[0]), cm.getTortugaRelease())) sys.exit(0) def _set_log_level(self, args: argparse.Namespace): """ Implements the --debug argument. """ if args.debug: root_logger = logging.getLogger(ROOT_NAMESPACE) root_logger.setLevel(logging.DEBUG) ch = logging.StreamHandler() ch.setLevel(logging.DEBUG) formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s') ch.setFormatter(formatter) root_logger.addHandler(ch) def _load_config(self, args: argparse.Namespace): """ Implements the --config argument. """ # # Load a config, filename may or may-not be provided... # try: self._config = TortugaScriptConfig.load(args.config) except ConfigException as ex: print(str(ex)) sys.exit(0) # # Override the config with any provided argument values # if args.url: self._config.url = args.url if args.username: self._config.username = args.username if args.password: self._config.password = args.password if args.token: self._config.token = args.token self._config.verify = args.verify def get_config(self) -> Optional[TortugaScriptConfig]: return self._config
class TortugaScript(Cli): """ The Tortuga Cli implementation. """ command_package = 'tortuga.scripts.tortuga.commands' arguments = [ Argument('-v', '--version', action='store_true', dest='version', default=False, help='print version and exit'), Argument('-d', '--debug', dest='debug', help='set debug level; valid values are: critical, error, ' 'warning, info, debug'), Argument('--url', help='Web service URL'), Argument('--username', dest='username', help='Web service user name'), Argument('--password', dest='password', help='Web service password'), Argument('--no-verify', dest='verify', default=True, action='store_false', help="Don't verify the API SSL certificate"), Argument( '--json', dest='fmt', default='yaml', action='store_const', const='json', help='Output as JSON', ) ] def get_command_package(self): return 'tortuga.cli.commands' def pre_execute(self, args: argparse.Namespace): self._version(args) self._set_log_level(args) def _version(self, args: argparse.Namespace): """ Implements the --version argument. """ if args.version: cm = ConfigManager() print('{0} version: {1}'.format(os.path.basename(sys.argv[0]), cm.getTortugaRelease())) sys.exit(0) def _set_log_level(self, args: argparse.Namespace): """ Implements the --debug argument. """ if args.debug: root_logger = logging.getLogger(ROOT_NAMESPACE) root_logger.setLevel(logging.DEBUG) ch = logging.StreamHandler() ch.setLevel(logging.DEBUG) formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s') ch.setFormatter(formatter) root_logger.addHandler(ch)
class LicenseUsageCommand(Command): """ Grab the usage between 2 dates and write to CSV file. IAM permission 'ce:GetCostAndUsage' is required to gather the data. """ name = 'usage' help = 'Create usage report' arguments = [ Argument('--start', help='YYYY-MM-DD', required=True), Argument('--end', help='YYYY-MM-DD', required=True), Argument('-o', '--output', help='Specify output file path', type=str, default=None) ] def __init__(self) -> None: """ Initialise client and logger. :returns: None """ super(LicenseUsageCommand, self).__init__() self._args = None self._client = boto3.client('ce') self._cm = ConfigManager() def execute(self, args: argparse.Namespace) -> None: """ Iterate over each row and write to file. :returns: None """ self._args = args start, end = self._string_to_date() if self._args.output: path: str = os.path.abspath(self._args.output) writer = PathWriter(path) else: writer = StreamWriter() with writer as f: for row in self._get_data(start, end): f.write(json.dumps(row)) def _string_to_date(self) -> Tuple[date]: """ Convert the arguments to date objects. :returns: Tuple Date Date """ try: start: date = date.fromisoformat(self._args.start) end: date = date.fromisoformat(self._args.end) except AttributeError: # Above method only in 3.7. split_start: Tuple[str] = self._args.start.split('-') split_end: Tuple[str] = self._args.end.split('-') split_start: Tuple[int] = map(int, split_start) split_end: Tuple[int] = map(int, split_end) start: date = date(*split_start) end: date = date(*split_end) return start, end def _get_data(self, start: date, end: date) -> Generator[Dict[Any, Any], None, None]: """ Get the usage data from AWS. :param start: Date :param end: Date :returns: Generator Dictionary """ page_token: Optional[str] = None while True: if page_token: kwargs: dict = {'NextPageToken': page_token} else: kwargs: dict = {} response: Dict[Any, Any] = self._client.get_cost_and_usage( TimePeriod={ 'Start': start.strftime('%Y-%m-%d'), 'End': end.strftime('%Y-%m-%d') }, Granularity='MONTHLY', Metrics=['UsageQuantity'], GroupBy=[{ 'Type': 'DIMENSION', 'Key': 'SERVICE', }, { 'Type': 'DIMENSION', 'Key': 'INSTANCE_TYPE' }], Filter={ 'And': [{ 'Tags': { 'Key': 'tortuga:installer_hostname', 'Values': [self._cm.getHost()] } }, { 'Dimensions': { 'Key': 'USAGE_TYPE_GROUP', 'Values': ['EC2: Running Hours'] } }] }, **kwargs) for result in response['ResultsByTime']: yield result page_token = response.get('NextPageToken', None) if not page_token: break