Coverage for nova/cmd/common.py: 59%

90 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-04-17 15:08 +0000

1# Copyright 2016 Cloudbase Solutions Srl 

2# All Rights Reserved. 

3# 

4# Licensed under the Apache License, Version 2.0 (the "License"); you may 

5# not use this file except in compliance with the License. You may obtain 

6# a copy of the License at 

7# 

8# http://www.apache.org/licenses/LICENSE-2.0 

9# 

10# Unless required by applicable law or agreed to in writing, software 

11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 

12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 

13# License for the specific language governing permissions and limitations 

14# under the License. 

15 

16""" 

17 Common functions used by different CLI interfaces. 

18""" 

19 

20import argparse 

21import inspect 

22 

23from oslo_log import log as logging 

24 

25import nova.conf 

26import nova.db.main.api 

27from nova import exception 

28from nova.i18n import _ 

29 

30CONF = nova.conf.CONF 

31LOG = logging.getLogger(__name__) 

32 

33 

34def validate_args(fn, *args, **kwargs): 

35 """Check that the supplied args are sufficient for calling a function. 

36 

37 >>> validate_args(lambda a: None) 

38 Traceback (most recent call last): 

39 ... 

40 MissingArgs: Missing argument(s): a 

41 >>> validate_args(lambda a, b, c, d: None, 0, c=1) 

42 Traceback (most recent call last): 

43 ... 

44 MissingArgs: Missing argument(s): b, d 

45 

46 :param fn: the function to check 

47 :param arg: the positional arguments supplied 

48 :param kwargs: the keyword arguments supplied 

49 """ 

50 argspec = inspect.getfullargspec(fn) 

51 

52 num_defaults = len(argspec.defaults or []) 

53 required_args = argspec.args[:len(argspec.args) - num_defaults] 

54 

55 if fn.__self__ is not None: 

56 required_args.pop(0) 

57 

58 missing = [arg for arg in required_args if arg not in kwargs] 

59 missing = missing[len(args):] 

60 return missing 

61 

62 

63# Decorators for actions 

64def args(*args, **kwargs): 

65 """Decorator which adds the given args and kwargs to the args list of 

66 the desired func's __dict__. 

67 """ 

68 def _decorator(func): 

69 func.__dict__.setdefault('args', []).insert(0, (args, kwargs)) 

70 return func 

71 return _decorator 

72 

73 

74def methods_of(obj): 

75 """Get all callable methods of an object that don't start with underscore 

76 

77 returns a list of tuples of the form (method_name, method) 

78 """ 

79 result = [] 

80 for i in dir(obj): 

81 if callable(getattr(obj, i)) and not i.startswith('_'): 

82 result.append((i, getattr(obj, i))) 

83 return result 

84 

85 

86def add_command_parsers(subparsers, categories): 

87 """Adds command parsers to the given subparsers. 

88 

89 Adds version and bash-completion parsers. 

90 Adds a parser with subparsers for each category in the categories dict 

91 given. 

92 """ 

93 parser = subparsers.add_parser('version') 

94 

95 parser = subparsers.add_parser('bash-completion') 

96 parser.add_argument('query_category', nargs='?') 

97 

98 for category in categories: 

99 command_object = categories[category]() 

100 

101 desc = getattr(command_object, 'description', None) 

102 parser = subparsers.add_parser(category, description=desc) 

103 parser.set_defaults(command_object=command_object) 

104 

105 category_subparsers = parser.add_subparsers(dest='action') 

106 category_subparsers.required = True 

107 

108 for (action, action_fn) in methods_of(command_object): 

109 parser = category_subparsers.add_parser( 

110 action, description=getattr(action_fn, 'description', desc)) 

111 

112 action_kwargs = [] 

113 for args, kwargs in getattr(action_fn, 'args', []): 

114 # we must handle positional parameters (ARG) separately from 

115 # positional parameters (--opt). Detect this by checking for 

116 # the presence of leading '--' 

117 if args[0] != args[0].lstrip('-'): 

118 kwargs.setdefault('dest', args[0].lstrip('-')) 

119 if kwargs['dest'].startswith('action_kwarg_'): 

120 action_kwargs.append( 

121 kwargs['dest'][len('action_kwarg_'):]) 

122 else: 

123 action_kwargs.append(kwargs['dest']) 

124 kwargs['dest'] = 'action_kwarg_' + kwargs['dest'] 

125 else: 

126 action_kwargs.append(args[0]) 

127 args = ['action_kwarg_' + arg for arg in args] 

128 

129 parser.add_argument(*args, **kwargs) 

130 

131 parser.set_defaults(action_fn=action_fn) 

132 parser.set_defaults(action_kwargs=action_kwargs) 

133 

134 parser.add_argument('action_args', nargs='*', 

135 help=argparse.SUPPRESS) 

136 

137 

138def print_bash_completion(categories): 

139 if not CONF.category.query_category: 

140 print(" ".join(categories.keys())) 

141 elif CONF.category.query_category in categories: 

142 fn = categories[CONF.category.query_category] 

143 command_object = fn() 

144 actions = methods_of(command_object) 

145 print(" ".join([k for (k, v) in actions])) 

146 

147 

148def get_action_fn(): 

149 fn = CONF.category.action_fn 

150 fn_args = [] 

151 for arg in CONF.category.action_args: 

152 if isinstance(arg, bytes): 152 ↛ 153line 152 didn't jump to line 153 because the condition on line 152 was never true

153 arg = arg.decode('utf-8') 

154 fn_args.append(arg) 

155 

156 fn_kwargs = {} 

157 for k in CONF.category.action_kwargs: 

158 v = getattr(CONF.category, 'action_kwarg_' + k) 

159 if v is None: 

160 continue 

161 if isinstance(v, bytes): 161 ↛ 162line 161 didn't jump to line 162 because the condition on line 161 was never true

162 v = v.decode('utf-8') 

163 fn_kwargs[k] = v 

164 

165 # call the action with the remaining arguments 

166 # check arguments 

167 missing = validate_args(fn, *fn_args, **fn_kwargs) 

168 if missing: 

169 # NOTE(mikal): this isn't the most helpful error message ever. It is 

170 # long, and tells you a lot of things you probably don't want to know 

171 # if you just got a single arg wrong. 

172 print(fn.__doc__) 

173 CONF.print_help() 

174 raise exception.Invalid( 

175 _("Missing arguments: %s") % ", ".join(missing)) 

176 

177 return fn, fn_args, fn_kwargs 

178 

179 

180def action_description(text): 

181 """Decorator for adding a description to command action. 

182 

183 To display help text on action call instead of common category help text 

184 action function can be decorated. 

185 

186 command <category> <action> -h will show description and arguments. 

187 

188 """ 

189 def _decorator(func): 

190 func.description = text 

191 return func 

192 return _decorator