AIP-2604

Numeric arguments

Some API fields refer to either a percentage or a fixed number. For example, in GCE a group can be created with a configurable limit for either the percentage or number of instances in the group that can undergo maintenance simultaneously:

message ConcurrencyControl {
  enum LimitType {
    INVALID = 0;
    PERCENT = 1;
    FIXED = 2;
  }
  optional int32 concurrency_limit = 1;
  optional LimitType limit_type = 2;
}

Guidance

Flag layout

In gcloud, such API fields should correspond to a mutually exclusive group consisting of two flags: one for specifying a number, and the other for specifying a percentage. The API field in the example above would thus correspond to the following surface specification in gcloud:

- group:
    mutex: true
    arguments:
    - name: concurrency-limit
      help_text: |
        Maximum number of instances in the group that can undergo maintenance
        simultaneously.
    - name: concurrency-limit-percent
      help_text: |
        Integer from 0 to 100 representing the maximum percentage of instances
        in the group that can undergo maintenance simultaneously.

Flag naming

Any flag taking a percentage should end with -percent (not -percentage).

Flag type

Any flag taking a percentage should take an integer from 0 to 100. If more precision is required, it is acceptable to take a float from 0 to 100.

Alternatives considered

One flag, where percentage is specified if the flag value ends with ‘%’, and number is assumed otherwise

In other words, to specify 10% one would use --concurrency-limit=10%, and to specify 10 instances one would use --concurrency-limit=10.

This would be a cleaner design since it naturally maps a single concept (the limit) to a single flag (--concurrency-limit), avoiding the need for additional flags that clutter the help text.

However, the main reason not to prefer this approach is that it significantly increases the risk of user error. For instance, suppose a group currently contains 20 instances with a concurrency limit of 10%, and a user wishes to update the limit to 20%. If the user issues a describe command (or performs a GET request) to see the existing limit, the API response will contain something like:

concurrencyControl:
  concurrencyLimit: 10
  limitType: PERCENT

It’s easy for the user to gloss over limitType and just assume that --concurrency-limit=20 will update the limit to 20%. However, in reality this would set the limit to 20 instances (100% of the group), potentially leading to catastrophic consequences.

The recommend approach avoids this ambiguity at the expense of some elegance, but we deem this tradeoff necessary.

One flag, where percentage is specified if the flag value ends with ‘%’, and number is specified if the flag value ends with ‘n’

In other words, to specify 10% one would use --concurrency-limit=10%, and to specify 10 instances one would use --concurrency-limit=10n. It would be an error to provide a value that doesn’t end in ‘%’ or ‘n’.

While this avoids the potential for a user to confuse percentages and numbers, requiring ‘n’ at the end of a number is inelegant UX. A number should obviously resemble a number, and the choice of ‘n’ is also rather arbitrary.

Exceptions

This CIP does not apply for API fields like timestamps, where we allow the user to enter either absolute times or durations in the same flag. This is because absolute times and durations have natural formats that differ, and thus there’s no potential for a user to confuse the two. In general if this is the case, one flag that accepts both formats should be preferred for the sake of UX simplicity.