diff options
author | Sergey Poznyakoff <gray@gnu.org.ua> | 2012-12-07 14:57:32 +0200 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org.ua> | 2012-12-07 15:16:14 +0200 |
commit | 5a7b73860974384d8e00065105435403b0842ab0 (patch) | |
tree | 3f029c22f0a29a842002279bb4b5561af61a8aa8 | |
parent | c12cd5695cf1a6c2c44100a68762ab66356f43b8 (diff) | |
download | eclat-5a7b73860974384d8e00065105435403b0842ab0.tar.gz eclat-5a7b73860974384d8e00065105435403b0842ab0.tar.bz2 |
Re-implement confirmation support.
* doc/eclat-delete-volume.1: Update.
* doc/eclat-release-address.1: Update.
* doc/eclat.1: Update.
* doc/eclat.conf.5: New section "CONFIRMATION"
* lib/getyn.c (eclat_vgetyn): Negative default stands for no
default at all.
* lib/confirm.c (eclat_confirm_mode): Remove.
(eclat_confirm): Change signature. Act according to the
first argument.
* lib/libeclat.h (eclat_confirm_mode): New enum.
(eclat_confirm): Change signature.
* src/cmdline.opt: Change handling of -Y and -N options.
* src/config.c: New statement "confirm".
* src/cretags.c: Remove call to eclat_confirm. This is done by
the caller.
* src/delvol.c: Likewise.
* src/reladdr.c: Likewise.
* src/eclat.c (confirm_mode): New variable.
(command) <flags>: New member.
(cmdtab): Mark commands with appropriate flags.
(main): Call eclat_confirm to confirm the command.
* src/eclat.h (confirm_mode): New extern.
(set_command_confirmation): New proto.
* etc/eclat.cfin: Set a reasonably safe confirmation default.
* lib/forlan.c (strtots): Remove unused variable.
-rw-r--r-- | doc/eclat-delete-volume.1 | 14 | ||||
-rw-r--r-- | doc/eclat-release-address.1 | 15 | ||||
-rw-r--r-- | doc/eclat.1 | 34 | ||||
-rw-r--r-- | doc/eclat.conf.5 | 88 | ||||
-rw-r--r-- | etc/eclat.cfin | 3 | ||||
-rw-r--r-- | lib/confirm.c | 28 | ||||
-rw-r--r-- | lib/forlan.c | 1 | ||||
-rw-r--r-- | lib/getyn.c | 14 | ||||
-rw-r--r-- | lib/libeclat.h | 12 | ||||
-rw-r--r-- | src/cmdline.opt | 4 | ||||
-rw-r--r-- | src/config.c | 74 | ||||
-rw-r--r-- | src/cretags.c | 1 | ||||
-rw-r--r-- | src/delvol.c | 2 | ||||
-rw-r--r-- | src/eclat.c | 66 | ||||
-rw-r--r-- | src/eclat.h | 4 | ||||
-rw-r--r-- | src/reladdr.c | 3 |
16 files changed, 286 insertions, 77 deletions
diff --git a/doc/eclat-delete-volume.1 b/doc/eclat-delete-volume.1 index a9d7489..f467d91 100644 --- a/doc/eclat-delete-volume.1 +++ b/doc/eclat-delete-volume.1 @@ -29,20 +29,6 @@ in .BR eclat (1)), this command treats the \fBVOL\-ID\fR as volume names and translates it to the AWS IDs using the \fBVolumeId\fR map. -.PP -Since this operation is destructive and may result in a loss of data -if used carelessly, -.B eclat -will ask you for confirmation before actually deleting the volume. -You can disable this feature by using the \fB\-Y\fR (\fB\-\-yes\fR) -eclat option, e.g.: -.nf -.sp 2 -$ \fBeclat -Y delete-volume vol-d1234aef -.fi -.PP -The use of one of this option is mandatory if this command is invoked -from a shell script. .SH "SEE ALSO" .BR eclat (1), .BR eclat\-create\-volume (1), diff --git a/doc/eclat-release-address.1 b/doc/eclat-release-address.1 index 9ecefa5..b34b113 100644 --- a/doc/eclat-release-address.1 +++ b/doc/eclat-release-address.1 @@ -21,17 +21,10 @@ eclat release\-address [\fB\-\-vpc\fR] [\fB\-v\fR] IP\-OR\-ALLOCID eclat release\-address \fB\-\-help\fR .SH DESCRIPTION -This command releases IP address. This is a destructive operation, -beacause normally the released IP address cannot be recovered for your -account. Therefore, before actually doing this, -.B eclat -will ask you for confirmation interactively. You can disable this by -using the \fB\-Y\fR (\fB\-\-yes\fR) or \fB\-N\fR (\fB\-\-no\fR) -option, for positive and negative answer, correspondingly. -.PP -The use of one of this options is mandatory if -.B eclat -is started from a shell script. +This command releases an Elastic IP address. The address must have been +previously allocated using the +.B allocate-address +command. .SH OPTIONS .TP \fB\-v\fR, \fB\-\-vpc\fR diff --git a/doc/eclat.1 b/doc/eclat.1 index 3b8a9ed..9fdabd0 100644 --- a/doc/eclat.1 +++ b/doc/eclat.1 @@ -13,7 +13,7 @@ .\" .\" You should have received a copy of the GNU General Public License .\" along with Eclat. If not, see <http://www.gnu.org/licenses/>. -.TH ECLAT 1 "December 6, 2012" "ECLAT" "Eclat User Reference" +.TH ECLAT 1 "December 7, 2012" "ECLAT" "Eclat User Reference" .SH NAME eclat \- EC2 Command Line Administrator Tool .SH SYNOPSIS @@ -322,13 +322,15 @@ Set AWS region name. .SS Modifiers .TP \fB\-N\fR, \fB\-\-no\fR -Some potentially dangerous and unrecoverable operations (such as -.BR release-address ) -require confirmation. If started from the command line, -.B eclat -will ask for confirmation interactively. This option disables this -behavior and instructs the program to assume negative answer to all -confirmations. See also \fB\-Y\fR option below. +.B Eclat +can be configured to ask for confirmation before running +a potentially dangerous and unrecoverable operation (see the +section +.B CONFIRMATION +in +.BR eclat.conf (5)). +This option overrides this setting, assuming negative answer. +See also \fB\-Y\fR option below. .TP \fB\-s\fR, \fB\-\-sort\fR Sort the returned XML teee prior to outputting it. @@ -337,13 +339,15 @@ Sort the returned XML teee prior to outputting it. Use SSL (HTTPS) connection .TP \fB\-Y\fR, \fB\-\-yes\fR -Some potentially dangerous and unrecoverable operations (such as -.BR release-address ) -require confirmation. If started from the command line, -.B eclat -will ask for confirmation interactively. This option disables this -behavior and instructs the program to assume positive answer to all -confirmations. See also \fB\-N\fR option above. +.B Eclat +can be configured to ask for confirmation before running +a potentially dangerous and unrecoverable operation (see the +section +.B CONFIRMATION +in +.BR eclat.conf (5)). +This option overrides this setting, assuming positive answer. +See also \fB\-N\fR option above. .SS Mapping .TP \fB\-M\fR, \fB\-\-map\fR=\fIMAPNAME\fR diff --git a/doc/eclat.conf.5 b/doc/eclat.conf.5 index 2a14e2e..d7599b7 100644 --- a/doc/eclat.conf.5 +++ b/doc/eclat.conf.5 @@ -13,7 +13,7 @@ .\" .\" You should have received a copy of the GNU General Public License .\" along with Eclat. If not, see <http://www.gnu.org/licenses/>. -.TH ECLAT.CONF 5 "October 16, 2012" "ECLAT" "Eclat User Reference" +.TH ECLAT.CONF 5 "December 7, 2012" "ECLAT" "Eclat User Reference" .SH NAME eclat.conf \- configuration file for .BR eclat (1). @@ -533,6 +533,92 @@ expansion. .IP \n+[step]. If the format cannot determined by the above steps, an error is reported and the program terminates. +.SH CONFIRMATION +Many +.B eclat +commands result in modification of your EC2 resources. Some of them +are destructive, in the sense that such modifications cannot be undone +(e.g. deleting of a volume or termination of an instance). To reduce +the possibility of careless usage, +.B eclat +can be configured to interactively ask for a confirmation when such a +command is requested. This is configured by the +.B confirm +statement: +.PP +.nf +.in +2 +\fBconfirm\fR \fImode\fR \fBcommand\fR; +\fBconfirm\fR \fImode\fR (\fBcommand\fR[, \fBcommand\fR...]); +\fBconfirm\fR \fImode\fR \fIclass\fR; +.fi +.PP +The \fImode\fR argument specifies the requested confirmation mode. +Its valid values are: +.TP +.B tty +Ask for confirmation if the controlling terminal is a tty, i.e. if +.B eclat +is started from the command line. +.TP +.B always +Always ask for confirmation. If the controlling terminal is not a +tty, abort the command. +.TP +.B positive +Never ask. Assume positive confirmation. This is the default. +.TP +.B negative +Never ask, assuming negative confirmation. +.PP +The second argument specifies the commands to which this mode is +applied. It can be a single command name or tag, a comma-separated +list of command names or tags, or a \fBclass\fR of commands. Valid +values for \fIclass\fR are: +.TP +.B all +All commands that modify EC2 resources. +.TP +.B destructive +Commands that destructively modify resources. +.PP +Consider the following example: +.PP +.nf +.in +2 +confirm tty destructive; +confirm tty (StopInstance, StartInstance); +.fi +.PP +It instructs +.B eclat +to ask for confirmation if one of the destructive commands is +requested, or if the command is start-instance or stop-instance. +.PP +Here is an example of how this modifies the behavior of +.B release-address +command: +.PP +.nf +.if +2 +$ \fBeclat release-address 192.168.0.1\fR +Proceed with release-address [Y/n] _ +.fi +.PP +If the response begins with \fBY\fR (case-insensitive), it is taken +for a positive answer, and the command will be executed. Otherwise, +.B eclat +exits returning code 16 to the shell. +.PP +The current confirmation setting can be overridden using the \fB\-Y\fR +(\fB\-\-yes\fR) or \fB\-N\fR (\fB\-\-no\fR) command line option. The +former forces \fBpositive\fN and the latter \fBnegative\fR +confirmation mode for the requested command, e.g.: +.PP +.nf +.in +2 +$ \fBeclat -Y delete-volume vol-d1234aef\fR +.fi .SH MAPS Maps provide a way to translate arbitrary symbolic names to the Amazon resource identifiers. See the section diff --git a/etc/eclat.cfin b/etc/eclat.cfin index 006f6d9..755187a 100644 --- a/etc/eclat.cfin +++ b/etc/eclat.cfin @@ -28,5 +28,6 @@ region sa-east-1 ec2.sa-east-1.amazonaws.com; format-file "FORMATDIR/FORMATNAME.forlan"; - +# Ask for confirmation before running a destructive command. +confirm tty destructive; diff --git a/lib/confirm.c b/lib/confirm.c index bd283a1..18eb409 100644 --- a/lib/confirm.c +++ b/lib/confirm.c @@ -17,22 +17,32 @@ #include "libeclat.h" #include <sysexits.h> -int eclat_confirm_mode = -1; - int -eclat_confirm(const char *prompt, ...) +eclat_confirm(enum eclat_confirm_mode mode, const char *prompt, ...) { int rc; va_list ap; - - if (!isatty(0) || eclat_confirm_mode >= 0) { - if (eclat_confirm_mode == -1) - die(EX_USAGE, "stdin not a terminal and no default response provided; use -Y or -N option to override"); - return eclat_confirm_mode; + + switch (mode) { + case eclat_confirm_unspecified: + case eclat_confirm_positive: + return 1; + case eclat_confirm_negative: + return 0; + case eclat_confirm_tty: + if (!isatty(0)) + return 1; + break; + case eclat_confirm_always: + if (!isatty(0)) + die(EX_USAGE, + "confirmation required, " + "but stdin is not a terminal"); + break; } va_start(ap, prompt); - rc = eclat_vgetyn(!!eclat_confirm_mode, prompt, ap); + rc = eclat_vgetyn(-1, prompt, ap); va_end(ap); return rc; diff --git a/lib/forlan.c b/lib/forlan.c index 7a9dab1..828fb09 100644 --- a/lib/forlan.c +++ b/lib/forlan.c @@ -58,7 +58,6 @@ static unsigned long strtots(const char *input) { struct tm tm; - time_t t; /* 2012-06-21T10:36:48.000Z */ memset(&tm, 0, sizeof(tm)); diff --git a/lib/getyn.c b/lib/getyn.c index 8ec8a07..ee49d3f 100644 --- a/lib/getyn.c +++ b/lib/getyn.c @@ -21,10 +21,14 @@ int eclat_vgetyn(int dfl, const char *prompt, va_list ap) { - static char *hint[] = { "y/N", "Y/n" }; + static char *hint[] = { "y/n", "y/N", "Y/n" }; int state = 0; int c, resp; - + + if (dfl < -1) + dfl = -1; + else if (dfl > 1) + dfl = 1; do { switch (state) { case 1: @@ -43,7 +47,9 @@ eclat_vgetyn(int dfl, const char *prompt, va_list ap) case 'N': return 0; case '\n': - return dfl; + if (dfl >= 0) + return dfl; + /* fall through */ default: err("Please, reply 'y' or 'n'"); } @@ -52,7 +58,7 @@ eclat_vgetyn(int dfl, const char *prompt, va_list ap) break; case 0: vfprintf(stdout, prompt, ap); - fprintf(stdout, " [%s] ", hint[dfl]); + fprintf(stdout, " [%s] ", hint[dfl+1]); fflush(stdout); state = 1; break; diff --git a/lib/libeclat.h b/lib/libeclat.h index 35d2538..a558dae 100644 --- a/lib/libeclat.h +++ b/lib/libeclat.h @@ -110,8 +110,16 @@ char *eclat_getans(char *prompt, char *dfl, int pass); int eclat_getyn(int dfl, const char *prompt, ...); int eclat_vgetyn(int dfl, const char *prompt, va_list ap); -extern int eclat_confirm_mode; -int eclat_confirm(const char *prompt, ...); +enum eclat_confirm_mode +{ + eclat_confirm_unspecified, + eclat_confirm_positive, + eclat_confirm_negative, + eclat_confirm_tty, + eclat_confirm_always +}; + +int eclat_confirm(enum eclat_confirm_mode mode, const char *prompt, ...); #define ECLAT_MAP_OPEN 0x01 diff --git a/src/cmdline.opt b/src/cmdline.opt index 2d75f24..54ec85f 100644 --- a/src/cmdline.opt +++ b/src/cmdline.opt @@ -156,13 +156,13 @@ END OPTION(yes,Y,, [<assume `yes' to all questions>]) BEGIN - eclat_confirm_mode = 1; + confirm_mode = eclat_confirm_positive; END OPTION(no,N,, [<assume `no' to all questions>]) BEGIN - eclat_confirm_mode = 0; + confirm_mode = eclat_confirm_negative; END GROUP(Identifier translation) diff --git a/src/config.c b/src/config.c index f58747c..48bece7 100644 --- a/src/config.c +++ b/src/config.c @@ -149,6 +149,76 @@ cb_define_format(enum grecs_callback_command cmd, return 0; } +static int +cb_confirm(enum grecs_callback_command cmd, + grecs_locus_t *locus, + void *varptr, + grecs_value_t *value, + void *cb_data) +{ + struct grecs_list_entry *ep; + grecs_value_t *argval; + enum eclat_confirm_mode cfmode; + char *s; + + if (cmd != grecs_callback_set_value) { + grecs_error(locus, 0, "Unexpected block statement"); + return 1; + } + if (!value || value->type != GRECS_TYPE_ARRAY || value->v.arg.c != 2) { + grecs_error(locus, 0, "expected two values"); + return 1; + } + + if (value->v.arg.v[0]->type != GRECS_TYPE_STRING) { + grecs_error(locus, 0, "first argument not a string"); + return 1; + } + + switch (value->v.arg.v[1]->type) { + case GRECS_TYPE_STRING: + set_command_confirmation(value->v.arg.v[1]->v.string, cfmode, + &value->v.arg.v[1]->locus); + return 0; + + case GRECS_TYPE_LIST: + break; + + default: + grecs_error(locus, 0, "second argument not a list"); + return 1; + } + + s = value->v.arg.v[0]->v.string; + if (strcmp(s, "positive") == 0) + cfmode = eclat_confirm_positive; + else if (strcmp(s, "negative") == 0) + cfmode = eclat_confirm_negative; + else if (strcmp(s, "tty") == 0) + cfmode = eclat_confirm_tty; + else if (strcmp(s, "always") == 0) + cfmode = eclat_confirm_always; + else { + grecs_error(&value->v.arg.v[0]->locus, 0, + "unrecognized confirmation mode"); + return 1; + } + + for (ep = value->v.arg.v[1]->v.list->head; ep; ep = ep->next) { + argval = ep->data; + if (argval->type != GRECS_TYPE_STRING) { + grecs_error(&argval->locus, 0, + "list element not a string"); + continue; + } + + set_command_confirmation(argval->v.string, cfmode, + &argval->locus); + } + + return 0; +} + static struct grecs_keyword ssl_kw[] = { { "enable", NULL, "Use SSL", @@ -218,6 +288,10 @@ static struct grecs_keyword eclat_kw[] = { grecs_type_string, GRECS_DFLT, &format_file }, { "map", "name: string", "Configure a map", grecs_type_section, GRECS_INAC }, + { "confirm", + "<mode: { positive | negative | tty | always }> <commands: list>", + "Set confirmation mode", + grecs_type_string, GRECS_LIST, NULL, 0, cb_confirm }, { NULL } }; diff --git a/src/cretags.c b/src/cretags.c index f0d3e1f..3554442 100644 --- a/src/cretags.c +++ b/src/cretags.c @@ -107,6 +107,7 @@ process_tags(CURL *curl, int argc, char **argv, const char *action) } } free(bufptr); + return eclat_send_query(curl, q); } diff --git a/src/delvol.c b/src/delvol.c index 675bd56..5180042 100644 --- a/src/delvol.c +++ b/src/delvol.c @@ -32,8 +32,6 @@ eclat_delete_volume(CURL *curl, int argc, char **argv) if (argc != 1) die(EX_USAGE, "bad number of arguments"); translate_ids(argc, argv, "VolumeId"); - if (!eclat_confirm("Really delete volume %s", argv[0])) - exit(EX_CANCELLED); q = eclat_query_create(use_ssl ? EC2_QF_HTTPS : 0, endpoint, "/"); eclat_query_add_param(q, "Action", "DeleteVolume"); eclat_query_add_param(q, "VolumeId", argv[0]); diff --git a/src/eclat.c b/src/eclat.c index 8eb2402..ef9dfd5 100644 --- a/src/eclat.c +++ b/src/eclat.c @@ -36,6 +36,7 @@ char *format_name_option; int sort_option; char *format_file; char *test_map_name; +enum eclat_confirm_mode confirm_mode; FILE *xml_dump_file; @@ -197,18 +198,26 @@ node_ident_cmp(struct grecs_node const *a, struct grecs_node const *b) #include "cmdline.h" +#define CMD_MOD 0x01 +#define CMD_DESTR 0x02 + struct command { const char *ident; const char *tag; eclat_command_handler_t handler; + int flags; + enum eclat_confirm_mode confirm; char *fmt; struct grecs_locus locus; }; struct command cmdtab[] = { - { "start-instances", "StartInstances", eclat_start_instance }, - { "stop-instances", "StopInstances", eclat_stop_instance }, - { "reboot-instances", "RebootInstances", eclat_reboot_instance }, + { "start-instances", "StartInstances", eclat_start_instance, + CMD_MOD }, + { "stop-instances", "StopInstances", eclat_stop_instance, + CMD_MOD }, + { "reboot-instances", "RebootInstances", eclat_reboot_instance, + CMD_MOD }, { "describe-addresses", "DescribeAddresses", eclat_describe_addresses }, { "describe-tags", "DescribeTags", eclat_describe_tags }, @@ -221,17 +230,17 @@ struct command cmdtab[] = { { "describe-volumes", "DescribeVolumes", eclat_describe_volumes }, { "allocate-address", "AllocateAddress", - eclat_allocate_address }, + eclat_allocate_address, CMD_MOD }, { "release-address", "ReleaseAddress", - eclat_release_address }, + eclat_release_address, CMD_MOD|CMD_DESTR }, { "associate-address", "AssociateAddress", - eclat_associate_address }, + eclat_associate_address, CMD_MOD }, { "disassociate-address", "DisassociateAddress", - eclat_disassociate_address }, + eclat_disassociate_address, CMD_MOD }, { "create-tags", "CreateTags", - eclat_create_tags }, + eclat_create_tags, CMD_MOD }, { "delete-tags", "DeleteTags", - eclat_delete_tags }, + eclat_delete_tags, CMD_MOD|CMD_DESTR }, { "get-console-output", "GetConsoleOutput", eclat_get_console_output }, { "describe-security-groups", "DescribeSecurityGroups", @@ -241,15 +250,15 @@ struct command cmdtab[] = { { "describe-snapshots", "DescribeSnapshots", eclat_describe_snapshots }, { "delete-snapshot", "DeleteSnapshot", - eclat_delete_snapshot }, + eclat_delete_snapshot, CMD_MOD|CMD_DESTR }, { "describe-avaialbility-zones", "DescribeAvaialbilityZones", eclat_describe_avaialbility_zones }, { "describe-regions", "DescribeRegions", eclat_describe_regions }, { "create-volume", "CreateVolume", - eclat_create_volume }, + eclat_create_volume, CMD_MOD }, { "delete-volume", "DeleteVolume", - eclat_delete_volume }, + eclat_delete_volume, CMD_MOD|CMD_DESTR }, }; size_t cmdcnt = sizeof(cmdtab) / sizeof(cmdtab[0]); @@ -362,6 +371,33 @@ find_command_tag(const char *tag) } void +set_command_confirmation(const char *name, enum eclat_confirm_mode cfmode, + grecs_locus_t *locus) +{ + struct command *cp; + int flag = 0; + + if (strcmp(name, "all") == 0) + flag = CMD_MOD; + else if (strcmp(name, "destructive") == 0) + flag = CMD_DESTR; + else { + cp = find_command_tag(name); + + if (!cp) + grecs_error(locus, 0, "unknown command or class"); + else + cp->confirm = cfmode; + return; + } + + for (cp = cmdtab; cp < cmdtab + cmdcnt; cp++) { + if (cp->flags & flag) + cp->confirm = cfmode; + } +} + +void set_command_format(const char *name, const char *format, grecs_locus_t *locus) { struct command *cp = find_command_tag(name); @@ -725,6 +761,12 @@ main(int argc, char **argv) } } + if (confirm_mode == eclat_confirm_unspecified) + confirm_mode = command->confirm; + if (!eclat_confirm(confirm_mode, + "Proceed with %s", command->ident)) + die(EX_CANCELLED, "command not confirmed"); + rc = command->handler(curl, argc, argv); if (rc) exit(rc); diff --git a/src/eclat.h b/src/eclat.h index ceca4d4..40c4014 100644 --- a/src/eclat.h +++ b/src/eclat.h @@ -52,6 +52,7 @@ extern char *secret_key; extern char *format_file; extern int translate_option; extern char *custom_map; +extern enum eclat_confirm_mode confirm_mode; typedef int (*config_finish_hook_t) (void*); @@ -130,3 +131,6 @@ forlan_eval_env_t find_format(const char *name); void generic_parse_options(const char *pname, const char *docstring, int argc, char *argv[], int *index); extern struct grecs_proginfo *generic_proginfo; + +void set_command_confirmation(const char *name, enum eclat_confirm_mode cfmode, + grecs_locus_t *locus); diff --git a/src/reladdr.c b/src/reladdr.c index 5dc4ead..a98c0fb 100644 --- a/src/reladdr.c +++ b/src/reladdr.c @@ -30,9 +30,6 @@ eclat_release_address(CURL *curl, int argc, char **argv) if (argc != 1) die(EX_USAGE, "wrong number of arguments to release-address"); - if (!eclat_confirm("Really release %s %s", - vpc ? "allocation ID" : "IP", argv[0])) - exit(EX_CANCELLED); q = eclat_query_create(use_ssl ? EC2_QF_HTTPS : 0, endpoint, "/"); eclat_query_add_param(q, "Action", "ReleaseAddress"); |