Fix some bugs when testing opensds ansible
[stor4nfv.git] / src / ceph / src / tools / rbd / Shell.cc
1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
3
4 #include "tools/rbd/Shell.h"
5 #include "tools/rbd/ArgumentTypes.h"
6 #include "tools/rbd/IndentStream.h"
7 #include "tools/rbd/OptionPrinter.h"
8 #include "common/config.h"
9 #include "global/global_context.h"
10 #include "include/stringify.h"
11 #include <algorithm>
12 #include <iostream>
13 #include <set>
14
15 namespace rbd {
16
17 namespace at = argument_types;
18 namespace po = boost::program_options;
19
20 namespace {
21
22 static const std::string APP_NAME("rbd");
23 static const std::string HELP_SPEC("help");
24 static const std::string BASH_COMPLETION_SPEC("bash-completion");
25
26 std::string format_command_spec(const Shell::CommandSpec &spec) {
27   return joinify<std::string>(spec.begin(), spec.end(), " ");
28 }
29
30 std::string format_command_name(const Shell::CommandSpec &spec,
31                                 const Shell::CommandSpec &alias_spec) {
32   std::string name = format_command_spec(spec);
33   if (!alias_spec.empty()) {
34     name += " (" + format_command_spec(alias_spec) + ")";
35   }
36   return name;
37 }
38
39 std::string format_option_suffix(
40     const boost::shared_ptr<po::option_description> &option) {
41   std::string suffix;
42   if (option->semantic()->max_tokens() != 0) {
43     if (option->description().find("path") != std::string::npos ||
44         option->description().find("file") != std::string::npos) {
45       suffix += " path";
46     } else if (option->description().find("host") != std::string::npos) {
47       suffix += " host";
48     } else {
49       suffix += " arg";
50     }
51   }
52   return suffix;
53 }
54
55 } // anonymous namespace
56
57 std::vector<Shell::Action *>& Shell::get_actions() {
58   static std::vector<Action *> actions;
59
60   return actions;
61 }
62
63 std::set<std::string>& Shell::get_switch_arguments() {
64   static std::set<std::string> switch_arguments;
65
66   return switch_arguments;
67 }
68
69 int Shell::execute(const Arguments& cmdline_arguments) {
70
71   std::vector<std::string> arguments(cmdline_arguments.begin(),
72                                      cmdline_arguments.end());
73   std::vector<std::string> command_spec;
74   get_command_spec(arguments, &command_spec);
75   bool is_alias = true;
76
77   if (command_spec.empty() || command_spec == CommandSpec({"help"})) {
78     // list all available actions
79     print_help();
80     return 0;
81   } else if (command_spec[0] == HELP_SPEC) {
82     // list help for specific action
83     command_spec.erase(command_spec.begin());
84     Action *action = find_action(command_spec, NULL, &is_alias);
85     if (action == NULL) {
86       print_unknown_action(command_spec);
87       return EXIT_FAILURE;
88     } else {
89       print_action_help(action, is_alias);
90       return 0;
91     }
92   } else if (command_spec[0] == BASH_COMPLETION_SPEC) {
93     command_spec.erase(command_spec.begin());
94     print_bash_completion(command_spec);
95     return 0;
96   }
97
98   CommandSpec *matching_spec;
99   Action *action = find_action(command_spec, &matching_spec, &is_alias);
100   if (action == NULL) {
101     print_unknown_action(command_spec);
102     return EXIT_FAILURE;
103   }
104
105   po::variables_map vm;
106   try {
107     po::options_description positional_opts;
108     po::options_description command_opts;
109     (*action->get_arguments)(&positional_opts, &command_opts);
110
111     // dynamically allocate options for our command (e.g. snap list) and
112     // its associated positional arguments
113     po::options_description argument_opts;
114     argument_opts.add_options()
115       (at::POSITIONAL_COMMAND_SPEC.c_str(),
116        po::value<std::vector<std::string> >()->required(), "")
117       (at::POSITIONAL_ARGUMENTS.c_str(),
118        po::value<std::vector<std::string> >(), "");
119
120     po::positional_options_description positional_options;
121     positional_options.add(at::POSITIONAL_COMMAND_SPEC.c_str(),
122                            matching_spec->size());
123     if (!positional_opts.options().empty()) {
124       int max_count = positional_opts.options().size();
125       if (positional_opts.options().back()->semantic()->max_tokens() > 1)
126         max_count = -1;
127       positional_options.add(at::POSITIONAL_ARGUMENTS.c_str(), max_count);
128     }
129
130     po::options_description global_opts;
131     get_global_options(&global_opts);
132
133     po::options_description group_opts;
134     group_opts.add(command_opts)
135               .add(argument_opts)
136               .add(global_opts);
137
138     po::store(po::command_line_parser(arguments)
139       .style(po::command_line_style::default_style &
140         ~po::command_line_style::allow_guessing)
141       .options(group_opts)
142       .positional(positional_options)
143       .run(), vm);
144
145     if (vm[at::POSITIONAL_COMMAND_SPEC].as<std::vector<std::string> >() !=
146           *matching_spec) {
147       std::cerr << "rbd: failed to parse command" << std::endl;
148       return EXIT_FAILURE;
149     }
150
151     int r = (*action->execute)(vm);
152     if (r != 0) {
153       return std::abs(r);
154     }
155   } catch (po::required_option& e) {
156     std::cerr << "rbd: " << e.what() << std::endl;
157     return EXIT_FAILURE;
158   } catch (po::too_many_positional_options_error& e) {
159     std::cerr << "rbd: too many arguments" << std::endl;
160     return EXIT_FAILURE;
161   } catch (po::error& e) {
162     std::cerr << "rbd: " << e.what() << std::endl;
163     return EXIT_FAILURE;
164   }
165
166   return 0;
167 }
168
169 void Shell::get_command_spec(const std::vector<std::string> &arguments,
170                              std::vector<std::string> *command_spec) {
171   for (size_t i = 0; i < arguments.size(); ++i) {
172     std::string arg(arguments[i]);
173     if (arg == "-h" || arg == "--help") {
174       *command_spec = {HELP_SPEC};
175       return;
176     } else if (arg == "--") {
177       // all arguments after a double-dash are positional
178       if (i + 1 < arguments.size()) {
179         command_spec->insert(command_spec->end(),
180                              arguments.data() + i + 1,
181                              arguments.data() + arguments.size());
182       }
183       return;
184     } else if (arg[0] == '-') {
185       // if the option is not a switch, skip its value
186       if (arg.size() >= 2 &&
187           (arg[1] == '-' ||
188            get_switch_arguments().count(arg.substr(1, 1)) == 0) &&
189           (arg[1] != '-' ||
190            get_switch_arguments().count(arg.substr(2, std::string::npos)) == 0) &&
191           at::SWITCH_ARGUMENTS.count(arg.substr(2, std::string::npos)) == 0 &&
192           arg.find('=') == std::string::npos) {
193         ++i;
194       }
195     } else {
196       command_spec->push_back(arg);
197     }
198   }
199 }
200
201 Shell::Action *Shell::find_action(const CommandSpec &command_spec,
202                                   CommandSpec **matching_spec, bool *is_alias) {
203   for (size_t i = 0; i < get_actions().size(); ++i) {
204     Action *action = get_actions()[i];
205     if (action->command_spec.size() <= command_spec.size()) {
206       if (std::includes(action->command_spec.begin(),
207                         action->command_spec.end(),
208                         command_spec.begin(),
209                         command_spec.begin() + action->command_spec.size())) {
210         if (matching_spec != NULL) {
211           *matching_spec = &action->command_spec;
212         }
213         *is_alias = false;
214         return action;
215       }
216     }
217     if (!action->alias_command_spec.empty() &&
218         action->alias_command_spec.size() <= command_spec.size()) {
219       if (std::includes(action->alias_command_spec.begin(),
220                         action->alias_command_spec.end(),
221                         command_spec.begin(),
222                         command_spec.begin() +
223                           action->alias_command_spec.size())) {
224         if (matching_spec != NULL) {
225           *matching_spec = &action->alias_command_spec;
226         }
227         *is_alias = true;
228         return action;
229       }
230     }
231   }
232   return NULL;
233 }
234
235 void Shell::get_global_options(po::options_description *opts) {
236   opts->add_options()
237     ((at::CONFIG_PATH + ",c").c_str(), po::value<std::string>(), "path to cluster configuration")
238     ("cluster", po::value<std::string>(), "cluster name")
239     ("id", po::value<std::string>(), "client id (without 'client.' prefix)")
240     ("user", po::value<std::string>(), "client id (without 'client.' prefix)")
241     ("name,n", po::value<std::string>(), "client name")
242     ("mon_host,m", po::value<std::string>(), "monitor host")
243     ("secret", po::value<at::Secret>(), "path to secret key (deprecated)")
244     ("keyfile,K", po::value<std::string>(), "path to secret key")
245     ("keyring,k", po::value<std::string>(), "path to keyring");
246 }
247
248 void Shell::print_help() {
249   std::cout << "usage: " << APP_NAME << " <command> ..."
250             << std::endl << std::endl
251             << "Command-line interface for managing Ceph RBD images."
252             << std::endl << std::endl;
253
254   std::vector<Action *> actions(get_actions());
255   std::sort(actions.begin(), actions.end(),
256             [](Action *lhs, Action *rhs) { return lhs->command_spec <
257                                                     rhs->command_spec; });
258
259   std::cout << OptionPrinter::POSITIONAL_ARGUMENTS << ":" << std::endl
260             << "  <command>" << std::endl;
261
262   // since the commands have spaces, we have to build our own formatter
263   std::string indent(4, ' ');
264   size_t name_width = OptionPrinter::MIN_NAME_WIDTH;
265   for (size_t i = 0; i < actions.size(); ++i) {
266     Action *action = actions[i];
267     std::string name = format_command_name(action->command_spec,
268                                            action->alias_command_spec);
269     name_width = std::max(name_width, name.size());
270   }
271   name_width += indent.size();
272   name_width = std::min(name_width, OptionPrinter::MAX_DESCRIPTION_OFFSET) + 1;
273
274   for (size_t i = 0; i < actions.size(); ++i) {
275     Action *action = actions[i];
276     if (!action->visible)
277       continue;
278     std::stringstream ss;
279     ss << indent
280        << format_command_name(action->command_spec, action->alias_command_spec);
281
282     std::cout << ss.str();
283     if (!action->description.empty()) {
284       IndentStream indent_stream(name_width, ss.str().size(),
285                                  OptionPrinter::LINE_WIDTH,
286                                  std::cout);
287       indent_stream << action->description << std::endl;
288     } else {
289       std::cout << std::endl;
290     }
291   }
292
293   po::options_description global_opts(OptionPrinter::OPTIONAL_ARGUMENTS);
294   get_global_options(&global_opts);
295   std::cout << std::endl << global_opts << std::endl
296             << "See '" << APP_NAME << " help <command>' for help on a specific "
297             << "command." << std::endl;
298  }
299
300 void Shell::print_action_help(Action *action, bool is_alias) {
301   std::stringstream ss;
302     ss << "usage: " << APP_NAME << " "
303        << format_command_spec(is_alias ? action->alias_command_spec : action->command_spec);
304     std::cout << ss.str();
305
306   po::options_description positional;
307   po::options_description options;
308   (*action->get_arguments)(&positional, &options);
309
310   OptionPrinter option_printer(positional, options);
311   option_printer.print_short(std::cout, ss.str().size());
312
313   if (!action->description.empty()) {
314     std::cout << std::endl << action->description << std::endl;
315   }
316
317   std::cout << std::endl;
318   option_printer.print_detailed(std::cout);
319
320   if (!action->help.empty()) {
321     std::cout << action->help << std::endl;
322   }
323 }
324
325 void Shell::print_unknown_action(const std::vector<std::string> &command_spec) {
326   std::cerr << "error: unknown option '"
327             << joinify<std::string>(command_spec.begin(),
328                                     command_spec.end(), " ") << "'"
329             << std::endl << std::endl;
330   print_help();
331 }
332
333 void Shell::print_bash_completion(const CommandSpec &command_spec) {
334   
335   bool is_alias = true;
336
337   Action *action = find_action(command_spec, NULL, &is_alias);
338   po::options_description global_opts;
339   get_global_options(&global_opts);
340   print_bash_completion_options(global_opts);
341
342   if (action != nullptr) {
343     po::options_description positional_opts;
344     po::options_description command_opts;
345     (*action->get_arguments)(&positional_opts, &command_opts);
346     print_bash_completion_options(command_opts);
347   } else {
348     std::cout << "|help";
349     for (size_t i = 0; i < get_actions().size(); ++i) {
350       Action *action = get_actions()[i];
351       std::cout << "|"
352                 << joinify<std::string>(action->command_spec.begin(),
353                                         action->command_spec.end(), " ");
354       if (!action->alias_command_spec.empty()) {
355         std::cout << "|"
356                    << joinify<std::string>(action->alias_command_spec.begin(),
357                                           action->alias_command_spec.end(),
358                                           " ");
359       }
360     }
361   }
362   std::cout << "|" << std::endl;
363 }
364
365 void Shell::print_bash_completion_options(const po::options_description &ops) {
366   for (size_t i = 0; i < ops.options().size(); ++i) {
367     auto option = ops.options()[i];
368     std::string long_name(option->canonical_display_name(0));
369     std::string short_name(option->canonical_display_name(
370       po::command_line_style::allow_dash_for_short));
371
372     std::cout << "|--" << long_name << format_option_suffix(option);
373     if (long_name != short_name) {
374       std::cout << "|" << short_name << format_option_suffix(option);
375     }
376   }
377 }
378
379 } // namespace rbd